diff --git a/README.md b/README.md index 9a4cc6f597..927a111de7 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ OPTIONS: | F634 | IfTuple | If test is a tuple, which is always `True` | | F704 | YieldOutsideFunction | a `yield` or `yield from` statement outside of a function/method | | F706 | ReturnOutsideFunction | a `return` statement outside of a function/method | +| F707 | DefaultExceptNotLast | an `except:` block as not the last exception handler | | F821 | UndefinedName | Undefined name `...` | | F822 | UndefinedExport | Undefined name `...` in `__all__` | | F823 | UndefinedLocal | Local variable `...` referenced before assignment | diff --git a/examples/generate_rules_table.rs b/examples/generate_rules_table.rs index 852a900919..1c55e42d92 100644 --- a/examples/generate_rules_table.rs +++ b/examples/generate_rules_table.rs @@ -4,6 +4,7 @@ use ruff::checks::CheckKind; fn main() { let mut check_kinds: Vec = vec![ CheckKind::AssertTuple, + CheckKind::DefaultExceptNotLast, CheckKind::DuplicateArgumentName, CheckKind::FStringMissingPlaceholders, CheckKind::IfTuple, diff --git a/resources/test/fixtures/F707.py b/resources/test/fixtures/F707.py new file mode 100644 index 0000000000..bf57810110 --- /dev/null +++ b/resources/test/fixtures/F707.py @@ -0,0 +1,46 @@ +try: + pass +except: + pass +except ValueError: + pass + +try: + pass +except: + pass +except ValueError: + pass +finally: + pass + +try: + pass +except: + pass +except ValueError: + pass +else: + pass + +try: + pass +except: + pass + +try: + pass +except ValueError: + pass +except: + pass + +try: + pass +except ValueError: + pass + +try: + pass +finally: + pass diff --git a/resources/test/fixtures/pyproject.toml b/resources/test/fixtures/pyproject.toml index 05d2b29663..ab3b449fbf 100644 --- a/resources/test/fixtures/pyproject.toml +++ b/resources/test/fixtures/pyproject.toml @@ -10,6 +10,7 @@ select = [ "F634", "F704", "F706", + "F707", "F821", "F822", "F823", diff --git a/src/check_ast.rs b/src/check_ast.rs index 5e04f0f3a5..945fa11013 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -295,6 +295,23 @@ impl Visitor for Checker<'_> { } } } + StmtKind::Try { handlers, .. } => { + if self + .settings + .select + .contains(CheckKind::DefaultExceptNotLast.code()) + { + for (idx, handler) in handlers.iter().enumerate() { + let ExcepthandlerKind::ExceptHandler { type_, .. } = &handler.node; + if type_.is_none() && idx < handlers.len() - 1 { + self.checks.push(Check { + kind: CheckKind::DefaultExceptNotLast, + location: handler.location, + }); + } + } + } + } _ => {} } diff --git a/src/checks.rs b/src/checks.rs index caaaa49eb0..70084c22bc 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -16,6 +16,7 @@ pub enum CheckCode { F634, F704, F706, + F707, F821, F822, F823, @@ -38,6 +39,7 @@ impl FromStr for CheckCode { "F634" => Ok(CheckCode::F634), "F704" => Ok(CheckCode::F704), "F706" => Ok(CheckCode::F706), + "F707" => Ok(CheckCode::F707), "F821" => Ok(CheckCode::F821), "F822" => Ok(CheckCode::F822), "F823" => Ok(CheckCode::F823), @@ -61,6 +63,7 @@ impl CheckCode { CheckCode::F634 => "F634", CheckCode::F704 => "F704", CheckCode::F706 => "F706", + CheckCode::F707 => "F707", CheckCode::F821 => "F821", CheckCode::F823 => "F823", CheckCode::F822 => "F822", @@ -82,6 +85,7 @@ impl CheckCode { CheckCode::F634 => &LintSource::AST, CheckCode::F704 => &LintSource::AST, CheckCode::F706 => &LintSource::AST, + CheckCode::F707 => &LintSource::AST, CheckCode::F821 => &LintSource::AST, CheckCode::F822 => &LintSource::AST, CheckCode::F823 => &LintSource::AST, @@ -109,6 +113,7 @@ pub enum CheckKind { LineTooLong, RaiseNotImplemented, ReturnOutsideFunction, + DefaultExceptNotLast, UndefinedLocal(String), UndefinedExport(String), UndefinedName(String), @@ -130,6 +135,7 @@ impl CheckKind { CheckKind::LineTooLong => "LineTooLong", CheckKind::RaiseNotImplemented => "RaiseNotImplemented", CheckKind::ReturnOutsideFunction => "ReturnOutsideFunction", + CheckKind::DefaultExceptNotLast => "DefaultExceptNotLast", CheckKind::UndefinedLocal(_) => "UndefinedLocal", CheckKind::UndefinedExport(_) => "UndefinedExport", CheckKind::UndefinedName(_) => "UndefinedName", @@ -151,6 +157,7 @@ impl CheckKind { CheckKind::LineTooLong => &CheckCode::E501, CheckKind::RaiseNotImplemented => &CheckCode::F901, CheckKind::ReturnOutsideFunction => &CheckCode::F706, + CheckKind::DefaultExceptNotLast => &CheckCode::F707, CheckKind::UndefinedExport(_) => &CheckCode::F822, CheckKind::UndefinedLocal(_) => &CheckCode::F823, CheckKind::UndefinedName(_) => &CheckCode::F821, @@ -182,6 +189,9 @@ impl CheckKind { CheckKind::ReturnOutsideFunction => { "a `return` statement outside of a function/method".to_string() } + CheckKind::DefaultExceptNotLast => { + "an `except:` block as not the last exception handler".to_string() + } CheckKind::UndefinedExport(name) => { format!("Undefined name `{name}` in `__all__`") } diff --git a/src/linter.rs b/src/linter.rs index a4a78c7823..f834ec5f96 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -320,6 +320,47 @@ mod tests { Ok(()) } + #[test] + fn f707() -> Result<()> { + let actual = check_path( + Path::new("./resources/test/fixtures/F707.py"), + &settings::Settings { + line_length: 88, + exclude: vec![], + select: BTreeSet::from([CheckCode::F707]), + }, + &cache::Mode::None, + )?; + let expected = vec![ + Message { + kind: CheckKind::DefaultExceptNotLast, + location: Location::new(3, 1), + filename: "./resources/test/fixtures/F707.py".to_string(), + }, + Message { + kind: CheckKind::DefaultExceptNotLast, + location: Location::new(10, 1), + filename: "./resources/test/fixtures/F707.py".to_string(), + }, + Message { + kind: CheckKind::DefaultExceptNotLast, + location: Location::new(19, 1), + filename: "./resources/test/fixtures/F707.py".to_string(), + }, + Message { + kind: CheckKind::DefaultExceptNotLast, + location: Location::new(28, 1), + filename: "./resources/test/fixtures/F707.py".to_string(), + }, + ]; + assert_eq!(actual.len(), expected.len()); + for i in 0..actual.len() { + assert_eq!(actual[i], expected[i]); + } + + Ok(()) + } + #[test] fn f821() -> Result<()> { let actual = check_path( diff --git a/src/pyproject.rs b/src/pyproject.rs index 87a6537400..0d5c31a8fe 100644 --- a/src/pyproject.rs +++ b/src/pyproject.rs @@ -245,6 +245,7 @@ other-attribute = 1 CheckCode::F634, CheckCode::F704, CheckCode::F706, + CheckCode::F707, CheckCode::F821, CheckCode::F822, CheckCode::F823, diff --git a/src/settings.rs b/src/settings.rs index 7fbd67a872..90e4700a5c 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -51,6 +51,7 @@ impl Settings { CheckCode::F631, CheckCode::F634, CheckCode::F706, + CheckCode::F707, CheckCode::F831, CheckCode::F823, CheckCode::F901,