1
- use ruff_diagnostics:: { Diagnostic , Violation } ;
1
+ use ruff_diagnostics:: { Applicability , Diagnostic , Edit , Fix , FixAvailability , Violation } ;
2
2
use ruff_macros:: { derive_message_formats, violation} ;
3
3
use ruff_python_ast:: visitor:: Visitor ;
4
- use ruff_python_ast:: { self as ast, ExceptHandler } ;
4
+ use ruff_python_ast:: { self as ast, ExceptHandler , Expr } ;
5
5
use ruff_python_semantic:: analyze:: logging:: exc_info;
6
6
use ruff_python_stdlib:: logging:: LoggingLevel ;
7
7
use ruff_text_size:: Ranged ;
8
8
9
9
use crate :: checkers:: ast:: Checker ;
10
+ use crate :: importer:: ImportRequest ;
10
11
use crate :: rules:: tryceratops:: helpers:: LoggerCandidateVisitor ;
11
12
12
13
/// ## What it does
@@ -42,16 +43,28 @@ use crate::rules::tryceratops::helpers::LoggerCandidateVisitor;
42
43
/// logging.exception("Exception occurred")
43
44
/// ```
44
45
///
46
+ /// ## Fix safety
47
+ /// This rule's fix is marked as safe when run against `logging.error` calls,
48
+ /// but unsafe when marked against other logger-like calls (e.g.,
49
+ /// `logger.error`), since the rule is prone to false positives when detecting
50
+ /// logger-like calls outside of the `logging` module.
51
+ ///
45
52
/// ## References
46
53
/// - [Python documentation: `logging.exception`](https://docs.python.org/3/library/logging.html#logging.exception)
47
54
#[ violation]
48
55
pub struct ErrorInsteadOfException ;
49
56
50
57
impl Violation for ErrorInsteadOfException {
58
+ const FIX_AVAILABILITY : FixAvailability = FixAvailability :: Sometimes ;
59
+
51
60
#[ derive_message_formats]
52
61
fn message ( & self ) -> String {
53
62
format ! ( "Use `logging.exception` instead of `logging.error`" )
54
63
}
64
+
65
+ fn fix_title ( & self ) -> Option < String > {
66
+ Some ( format ! ( "Replace with `exception`" ) )
67
+ }
55
68
}
56
69
57
70
/// TRY400
@@ -67,9 +80,60 @@ pub(crate) fn error_instead_of_exception(checker: &mut Checker, handlers: &[Exce
67
80
for ( expr, logging_level) in calls {
68
81
if matches ! ( logging_level, LoggingLevel :: Error ) {
69
82
if exc_info ( & expr. arguments , checker. semantic ( ) ) . is_none ( ) {
70
- checker
71
- . diagnostics
72
- . push ( Diagnostic :: new ( ErrorInsteadOfException , expr. range ( ) ) ) ;
83
+ let mut diagnostic = Diagnostic :: new ( ErrorInsteadOfException , expr. range ( ) ) ;
84
+ if checker. settings . preview . is_enabled ( ) {
85
+ match expr. func . as_ref ( ) {
86
+ Expr :: Attribute ( ast:: ExprAttribute { attr, .. } ) => {
87
+ diagnostic. set_fix ( Fix :: applicable_edit (
88
+ Edit :: range_replacement ( "exception" . to_string ( ) , attr. range ( ) ) ,
89
+ // When run against `logging.error`, the fix is safe; otherwise,
90
+ // the object _may_ not be a logger.
91
+ if checker
92
+ . semantic ( )
93
+ . resolve_call_path ( expr. func . as_ref ( ) )
94
+ . is_some_and ( |call_path| {
95
+ matches ! ( call_path. as_slice( ) , [ "logging" , "error" ] )
96
+ } )
97
+ {
98
+ Applicability :: Safe
99
+ } else {
100
+ Applicability :: Unsafe
101
+ } ,
102
+ ) ) ;
103
+ }
104
+ Expr :: Name ( _) => {
105
+ diagnostic. try_set_fix ( || {
106
+ let ( import_edit, binding) =
107
+ checker. importer ( ) . get_or_import_symbol (
108
+ & ImportRequest :: import ( "logging" , "exception" ) ,
109
+ expr. start ( ) ,
110
+ checker. semantic ( ) ,
111
+ ) ?;
112
+ let name_edit =
113
+ Edit :: range_replacement ( binding, expr. func . range ( ) ) ;
114
+ Ok ( Fix :: applicable_edits (
115
+ import_edit,
116
+ [ name_edit] ,
117
+ // When run against `logging.error`, the fix is safe; otherwise,
118
+ // the object _may_ not be a logger.
119
+ if checker
120
+ . semantic ( )
121
+ . resolve_call_path ( expr. func . as_ref ( ) )
122
+ . is_some_and ( |call_path| {
123
+ matches ! ( call_path. as_slice( ) , [ "logging" , "error" ] )
124
+ } )
125
+ {
126
+ Applicability :: Safe
127
+ } else {
128
+ Applicability :: Unsafe
129
+ } ,
130
+ ) )
131
+ } ) ;
132
+ }
133
+ _ => { }
134
+ }
135
+ }
136
+ checker. diagnostics . push ( diagnostic) ;
73
137
}
74
138
}
75
139
}
0 commit comments