From 3349ceb96999ddb40e35465be78f8b3538b8498f Mon Sep 17 00:00:00 2001 From: Aaron Cunningham Date: Wed, 8 Mar 2023 12:41:29 -0800 Subject: [PATCH] [`flake8-bugbear`] Add `flake8-bugbear`'s B030 rule (#3400) --- .../test/fixtures/flake8_bugbear/B030.py | 47 +++++++++++++++ crates/ruff/src/checkers/ast/mod.rs | 7 +++ crates/ruff/src/codes.rs | 1 + crates/ruff/src/registry.rs | 1 + crates/ruff/src/rules/flake8_bugbear/mod.rs | 1 + .../except_with_non_exception_classes.rs | 60 +++++++++++++++++++ .../src/rules/flake8_bugbear/rules/mod.rs | 4 ++ ...__flake8_bugbear__tests__B030_B030.py.snap | 57 ++++++++++++++++++ ruff.schema.json | 1 + 9 files changed, 179 insertions(+) create mode 100644 crates/ruff/resources/test/fixtures/flake8_bugbear/B030.py create mode 100644 crates/ruff/src/rules/flake8_bugbear/rules/except_with_non_exception_classes.rs create mode 100644 crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B030_B030.py.snap diff --git a/crates/ruff/resources/test/fixtures/flake8_bugbear/B030.py b/crates/ruff/resources/test/fixtures/flake8_bugbear/B030.py new file mode 100644 index 0000000000..410991c6b5 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/flake8_bugbear/B030.py @@ -0,0 +1,47 @@ +""" +Should emit: +B030: + - line 12, column 8 + - line 17, column 9 + - line 22, column 21 + - line 27, column 37 +""" + +try: + pass +except 1: # error + pass + +try: + pass +except (1, ValueError): # error + pass + +try: + pass +except (ValueError, (RuntimeError, (KeyError, TypeError))): # error + pass + +try: + pass +except (ValueError, *(RuntimeError, (KeyError, TypeError))): # error + pass + + +try: + pass +except (ValueError, *(RuntimeError, TypeError)): # ok + pass + +try: + pass +except (ValueError, *[RuntimeError, *(TypeError,)]): # ok + pass + +def what_to_catch(): + return ... + +try: + pass +except what_to_catch(): # ok + pass \ No newline at end of file diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index c3f4b13b9a..fff365b6cc 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -3766,6 +3766,13 @@ where if self.settings.rules.enabled(&Rule::ExceptWithEmptyTuple) { flake8_bugbear::rules::except_with_empty_tuple(self, excepthandler); } + if self + .settings + .rules + .enabled(&Rule::ExceptWithNonExceptionClasses) + { + flake8_bugbear::rules::except_with_non_exception_classes(self, excepthandler); + } if self.settings.rules.enabled(&Rule::ReraiseNoCause) { tryceratops::rules::reraise_no_cause(self, body); } diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index 5c1bf6c811..b8a819cc25 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -206,6 +206,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option { (Flake8Bugbear, "026") => Rule::StarArgUnpackingAfterKeywordArg, (Flake8Bugbear, "027") => Rule::EmptyMethodWithoutAbstractDecorator, (Flake8Bugbear, "029") => Rule::ExceptWithEmptyTuple, + (Flake8Bugbear, "030") => Rule::ExceptWithNonExceptionClasses, (Flake8Bugbear, "032") => Rule::UnintentionalTypeAnnotation, (Flake8Bugbear, "904") => Rule::RaiseWithoutFromInsideExcept, (Flake8Bugbear, "905") => Rule::ZipWithoutExplicitStrict, diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index 7a327eba35..69d59f599b 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -210,6 +210,7 @@ ruff_macros::register_rules!( rules::flake8_bugbear::rules::RaiseWithoutFromInsideExcept, rules::flake8_bugbear::rules::ZipWithoutExplicitStrict, rules::flake8_bugbear::rules::ExceptWithEmptyTuple, + rules::flake8_bugbear::rules::ExceptWithNonExceptionClasses, rules::flake8_bugbear::rules::UnintentionalTypeAnnotation, // flake8-blind-except rules::flake8_blind_except::rules::BlindExcept, diff --git a/crates/ruff/src/rules/flake8_bugbear/mod.rs b/crates/ruff/src/rules/flake8_bugbear/mod.rs index b9c43ef6c1..a20926156c 100644 --- a/crates/ruff/src/rules/flake8_bugbear/mod.rs +++ b/crates/ruff/src/rules/flake8_bugbear/mod.rs @@ -42,6 +42,7 @@ mod tests { #[test_case(Rule::EmptyMethodWithoutAbstractDecorator, Path::new("B027.py"); "B027")] #[test_case(Rule::EmptyMethodWithoutAbstractDecorator, Path::new("B027.pyi"); "B027_pyi")] #[test_case(Rule::ExceptWithEmptyTuple, Path::new("B029.py"); "B029")] + #[test_case(Rule::ExceptWithNonExceptionClasses, Path::new("B030.py"); "B030")] #[test_case(Rule::UnintentionalTypeAnnotation, Path::new("B032.py"); "B032")] #[test_case(Rule::RaiseWithoutFromInsideExcept, Path::new("B904.py"); "B904")] #[test_case(Rule::ZipWithoutExplicitStrict, Path::new("B905.py"); "B905")] diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/except_with_non_exception_classes.rs b/crates/ruff/src/rules/flake8_bugbear/rules/except_with_non_exception_classes.rs new file mode 100644 index 0000000000..abb86ab23b --- /dev/null +++ b/crates/ruff/src/rules/flake8_bugbear/rules/except_with_non_exception_classes.rs @@ -0,0 +1,60 @@ +use std::collections::VecDeque; + +use rustpython_parser::ast::{Excepthandler, ExcepthandlerKind, Expr, ExprKind}; + +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::types::Range; + +use crate::checkers::ast::Checker; +use crate::registry::Diagnostic; +use crate::violation::Violation; + +#[violation] +pub struct ExceptWithNonExceptionClasses; + +impl Violation for ExceptWithNonExceptionClasses { + #[derive_message_formats] + fn message(&self) -> String { + format!("`except` handlers should only be exception classes or tuples of exception classes") + } +} + +/// Given an [`Expr`], flatten any [`ExprKind::Starred`] expressions. +/// This should leave any unstarred iterables alone (subsequently raising a +/// warning for B029). +fn flatten_starred_iterables(expr: &Expr) -> Vec<&Expr> { + let ExprKind::Tuple { elts, .. } = &expr.node else { + return vec![expr]; + }; + let mut flattened_exprs: Vec<&Expr> = Vec::with_capacity(elts.len()); + let mut exprs_to_process: VecDeque<&Expr> = elts.iter().collect(); + while let Some(expr) = exprs_to_process.pop_front() { + match &expr.node { + ExprKind::Starred { value, .. } => match &value.node { + ExprKind::Tuple { elts, .. } | ExprKind::List { elts, .. } => { + exprs_to_process.append(&mut elts.iter().collect()); + } + _ => flattened_exprs.push(expr), + }, + _ => flattened_exprs.push(expr), + } + } + flattened_exprs +} + +/// B030 +pub fn except_with_non_exception_classes(checker: &mut Checker, excepthandler: &Excepthandler) { + let ExcepthandlerKind::ExceptHandler { type_, .. } = &excepthandler.node; + let Some(type_) = type_ else { + return; + }; + for expr in flatten_starred_iterables(type_) { + match expr.node { + ExprKind::Attribute { .. } | ExprKind::Name { .. } | ExprKind::Call { .. } => (), + _ => checker.diagnostics.push(Diagnostic::new( + ExceptWithNonExceptionClasses, + Range::from(expr), + )), + } + } +} diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/mod.rs b/crates/ruff/src/rules/flake8_bugbear/rules/mod.rs index 3466921137..1c1f2ada28 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/mod.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/mod.rs @@ -11,6 +11,9 @@ pub use duplicate_exceptions::{ duplicate_exceptions, DuplicateHandlerException, DuplicateTryBlockException, }; pub use except_with_empty_tuple::{except_with_empty_tuple, ExceptWithEmptyTuple}; +pub use except_with_non_exception_classes::{ + except_with_non_exception_classes, ExceptWithNonExceptionClasses, +}; pub use f_string_docstring::{f_string_docstring, FStringDocstring}; pub use function_call_argument_default::{ function_call_argument_default, FunctionCallArgumentDefault, @@ -52,6 +55,7 @@ mod cached_instance_method; mod cannot_raise_literal; mod duplicate_exceptions; mod except_with_empty_tuple; +mod except_with_non_exception_classes; mod f_string_docstring; mod function_call_argument_default; mod function_uses_loop_variable; diff --git a/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B030_B030.py.snap b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B030_B030.py.snap new file mode 100644 index 0000000000..2349199523 --- /dev/null +++ b/crates/ruff/src/rules/flake8_bugbear/snapshots/ruff__rules__flake8_bugbear__tests__B030_B030.py.snap @@ -0,0 +1,57 @@ +--- +source: crates/ruff/src/rules/flake8_bugbear/mod.rs +expression: diagnostics +--- +- kind: + name: ExceptWithNonExceptionClasses + body: "`except` handlers should only be exception classes or tuples of exception classes" + suggestion: ~ + fixable: false + location: + row: 12 + column: 7 + end_location: + row: 12 + column: 8 + fix: ~ + parent: ~ +- kind: + name: ExceptWithNonExceptionClasses + body: "`except` handlers should only be exception classes or tuples of exception classes" + suggestion: ~ + fixable: false + location: + row: 17 + column: 8 + end_location: + row: 17 + column: 9 + fix: ~ + parent: ~ +- kind: + name: ExceptWithNonExceptionClasses + body: "`except` handlers should only be exception classes or tuples of exception classes" + suggestion: ~ + fixable: false + location: + row: 22 + column: 20 + end_location: + row: 22 + column: 57 + fix: ~ + parent: ~ +- kind: + name: ExceptWithNonExceptionClasses + body: "`except` handlers should only be exception classes or tuples of exception classes" + suggestion: ~ + fixable: false + location: + row: 27 + column: 36 + end_location: + row: 27 + column: 57 + fix: ~ + parent: ~ + diff --git a/ruff.schema.json b/ruff.schema.json index 494650044f..531b9c2f8d 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1471,6 +1471,7 @@ "B027", "B029", "B03", + "B030", "B032", "B9", "B90",