diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B014.py b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B014.py index 99cbd2a249..8798c51eea 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B014.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B014.py @@ -88,3 +88,14 @@ try: pas except(re.error, re.error): p + + +try: + pass +except ( + ValueError, + ValueError, + # text + TypeError, +): + pass diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs index 04a0c3c552..1c8c8774fe 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/duplicate_exceptions.rs @@ -1,10 +1,10 @@ use itertools::Itertools; -use rustc_hash::{FxHashMap, FxHashSet}; - +use ruff_diagnostics::Applicability; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::name::UnqualifiedName; use ruff_python_ast::{self as ast, ExceptHandler, Expr, ExprContext}; use ruff_text_size::{Ranged, TextRange}; +use rustc_hash::{FxHashMap, FxHashSet}; use crate::checkers::ast::Checker; use crate::fix::edits::pad; @@ -37,6 +37,9 @@ use crate::{Edit, Fix}; /// ... /// ``` /// +/// ## Fix safety +/// This rule's fix is marked as safe, unless the exception handler contains comments. +/// /// ## References /// - [Python documentation: `except` clause](https://docs.python.org/3/reference/compound_stmts.html#except-clause) #[derive(ViolationMetadata)] @@ -154,22 +157,32 @@ fn duplicate_handler_exceptions<'a>( }, expr.range(), ); - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - // Single exceptions don't require parentheses, but since we're _removing_ - // parentheses, insert whitespace as needed. - if let [elt] = unique_elts.as_slice() { - pad( - checker.generator().expr(elt), - expr.range(), - checker.locator(), - ) - } else { - // Multiple exceptions must always be parenthesized. This is done - // manually as the generator never parenthesizes lone tuples. - format!("({})", checker.generator().expr(&type_pattern(unique_elts))) - }, - expr.range(), - ))); + + let applicability = if checker.comment_ranges().intersects(expr.range()) { + Applicability::Unsafe + } else { + Applicability::Safe + }; + + diagnostic.set_fix(Fix::applicable_edit( + Edit::range_replacement( + // Single exceptions don't require parentheses, but since we're _removing_ + // parentheses, insert whitespace as needed. + if let [elt] = unique_elts.as_slice() { + pad( + checker.generator().expr(elt), + expr.range(), + checker.locator(), + ) + } else { + // Multiple exceptions must always be parenthesized. This is done + // manually as the generator never parenthesizes lone tuples. + format!("({})", checker.generator().expr(&type_pattern(unique_elts))) + }, + expr.range(), + ), + applicability, + )); } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B014_B014.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B014_B014.py.snap index e4bd89d12e..406eb0469f 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B014_B014.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B014_B014.py.snap @@ -96,3 +96,34 @@ help: De-duplicate exceptions - except(re.error, re.error): 89 + except re.error: 90 | p +91 | +92 | + +B014 [*] Exception handler with duplicate exception: `ValueError` + --> B014.py:95:8 + | + 93 | try: + 94 | pass + 95 | except ( + | ________^ + 96 | | ValueError, + 97 | | ValueError, + 98 | | # text + 99 | | TypeError, +100 | | ): + | |_^ +101 | pass + | +help: De-duplicate exceptions +92 | +93 | try: +94 | pass + - except ( + - ValueError, + - ValueError, + - # text + - TypeError, + - ): +95 + except (ValueError, TypeError): +96 | pass +note: This is an unsafe fix and may change runtime behavior