-
Notifications
You must be signed in to change notification settings - Fork 902
/
unnecessary_paren_on_raise_exception.rs
124 lines (113 loc) · 4.02 KB
/
unnecessary_paren_on_raise_exception.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::BindingKind;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for unnecessary parentheses on raised exceptions.
///
/// ## Why is this bad?
/// If an exception is raised without any arguments, parentheses are not
/// required, as the `raise` statement accepts either an exception instance
/// or an exception class (which is then implicitly instantiated).
///
/// Removing the parentheses makes the code more concise.
///
/// ## Known problems
/// Parentheses can only be omitted if the exception is a class, as opposed to
/// a function call. This rule isn't always capable of distinguishing between
/// the two. For example, if you define a method `get_exception` that itself
/// returns an exception object, this rule will falsy mark the parentheses
/// in `raise get_exception()` as unnecessary.
///
/// ## Example
/// ```python
/// raise TypeError()
/// ```
///
/// Use instead:
/// ```python
/// raise TypeError
/// ```
///
/// ## References
/// - [Python documentation: The `raise` statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement)
#[violation]
pub struct UnnecessaryParenOnRaiseException;
impl AlwaysFixableViolation for UnnecessaryParenOnRaiseException {
#[derive_message_formats]
fn message(&self) -> String {
format!("Unnecessary parentheses on raised exception")
}
fn fix_title(&self) -> String {
format!("Remove unnecessary parentheses")
}
}
/// RSE102
pub(crate) fn unnecessary_paren_on_raise_exception(checker: &mut Checker, expr: &Expr) {
let Expr::Call(ast::ExprCall {
func,
arguments,
range: _,
}) = expr
else {
return;
};
if arguments.is_empty() {
// `raise func()` still requires parentheses; only `raise Class()` does not.
let exception_type = if let Some(id) = checker.semantic().lookup_attribute(func) {
match checker.semantic().binding(id).kind {
BindingKind::FunctionDefinition(_) => return,
BindingKind::ClassDefinition(_) => Some(ExceptionType::Class),
BindingKind::Builtin => Some(ExceptionType::Builtin),
_ => None,
}
} else {
None
};
// `ctypes.WinError()` is a function, not a class. It's part of the standard library, so
// we might as well get it right.
if exception_type
.as_ref()
.is_some_and(ExceptionType::is_builtin)
&& checker
.semantic()
.resolve_call_path(func)
.is_some_and(|call_path| matches!(call_path.as_slice(), ["ctypes", "WinError"]))
{
return;
}
let mut diagnostic = Diagnostic::new(UnnecessaryParenOnRaiseException, arguments.range());
// If the arguments are immediately followed by a `from`, insert whitespace to avoid
// a syntax error, as in:
// ```python
// raise IndexError()from ZeroDivisionError
// ```
if checker
.locator()
.after(arguments.end())
.chars()
.next()
.is_some_and(char::is_alphanumeric)
{
diagnostic.set_fix(if exception_type.is_some() {
Fix::safe_edit(Edit::range_replacement(" ".to_string(), arguments.range()))
} else {
Fix::unsafe_edit(Edit::range_replacement(" ".to_string(), arguments.range()))
});
} else {
diagnostic.set_fix(if exception_type.is_some() {
Fix::safe_edit(Edit::range_deletion(arguments.range()))
} else {
Fix::unsafe_edit(Edit::range_deletion(arguments.range()))
});
}
checker.diagnostics.push(diagnostic);
}
}
#[derive(Debug, is_macro::Is)]
enum ExceptionType {
Class,
Builtin,
}