diff --git a/crates/ruff/resources/test/fixtures/flake8_bugbear/B013.py b/crates/ruff/resources/test/fixtures/flake8_bugbear/B013.py index 27b21bb638603..e9b9c9636bcd1 100644 --- a/crates/ruff/resources/test/fixtures/flake8_bugbear/B013.py +++ b/crates/ruff/resources/test/fixtures/flake8_bugbear/B013.py @@ -1,3 +1,5 @@ +retriable_exceptions = (FileExistsError, FileNotFoundError) + try: pass except (ValueError,): @@ -6,3 +8,5 @@ pass except (ImportError, TypeError): pass +except (*retriable_exceptions,): + pass diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs b/crates/ruff/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs index 68accc1b65836..f2038152f2623 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs @@ -2,6 +2,7 @@ use ruff_python_ast::{self as ast, ExceptHandler, Expr}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::helpers::map_starred; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -41,11 +42,7 @@ pub struct RedundantTupleInExceptionHandler { impl AlwaysAutofixableViolation for RedundantTupleInExceptionHandler { #[derive_message_formats] fn message(&self) -> String { - let RedundantTupleInExceptionHandler { name } = self; - format!( - "A length-one tuple literal is redundant. Write `except {name}` instead of `except \ - ({name},)`." - ) + format!("A length-one tuple literal is redundant in exception handlers") } fn autofix_title(&self) -> String { @@ -70,9 +67,10 @@ pub(crate) fn redundant_tuple_in_exception_handler( let Expr::Tuple(ast::ExprTuple { elts, .. }) = type_.as_ref() else { continue; }; - let [elt] = &elts[..] else { + let [elt] = elts.as_slice() else { continue; }; + let elt = map_starred(elt); let mut diagnostic = Diagnostic::new( RedundantTupleInExceptionHandler { name: checker.generator().expr(elt), diff --git a/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B013_B013.py.snap b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B013_B013.py.snap index 3be573fd87543..a5e818879f411 100644 --- a/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B013_B013.py.snap +++ b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B013_B013.py.snap @@ -1,24 +1,43 @@ --- source: crates/ruff/src/rules/flake8_bugbear/mod.rs --- -B013.py:3:8: B013 [*] A length-one tuple literal is redundant. Write `except ValueError` instead of `except (ValueError,)`. +B013.py:5:8: B013 [*] A length-one tuple literal is redundant in exception handlers | -1 | try: -2 | pass -3 | except (ValueError,): - | ^^^^^^^^^^^^^ B013 +3 | try: 4 | pass -5 | except AttributeError: +5 | except (ValueError,): + | ^^^^^^^^^^^^^ B013 +6 | pass +7 | except AttributeError: | = help: Replace with `except ValueError` ℹ Fix -1 1 | try: -2 2 | pass -3 |-except (ValueError,): - 3 |+except ValueError: +2 2 | +3 3 | try: 4 4 | pass -5 5 | except AttributeError: +5 |-except (ValueError,): + 5 |+except ValueError: 6 6 | pass +7 7 | except AttributeError: +8 8 | pass + +B013.py:11:8: B013 [*] A length-one tuple literal is redundant in exception handlers + | + 9 | except (ImportError, TypeError): +10 | pass +11 | except (*retriable_exceptions,): + | ^^^^^^^^^^^^^^^^^^^^^^^^ B013 +12 | pass + | + = help: Replace with `except retriable_exceptions` + +ℹ Fix +8 8 | pass +9 9 | except (ImportError, TypeError): +10 10 | pass +11 |-except (*retriable_exceptions,): + 11 |+except retriable_exceptions: +12 12 | pass diff --git a/crates/ruff_python_ast/src/helpers.rs b/crates/ruff_python_ast/src/helpers.rs index 8c97f9a172c03..9a9b7982a2ed9 100644 --- a/crates/ruff_python_ast/src/helpers.rs +++ b/crates/ruff_python_ast/src/helpers.rs @@ -653,6 +653,17 @@ pub fn map_subscript(expr: &Expr) -> &Expr { } } +/// Given an [`Expr`] that can be starred, return the underlying starred expression. +pub fn map_starred(expr: &Expr) -> &Expr { + if let Expr::Starred(ast::ExprStarred { value, .. }) = expr { + // Ex) `*args` + value + } else { + // Ex) `args` + expr + } +} + /// Return `true` if the body uses `locals()`, `globals()`, `vars()`, `eval()`. /// /// Accepts a closure that determines whether a given name (e.g., `"list"`) is a Python builtin.