diff --git a/crates/ruff/src/rules/flake8_simplify/rules/suppressible_exception.rs b/crates/ruff/src/rules/flake8_simplify/rules/suppressible_exception.rs index 4118b74d8e8db..c7a2df4b52093 100644 --- a/crates/ruff/src/rules/flake8_simplify/rules/suppressible_exception.rs +++ b/crates/ruff/src/rules/flake8_simplify/rules/suppressible_exception.rs @@ -1,36 +1,44 @@ -use rustpython_parser::ast::{Excepthandler, ExcepthandlerKind, Located, Stmt, StmtKind}; +use rustpython_parser::ast::{Excepthandler, ExcepthandlerKind, Located, Location, Stmt, StmtKind}; -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::call_path::compose_call_path; use ruff_python_ast::helpers; use ruff_python_ast::types::Range; +use crate::autofix::actions::get_or_import_symbol; use crate::checkers::ast::Checker; +use crate::registry::AsRule; #[violation] pub struct SuppressibleException { pub exception: String, } -impl Violation for SuppressibleException { +impl AlwaysAutofixableViolation for SuppressibleException { #[derive_message_formats] fn message(&self) -> String { let SuppressibleException { exception } = self; - format!("Use `contextlib.suppress({exception})` instead of try-except-pass") + format!("Use `contextlib.suppress({exception})` instead of `try`-`except`-`pass`") + } + + fn autofix_title(&self) -> String { + let SuppressibleException { exception } = self; + format!("Replace with `contextlib.suppress({exception})`") } } + /// SIM105 pub fn suppressible_exception( checker: &mut Checker, stmt: &Stmt, - body: &[Stmt], + try_body: &[Stmt], handlers: &[Excepthandler], orelse: &[Stmt], finalbody: &[Stmt], ) { if !matches!( - body, + try_body, [Located { node: StmtKind::Delete { .. } | StmtKind::Assign { .. } @@ -62,10 +70,36 @@ pub fn suppressible_exception( } else { handler_names.join(", ") }; - checker.diagnostics.push(Diagnostic::new( - SuppressibleException { exception }, + let mut diagnostic = Diagnostic::new( + SuppressibleException { + exception: exception.clone(), + }, Range::from(stmt), - )); + ); + + if checker.patch(diagnostic.kind.rule()) { + diagnostic.try_set_fix(|| { + let (import_edit, binding) = get_or_import_symbol( + "contextlib", + "suppress", + &checker.ctx, + &checker.importer, + checker.locator, + )?; + let try_ending = stmt.location.with_col_offset(3); // size of "try" + let replace_try = Edit::replacement( + format!("with {binding}({exception})"), + stmt.location, + try_ending, + ); + let handler_line_begin = Location::new(handler.location.row(), 0); + let remove_handler = + Edit::deletion(handler_line_begin, handler.end_location.unwrap()); + Ok(Fix::from_iter([import_edit, replace_try, remove_handler])) + }); + } + + checker.diagnostics.push(diagnostic); } } } diff --git a/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM105_SIM105.py.snap b/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM105_SIM105.py.snap index f5d9ac276b92f..f28d9e8688574 100644 --- a/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM105_SIM105.py.snap +++ b/crates/ruff/src/rules/flake8_simplify/snapshots/ruff__rules__flake8_simplify__tests__SIM105_SIM105.py.snap @@ -4,9 +4,9 @@ expression: diagnostics --- - kind: name: SuppressibleException - body: "Use `contextlib.suppress(ValueError)` instead of try-except-pass" - suggestion: ~ - fixable: false + body: "Use `contextlib.suppress(ValueError)` instead of `try`-`except`-`pass`" + suggestion: "Replace with `contextlib.suppress(ValueError)`" + fixable: true location: row: 4 column: 0 @@ -14,13 +14,34 @@ expression: diagnostics row: 7 column: 8 fix: - edits: [] + edits: + - content: "import contextlib\n" + location: + row: 1 + column: 0 + end_location: + row: 1 + column: 0 + - content: with contextlib.suppress(ValueError) + location: + row: 4 + column: 0 + end_location: + row: 4 + column: 3 + - content: "" + location: + row: 6 + column: 0 + end_location: + row: 7 + column: 8 parent: ~ - kind: name: SuppressibleException - body: "Use `contextlib.suppress(ValueError, OSError)` instead of try-except-pass" - suggestion: ~ - fixable: false + body: "Use `contextlib.suppress(ValueError, OSError)` instead of `try`-`except`-`pass`" + suggestion: "Replace with `contextlib.suppress(ValueError, OSError)`" + fixable: true location: row: 9 column: 0 @@ -28,13 +49,34 @@ expression: diagnostics row: 12 column: 8 fix: - edits: [] + edits: + - content: "import contextlib\n" + location: + row: 1 + column: 0 + end_location: + row: 1 + column: 0 + - content: "with contextlib.suppress(ValueError, OSError)" + location: + row: 9 + column: 0 + end_location: + row: 9 + column: 3 + - content: "" + location: + row: 11 + column: 0 + end_location: + row: 12 + column: 8 parent: ~ - kind: name: SuppressibleException - body: "Use `contextlib.suppress(Exception)` instead of try-except-pass" - suggestion: ~ - fixable: false + body: "Use `contextlib.suppress(Exception)` instead of `try`-`except`-`pass`" + suggestion: "Replace with `contextlib.suppress(Exception)`" + fixable: true location: row: 14 column: 0 @@ -42,13 +84,34 @@ expression: diagnostics row: 17 column: 8 fix: - edits: [] + edits: + - content: "import contextlib\n" + location: + row: 1 + column: 0 + end_location: + row: 1 + column: 0 + - content: with contextlib.suppress(Exception) + location: + row: 14 + column: 0 + end_location: + row: 14 + column: 3 + - content: "" + location: + row: 16 + column: 0 + end_location: + row: 17 + column: 8 parent: ~ - kind: name: SuppressibleException - body: "Use `contextlib.suppress(a.Error, b.Error)` instead of try-except-pass" - suggestion: ~ - fixable: false + body: "Use `contextlib.suppress(a.Error, b.Error)` instead of `try`-`except`-`pass`" + suggestion: "Replace with `contextlib.suppress(a.Error, b.Error)`" + fixable: true location: row: 19 column: 0 @@ -56,6 +119,27 @@ expression: diagnostics row: 22 column: 8 fix: - edits: [] + edits: + - content: "import contextlib\n" + location: + row: 1 + column: 0 + end_location: + row: 1 + column: 0 + - content: "with contextlib.suppress(a.Error, b.Error)" + location: + row: 19 + column: 0 + end_location: + row: 19 + column: 3 + - content: "" + location: + row: 21 + column: 0 + end_location: + row: 22 + column: 8 parent: ~