diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/useless_exception_statement.py b/crates/ruff_linter/resources/test/fixtures/pylint/useless_exception_statement.py index c3ca0b90d25136..4d9aad129ef467 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/useless_exception_statement.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/useless_exception_statement.py @@ -1,8 +1,8 @@ -# Test case 1: Useless exception statement from abc import ABC, abstractmethod from contextlib import suppress +# Test case 1: Useless exception statement def func(): AssertionError("This is an assertion error") # PLW0133 @@ -66,6 +66,11 @@ def func(): x = 1; (RuntimeError("This is an exception")); y = 2 # PLW0133 +# Test case 11: Useless warning statement +def func(): + UserWarning("This is an assertion error") # PLW0133 + + # Non-violation test cases: PLW0133 diff --git a/crates/ruff_linter/src/rules/pylint/rules/useless_exception_statement.rs b/crates/ruff_linter/src/rules/pylint/rules/useless_exception_statement.rs index 1df8a4ab253588..5c6d32e4ae791a 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/useless_exception_statement.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/useless_exception_statement.rs @@ -2,6 +2,7 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::SemanticModel; +use ruff_python_stdlib::builtins; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -60,38 +61,8 @@ pub(crate) fn useless_exception_statement(checker: &mut Checker, expr: &ast::Stm } /// Returns `true` if the given expression is a builtin exception. -/// -/// See: fn is_builtin_exception(expr: &Expr, semantic: &SemanticModel) -> bool { - return semantic + semantic .resolve_qualified_name(expr) - .is_some_and(|qualified_name| { - matches!( - qualified_name.segments(), - [ - "", - "SystemExit" - | "Exception" - | "ArithmeticError" - | "AssertionError" - | "AttributeError" - | "BufferError" - | "EOFError" - | "ImportError" - | "LookupError" - | "IndexError" - | "KeyError" - | "MemoryError" - | "NameError" - | "ReferenceError" - | "RuntimeError" - | "NotImplementedError" - | "StopIteration" - | "SyntaxError" - | "SystemError" - | "TypeError" - | "ValueError" - ] - ) - }); + .is_some_and(|qualified_name| matches!(qualified_name.segments(), ["", name] if builtins::is_exception(name))) } diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0133_useless_exception_statement.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0133_useless_exception_statement.py.snap index 91c485cfb4e8c0..4b5ed1085d6e0c 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0133_useless_exception_statement.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0133_useless_exception_statement.py.snap @@ -3,6 +3,7 @@ source: crates/ruff_linter/src/rules/pylint/mod.rs --- useless_exception_statement.py:7:5: PLW0133 [*] Missing `raise` statement on exception | +5 | # Test case 1: Useless exception statement 6 | def func(): 7 | AssertionError("This is an assertion error") # PLW0133 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLW0133 @@ -11,7 +12,7 @@ useless_exception_statement.py:7:5: PLW0133 [*] Missing `raise` statement on exc ℹ Unsafe fix 4 4 | -5 5 | +5 5 | # Test case 1: Useless exception statement 6 6 | def func(): 7 |- AssertionError("This is an assertion error") # PLW0133 7 |+ raise AssertionError("This is an assertion error") # PLW0133 @@ -192,4 +193,23 @@ useless_exception_statement.py:66:12: PLW0133 [*] Missing `raise` statement on e 66 |+ x = 1; raise (RuntimeError("This is an exception")); y = 2 # PLW0133 67 67 | 68 68 | -69 69 | # Non-violation test cases: PLW0133 +69 69 | # Test case 11: Useless warning statement + +useless_exception_statement.py:71:5: PLW0133 [*] Missing `raise` statement on exception + | +69 | # Test case 11: Useless warning statement +70 | def func(): +71 | UserWarning("This is an assertion error") # PLW0133 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLW0133 + | + = help: Add `raise` keyword + +ℹ Unsafe fix +68 68 | +69 69 | # Test case 11: Useless warning statement +70 70 | def func(): +71 |- UserWarning("This is an assertion error") # PLW0133 + 71 |+ raise UserWarning("This is an assertion error") # PLW0133 +72 72 | +73 73 | +74 74 | # Non-violation test cases: PLW0133 diff --git a/crates/ruff_linter/src/rules/tryceratops/rules/type_check_without_type_error.rs b/crates/ruff_linter/src/rules/tryceratops/rules/type_check_without_type_error.rs index 9c326349138a4e..a982a24a3ee9a1 100644 --- a/crates/ruff_linter/src/rules/tryceratops/rules/type_check_without_type_error.rs +++ b/crates/ruff_linter/src/rules/tryceratops/rules/type_check_without_type_error.rs @@ -1,7 +1,9 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::helpers::map_callable; use ruff_python_ast::statement_visitor::{walk_stmt, StatementVisitor}; use ruff_python_ast::{self as ast, Expr, Stmt, StmtIf}; +use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -96,11 +98,32 @@ fn check_type_check_test(checker: &mut Checker, test: &Expr) -> bool { } } -/// Returns `true` if `exc` is a reference to a builtin exception. -fn is_builtin_exception(checker: &mut Checker, exc: &Expr) -> bool { - return checker - .semantic() - .resolve_qualified_name(exc) +fn check_raise(checker: &mut Checker, exc: &Expr, item: &Stmt) { + if is_builtin_exception(exc, checker.semantic()) { + checker + .diagnostics + .push(Diagnostic::new(TypeCheckWithoutTypeError, item.range())); + } +} + +/// Search the body of an if-condition for raises. +fn check_body(checker: &mut Checker, body: &[Stmt]) { + for item in body { + if has_control_flow(item) { + return; + } + if let Stmt::Raise(ast::StmtRaise { exc: Some(exc), .. }) = &item { + check_raise(checker, exc, item); + } + } +} + +/// Returns `true` if the given expression is a builtin exception. +/// +/// This function only matches to a subset of the builtin exceptions, and omits `TypeError`. +fn is_builtin_exception(expr: &Expr, semantic: &SemanticModel) -> bool { + semantic + .resolve_qualified_name(map_callable(expr)) .is_some_and(|qualified_name| { matches!( qualified_name.segments(), @@ -123,42 +146,7 @@ fn is_builtin_exception(checker: &mut Checker, exc: &Expr) -> bool { | "ValueError" ] ) - }); -} - -/// Returns `true` if an [`Expr`] is a reference to a builtin exception. -fn check_raise_type(checker: &mut Checker, exc: &Expr) -> bool { - match exc { - Expr::Name(_) => is_builtin_exception(checker, exc), - Expr::Call(ast::ExprCall { func, .. }) => { - if let Expr::Name(_) = func.as_ref() { - is_builtin_exception(checker, func) - } else { - false - } - } - _ => false, - } -} - -fn check_raise(checker: &mut Checker, exc: &Expr, item: &Stmt) { - if check_raise_type(checker, exc) { - checker - .diagnostics - .push(Diagnostic::new(TypeCheckWithoutTypeError, item.range())); - } -} - -/// Search the body of an if-condition for raises. -fn check_body(checker: &mut Checker, body: &[Stmt]) { - for item in body { - if has_control_flow(item) { - return; - } - if let Stmt::Raise(ast::StmtRaise { exc: Some(exc), .. }) = &item { - check_raise(checker, exc, item); - } - } + }) } /// TRY004 diff --git a/crates/ruff_python_stdlib/src/builtins.rs b/crates/ruff_python_stdlib/src/builtins.rs index 2dcd873436f357..19ce5cfe116c3c 100644 --- a/crates/ruff_python_stdlib/src/builtins.rs +++ b/crates/ruff_python_stdlib/src/builtins.rs @@ -368,3 +368,79 @@ pub fn is_ipython_builtin(name: &str) -> bool { // Constructed by converting the `IPYTHON_BUILTINS` slice to a `match` expression. matches!(name, "__IPYTHON__" | "display" | "get_ipython") } + +/// Returns `true` if the given name is that of a builtin exception. +/// +/// See: +pub fn is_exception(name: &str) -> bool { + matches!( + name, + "BaseException" + | "BaseExceptionGroup" + | "GeneratorExit" + | "KeyboardInterrupt" + | "SystemExit" + | "Exception" + | "ArithmeticError" + | "FloatingPointError" + | "OverflowError" + | "ZeroDivisionError" + | "AssertionError" + | "AttributeError" + | "BufferError" + | "EOFError" + | "ExceptionGroup" + | "ImportError" + | "ModuleNotFoundError" + | "LookupError" + | "IndexError" + | "KeyError" + | "MemoryError" + | "NameError" + | "UnboundLocalError" + | "OSError" + | "BlockingIOError" + | "ChildProcessError" + | "ConnectionError" + | "BrokenPipeError" + | "ConnectionAbortedError" + | "ConnectionRefusedError" + | "ConnectionResetError" + | "FileExistsError" + | "FileNotFoundError" + | "InterruptedError" + | "IsADirectoryError" + | "NotADirectoryError" + | "PermissionError" + | "ProcessLookupError" + | "TimeoutError" + | "ReferenceError" + | "RuntimeError" + | "NotImplementedError" + | "RecursionError" + | "StopAsyncIteration" + | "StopIteration" + | "SyntaxError" + | "IndentationError" + | "TabError" + | "SystemError" + | "TypeError" + | "ValueError" + | "UnicodeError" + | "UnicodeDecodeError" + | "UnicodeEncodeError" + | "UnicodeTranslateError" + | "Warning" + | "BytesWarning" + | "DeprecationWarning" + | "EncodingWarning" + | "FutureWarning" + | "ImportWarning" + | "PendingDeprecationWarning" + | "ResourceWarning" + | "RuntimeWarning" + | "SyntaxWarning" + | "UnicodeWarning" + | "UserWarning" + ) +}