1
1
use ruff_diagnostics:: { AlwaysFixableViolation , Diagnostic , Edit , Fix } ;
2
2
use ruff_macros:: { derive_message_formats, violation} ;
3
3
use ruff_python_ast:: { self as ast, Expr } ;
4
+ use ruff_python_semantic:: BindingKind ;
4
5
use ruff_text_size:: Ranged ;
5
6
6
7
use crate :: checkers:: ast:: Checker ;
@@ -15,6 +16,13 @@ use crate::checkers::ast::Checker;
15
16
///
16
17
/// Removing the parentheses makes the code more concise.
17
18
///
19
+ /// ## Known problems
20
+ /// Parentheses can only be omitted if the exception is a class, as opposed to
21
+ /// a function call. This rule isn't always capable of distinguishing between
22
+ /// the two. For example, if you define a method `get_exception` that itself
23
+ /// returns an exception object, this rule will falsy mark the parentheses
24
+ /// in `raise get_exception()` as unnecessary.
25
+ ///
18
26
/// ## Example
19
27
/// ```python
20
28
/// raise TypeError()
@@ -54,25 +62,32 @@ pub(crate) fn unnecessary_paren_on_raise_exception(checker: &mut Checker, expr:
54
62
55
63
if arguments. is_empty ( ) {
56
64
// `raise func()` still requires parentheses; only `raise Class()` does not.
57
- if checker
58
- . semantic ( )
59
- . lookup_attribute ( func)
60
- . is_some_and ( |id| checker. semantic ( ) . binding ( id) . kind . is_function_definition ( ) )
61
- {
62
- return ;
63
- }
65
+ let exception_type = if let Some ( id) = checker. semantic ( ) . lookup_attribute ( func) {
66
+ match checker. semantic ( ) . binding ( id) . kind {
67
+ BindingKind :: FunctionDefinition ( _) => return ,
68
+ BindingKind :: ClassDefinition ( _) => Some ( ExceptionType :: Class ) ,
69
+ BindingKind :: Builtin => Some ( ExceptionType :: Builtin ) ,
70
+ _ => None ,
71
+ }
72
+ } else {
73
+ None
74
+ } ;
64
75
65
76
// `ctypes.WinError()` is a function, not a class. It's part of the standard library, so
66
77
// we might as well get it right.
67
- if checker
68
- . semantic ( )
69
- . resolve_call_path ( func)
70
- . is_some_and ( |call_path| matches ! ( call_path. as_slice( ) , [ "ctypes" , "WinError" ] ) )
78
+ if exception_type
79
+ . as_ref ( )
80
+ . is_some_and ( ExceptionType :: is_builtin)
81
+ && checker
82
+ . semantic ( )
83
+ . resolve_call_path ( func)
84
+ . is_some_and ( |call_path| matches ! ( call_path. as_slice( ) , [ "ctypes" , "WinError" ] ) )
71
85
{
72
86
return ;
73
87
}
74
88
75
89
let mut diagnostic = Diagnostic :: new ( UnnecessaryParenOnRaiseException , arguments. range ( ) ) ;
90
+
76
91
// If the arguments are immediately followed by a `from`, insert whitespace to avoid
77
92
// a syntax error, as in:
78
93
// ```python
@@ -85,13 +100,25 @@ pub(crate) fn unnecessary_paren_on_raise_exception(checker: &mut Checker, expr:
85
100
. next ( )
86
101
. is_some_and ( char:: is_alphanumeric)
87
102
{
88
- diagnostic. set_fix ( Fix :: safe_edit ( Edit :: range_replacement (
89
- " " . to_string ( ) ,
90
- arguments. range ( ) ,
91
- ) ) ) ;
103
+ diagnostic. set_fix ( if exception_type. is_some ( ) {
104
+ Fix :: safe_edit ( Edit :: range_replacement ( " " . to_string ( ) , arguments. range ( ) ) )
105
+ } else {
106
+ Fix :: unsafe_edit ( Edit :: range_replacement ( " " . to_string ( ) , arguments. range ( ) ) )
107
+ } ) ;
92
108
} else {
93
- diagnostic. set_fix ( Fix :: safe_edit ( Edit :: range_deletion ( arguments. range ( ) ) ) ) ;
109
+ diagnostic. set_fix ( if exception_type. is_some ( ) {
110
+ Fix :: safe_edit ( Edit :: range_deletion ( arguments. range ( ) ) )
111
+ } else {
112
+ Fix :: unsafe_edit ( Edit :: range_deletion ( arguments. range ( ) ) )
113
+ } ) ;
94
114
}
115
+
95
116
checker. diagnostics . push ( diagnostic) ;
96
117
}
97
118
}
119
+
120
+ #[ derive( Debug , is_macro:: Is ) ]
121
+ enum ExceptionType {
122
+ Class ,
123
+ Builtin ,
124
+ }
0 commit comments