diff options
36 files changed, 97 insertions, 42 deletions
diff --git a/doc/data/messages/b/broad-except/bad.py b/doc/data/messages/b/broad-except/bad.py deleted file mode 100644 index f4946093e..000000000 --- a/doc/data/messages/b/broad-except/bad.py +++ /dev/null @@ -1,4 +0,0 @@ -try: - 1 / 0 -except Exception: # [broad-except] - pass diff --git a/doc/data/messages/b/broad-exception-caught/bad.py b/doc/data/messages/b/broad-exception-caught/bad.py new file mode 100644 index 000000000..3423925a6 --- /dev/null +++ b/doc/data/messages/b/broad-exception-caught/bad.py @@ -0,0 +1,4 @@ +try: + 1 / 0 +except Exception: # [broad-exception-caught] + pass diff --git a/doc/data/messages/b/broad-except/good.py b/doc/data/messages/b/broad-exception-caught/good.py index b02b365b0..b02b365b0 100644 --- a/doc/data/messages/b/broad-except/good.py +++ b/doc/data/messages/b/broad-exception-caught/good.py diff --git a/doc/data/messages/b/broad-exception-raised/bad.py b/doc/data/messages/b/broad-exception-raised/bad.py new file mode 100644 index 000000000..4c8ff3b5a --- /dev/null +++ b/doc/data/messages/b/broad-exception-raised/bad.py @@ -0,0 +1,4 @@ +def small_apple(apple, length): + if len(apple) < length: + raise Exception("Apple is too small!") # [broad-exception-raised] + print(f"{apple} is proper size.") diff --git a/doc/data/messages/b/broad-exception-raised/good.py b/doc/data/messages/b/broad-exception-raised/good.py new file mode 100644 index 000000000..a63b1b356 --- /dev/null +++ b/doc/data/messages/b/broad-exception-raised/good.py @@ -0,0 +1,4 @@ +def small_apple(apple, length): + if len(apple) < length: + raise ValueError("Apple is too small!") + print(f"{apple} is proper size.") diff --git a/doc/user_guide/checkers/features.rst b/doc/user_guide/checkers/features.rst index f6616617c..916f8c8f4 100644 --- a/doc/user_guide/checkers/features.rst +++ b/doc/user_guide/checkers/features.rst @@ -440,7 +440,7 @@ Exceptions checker Messages :duplicate-except (W0705): *Catching previously caught exception type %s* Used when an except catches a type that was already caught by a previous handler. -:broad-except (W0703): *Catching too general exception %s* +:broad-exception-caught (W0718): *Catching too general exception %s* Used when an except catches a too general exception, possibly burying unrelated errors. :raise-missing-from (W0707): *Consider explicitly re-raising using %s'%s from %s'* @@ -462,6 +462,8 @@ Exceptions checker Messages operations between exceptions in except handlers. :bare-except (W0702): *No exception type(s) specified* Used when an except clause doesn't specify exceptions type to catch. +:broad-exception-raised (W0719): *Raising too general exception: %s* + Used when an except raises a too general exception. :try-except-raise (W0706): *The except handler raises immediately* Used when an except handler uses raise as its first or only operator. This is useless because it raises back the exception immediately. Remove the raise diff --git a/doc/user_guide/messages/messages_overview.rst b/doc/user_guide/messages/messages_overview.rst index 0b96b275b..a5d2a3b12 100644 --- a/doc/user_guide/messages/messages_overview.rst +++ b/doc/user_guide/messages/messages_overview.rst @@ -217,7 +217,8 @@ All messages in the warning category: warning/bare-except warning/binary-op-exception warning/boolean-datetime - warning/broad-except + warning/broad-exception-caught + warning/broad-exception-raised warning/cell-var-from-loop warning/comparison-with-callable warning/confusing-with-statement @@ -344,6 +345,7 @@ All renamed messages in the warning category: :maxdepth: 1 :titlesonly: + warning/broad-except warning/cache-max-size-none warning/implicit-str-concat-in-sequence warning/lru-cache-decorating-method diff --git a/doc/whatsnew/fragments/7494.new_check b/doc/whatsnew/fragments/7494.new_check new file mode 100644 index 000000000..fe62e1483 --- /dev/null +++ b/doc/whatsnew/fragments/7494.new_check @@ -0,0 +1,4 @@ +Rename ``broad-except`` to ``broad-exception-caught`` and add new checker ``broad-exception-raised`` +which will warn if general exceptions ``BaseException`` or ``Exception`` are raised. + +Closes #7494 diff --git a/pylint/checkers/exceptions.py b/pylint/checkers/exceptions.py index 0d3f0706f..495ba65a8 100644 --- a/pylint/checkers/exceptions.py +++ b/pylint/checkers/exceptions.py @@ -115,11 +115,12 @@ MSGS: dict[str, MessageDefinitionTuple] = { "bare-except", "Used when an except clause doesn't specify exceptions type to catch.", ), - "W0703": ( + "W0718": ( "Catching too general exception %s", - "broad-except", + "broad-exception-caught", "Used when an except catches a too general exception, " "possibly burying unrelated errors.", + {"old_names": [("W0703", "broad-except")]}, ), "W0705": ( "Catching previously caught exception type %s", @@ -165,6 +166,11 @@ MSGS: dict[str, MessageDefinitionTuple] = { "is not valid for the exception in question. Usually emitted when having " "binary operations between exceptions in except handlers.", ), + "W0719": ( + "Raising too general exception: %s", + "broad-exception-raised", + "Used when an except raises a too general exception.", + ), } @@ -193,6 +199,13 @@ class ExceptionRaiseRefVisitor(BaseVisitor): def visit_name(self, node: nodes.Name) -> None: if node.name == "NotImplemented": self._checker.add_message("notimplemented-raised", node=self._node) + elif node.name in OVERGENERAL_EXCEPTIONS: + self._checker.add_message( + "broad-exception-raised", + args=node.name, + node=self._node, + confidence=HIGH, + ) def visit_call(self, node: nodes.Call) -> None: if isinstance(node.func, nodes.Name): @@ -264,6 +277,7 @@ class ExceptionsChecker(checkers.BaseChecker): "bad-exception-cause", "raising-format-tuple", "raise-missing-from", + "broad-exception-raised", ) def visit_raise(self, node: nodes.Raise) -> None: if node.exc is None: @@ -493,7 +507,7 @@ class ExceptionsChecker(checkers.BaseChecker): @utils.only_required_for_messages( "bare-except", - "broad-except", + "broad-exception-caught", "try-except-raise", "binary-op-exception", "bad-except-order", @@ -555,7 +569,9 @@ class ExceptionsChecker(checkers.BaseChecker): and not _is_raising(handler.body) ): self.add_message( - "broad-except", args=exception.name, node=handler.type + "broad-exception-caught", + args=exception.name, + node=handler.type, ) if exception in exceptions_classes: diff --git a/pylint/config/option.py b/pylint/config/option.py index 12412a2d4..00e93fbc3 100644 --- a/pylint/config/option.py +++ b/pylint/config/option.py @@ -136,7 +136,7 @@ VALIDATORS: dict[str, Callable[[Any, str, Any], Any] | Callable[[Any], Any]] = { def _call_validator(opttype: str, optdict: Any, option: str, value: Any) -> Any: if opttype not in VALIDATORS: - raise Exception(f'Unsupported type "{opttype}"') + raise TypeError(f'Unsupported type "{opttype}"') try: return VALIDATORS[opttype](optdict, option, value) # type: ignore[call-arg] except TypeError: diff --git a/pylint/lint/parallel.py b/pylint/lint/parallel.py index 646d26994..8d0053108 100644 --- a/pylint/lint/parallel.py +++ b/pylint/lint/parallel.py @@ -67,7 +67,7 @@ def _worker_check_single_file( defaultdict[str, list[Any]], ]: if not _worker_linter: - raise Exception("Worker linter not yet initialised") + raise RuntimeError("Worker linter not yet initialised") _worker_linter.open() _worker_linter.check_single_file_item(file_item) mapreduce_data = defaultdict(list) diff --git a/pylint/pyreverse/vcg_printer.py b/pylint/pyreverse/vcg_printer.py index 805a22d49..7a1d2d02d 100644 --- a/pylint/pyreverse/vcg_printer.py +++ b/pylint/pyreverse/vcg_printer.py @@ -273,7 +273,7 @@ class VCGPrinter(Printer): try: _type = attributes_dict[key] except KeyError as e: - raise Exception( + raise AttributeError( f"no such attribute {key}\npossible attributes are {attributes_dict.keys()}" ) from e @@ -284,6 +284,6 @@ class VCGPrinter(Printer): elif value in _type: self.emit(f"{key}:{value}\n") else: - raise Exception( + raise ValueError( f"value {value} isn't correct for attribute {key} correct values are {type}" ) diff --git a/tests/functional/b/bad_exception_cause.py b/tests/functional/b/bad_exception_cause.py index fd9a9cca0..8d8db3677 100644 --- a/tests/functional/b/bad_exception_cause.py +++ b/tests/functional/b/bad_exception_cause.py @@ -28,4 +28,4 @@ def function(): try: pass except function as exc: # [catching-non-exception] - raise Exception from exc # [bad-exception-cause] + raise Exception from exc # [bad-exception-cause, broad-exception-raised] diff --git a/tests/functional/b/bad_exception_cause.txt b/tests/functional/b/bad_exception_cause.txt index ef8ce8831..446a34ede 100644 --- a/tests/functional/b/bad_exception_cause.txt +++ b/tests/functional/b/bad_exception_cause.txt @@ -3,3 +3,4 @@ bad-exception-cause:16:4:16:34:test:Exception cause set to something which is no bad-exception-cause:22:4:22:36:test:Exception cause set to something which is not an exception, nor None:INFERENCE catching-non-exception:30:7:30:15::"Catching an exception which doesn't inherit from Exception: function":UNDEFINED bad-exception-cause:31:4:31:28::Exception cause set to something which is not an exception, nor None:INFERENCE +broad-exception-raised:31:4:31:28::"Raising too general exception: Exception":HIGH diff --git a/tests/functional/b/broad_except.txt b/tests/functional/b/broad_except.txt deleted file mode 100644 index 9a795d3c6..000000000 --- a/tests/functional/b/broad_except.txt +++ /dev/null @@ -1,2 +0,0 @@ -broad-except:6:7:6:16::Catching too general exception Exception:UNDEFINED -broad-except:12:7:12:20::Catching too general exception BaseException:UNDEFINED diff --git a/tests/functional/b/broad_except.py b/tests/functional/b/broad_exception_caught.py index b38b6f8dc..5a14cc097 100644 --- a/tests/functional/b/broad_except.py +++ b/tests/functional/b/broad_exception_caught.py @@ -3,11 +3,11 @@ __revision__ = 0 try: __revision__ += 1 -except Exception: # [broad-except] +except Exception: # [broad-exception-caught] print('error') try: __revision__ += 1 -except BaseException: # [broad-except] +except BaseException: # [broad-exception-caught] print('error') diff --git a/tests/functional/b/broad_exception_caught.txt b/tests/functional/b/broad_exception_caught.txt new file mode 100644 index 000000000..387a60ccd --- /dev/null +++ b/tests/functional/b/broad_exception_caught.txt @@ -0,0 +1,2 @@ +broad-exception-caught:6:7:6:16::Catching too general exception Exception:UNDEFINED +broad-exception-caught:12:7:12:20::Catching too general exception BaseException:UNDEFINED diff --git a/tests/functional/b/broad_exception_raised.py b/tests/functional/b/broad_exception_raised.py new file mode 100644 index 000000000..65a509fb9 --- /dev/null +++ b/tests/functional/b/broad_exception_raised.py @@ -0,0 +1,16 @@ +# pylint: disable=missing-module-docstring, missing-function-docstring, unreachable + +def exploding_apple(apple): + print(f"{apple} is about to explode") + raise Exception("{apple} exploded !") # [broad-exception-raised] + + +def raise_and_catch(): + try: + raise Exception("Oh No!!") # [broad-exception-raised] + except Exception as ex: # [broad-exception-caught] + print(ex) + +raise Exception() # [broad-exception-raised] +raise BaseException() # [broad-exception-raised] +raise IndexError from None diff --git a/tests/functional/b/broad_exception_raised.txt b/tests/functional/b/broad_exception_raised.txt new file mode 100644 index 000000000..594dd5200 --- /dev/null +++ b/tests/functional/b/broad_exception_raised.txt @@ -0,0 +1,5 @@ +broad-exception-raised:5:4:5:41:exploding_apple:"Raising too general exception: Exception":HIGH +broad-exception-raised:10:8:10:34:raise_and_catch:"Raising too general exception: Exception":HIGH +broad-exception-caught:11:11:11:20:raise_and_catch:Catching too general exception Exception:UNDEFINED +broad-exception-raised:14:0:14:17::"Raising too general exception: Exception":HIGH +broad-exception-raised:15:0:15:21::"Raising too general exception: BaseException":HIGH diff --git a/tests/functional/c/comparison_with_callable.py b/tests/functional/c/comparison_with_callable.py index 1e8ea5d90..7006d4960 100644 --- a/tests/functional/c/comparison_with_callable.py +++ b/tests/functional/c/comparison_with_callable.py @@ -1,4 +1,4 @@ -# pylint: disable = disallowed-name, missing-docstring, useless-return, invalid-name, line-too-long, comparison-of-constants +# pylint: disable = disallowed-name, missing-docstring, useless-return, invalid-name, line-too-long, comparison-of-constants, broad-exception-raised def foo(): return None diff --git a/tests/functional/ext/docparams/docparams.py b/tests/functional/ext/docparams/docparams.py index 3798ee3ac..051bdd24d 100644 --- a/tests/functional/ext/docparams/docparams.py +++ b/tests/functional/ext/docparams/docparams.py @@ -1,5 +1,5 @@ """Fixture for testing missing documentation in docparams.""" - +# pylint: disable=broad-exception-raised def _private_func1( # [missing-return-doc, missing-return-type-doc, missing-any-param-doc] param1, diff --git a/tests/functional/ext/mccabe/mccabe.py b/tests/functional/ext/mccabe/mccabe.py index e3d11c5c8..92623cd1c 100644 --- a/tests/functional/ext/mccabe/mccabe.py +++ b/tests/functional/ext/mccabe/mccabe.py @@ -1,7 +1,7 @@ # pylint: disable=invalid-name,unnecessary-pass,no-else-return,useless-else-on-loop # pylint: disable=undefined-variable,consider-using-sys-exit,unused-variable,too-many-return-statements # pylint: disable=redefined-outer-name,using-constant-test,unused-argument -# pylint: disable=broad-except, not-context-manager, no-method-argument, unspecified-encoding +# pylint: disable=broad-except, not-context-manager, no-method-argument, unspecified-encoding, broad-exception-raised """Checks use of "too-complex" check""" diff --git a/tests/functional/ext/typing/typing_broken_noreturn.py b/tests/functional/ext/typing/typing_broken_noreturn.py index 4d10ed13a..ef4abf70b 100644 --- a/tests/functional/ext/typing/typing_broken_noreturn.py +++ b/tests/functional/ext/typing/typing_broken_noreturn.py @@ -4,7 +4,7 @@ https://bugs.python.org/issue34921 If no runtime introspection is required, use string annotations instead. """ -# pylint: disable=missing-docstring +# pylint: disable=missing-docstring, broad-exception-raised import typing from typing import TYPE_CHECKING, Callable, NoReturn, Union diff --git a/tests/functional/ext/typing/typing_broken_noreturn_future_import.py b/tests/functional/ext/typing/typing_broken_noreturn_future_import.py index 4743750bc..e0ea7761b 100644 --- a/tests/functional/ext/typing/typing_broken_noreturn_future_import.py +++ b/tests/functional/ext/typing/typing_broken_noreturn_future_import.py @@ -7,7 +7,7 @@ If no runtime introspection is required, use string annotations instead. With 'from __future__ import annotations', only emit errors for nodes not in a type annotation context. """ -# pylint: disable=missing-docstring +# pylint: disable=missing-docstring, broad-exception-raised from __future__ import annotations import typing diff --git a/tests/functional/ext/typing/typing_broken_noreturn_py372.py b/tests/functional/ext/typing/typing_broken_noreturn_py372.py index 4ff1a71b7..6bd31f069 100644 --- a/tests/functional/ext/typing/typing_broken_noreturn_py372.py +++ b/tests/functional/ext/typing/typing_broken_noreturn_py372.py @@ -6,7 +6,7 @@ If no runtime introspection is required, use string annotations instead. Don't emit errors if py-version set to >= 3.7.2. """ -# pylint: disable=missing-docstring +# pylint: disable=missing-docstring, broad-exception-raised import typing from typing import TYPE_CHECKING, Callable, NoReturn, Union diff --git a/tests/functional/n/no/no_else_raise.py b/tests/functional/n/no/no_else_raise.py index 9a54dfc9f..33a1fb561 100644 --- a/tests/functional/n/no/no_else_raise.py +++ b/tests/functional/n/no/no_else_raise.py @@ -1,6 +1,6 @@ """ Test that superfluous else raise are detected. """ -# pylint:disable=invalid-name,missing-docstring,unused-variable,raise-missing-from +# pylint:disable=invalid-name,missing-docstring,unused-variable,raise-missing-from,broad-exception-raised def foo1(x, y, z): if x: # [no-else-raise] diff --git a/tests/functional/r/regression_02/regression_5048.py b/tests/functional/r/regression_02/regression_5048.py index 5656759af..08ff55fb2 100644 --- a/tests/functional/r/regression_02/regression_5048.py +++ b/tests/functional/r/regression_02/regression_5048.py @@ -1,6 +1,6 @@ """Crash regression in astroid on Compare node inference Fixed in https://github.com/PyCQA/astroid/pull/1185""" -# pylint: disable=missing-docstring +# pylint: disable=missing-docstring, broad-exception-raised # Reported at https://github.com/PyCQA/pylint/issues/5048 diff --git a/tests/functional/s/stop_iteration_inside_generator.py b/tests/functional/s/stop_iteration_inside_generator.py index 945a952bd..084c42058 100644 --- a/tests/functional/s/stop_iteration_inside_generator.py +++ b/tests/functional/s/stop_iteration_inside_generator.py @@ -2,9 +2,9 @@ Test that no StopIteration is raised inside a generator """ # pylint: disable=missing-docstring,invalid-name,import-error, try-except-raise, wrong-import-position,not-callable,raise-missing-from +# pylint: disable=broad-exception-raised import asyncio - class RebornStopIteration(StopIteration): """ A class inheriting from StopIteration exception diff --git a/tests/functional/u/undefined/undefined_loop_variable.py b/tests/functional/u/undefined/undefined_loop_variable.py index 9ab08d595..9d5cf4111 100644 --- a/tests/functional/u/undefined/undefined_loop_variable.py +++ b/tests/functional/u/undefined/undefined_loop_variable.py @@ -1,4 +1,4 @@ -# pylint: disable=missing-docstring,redefined-builtin, consider-using-f-string, unnecessary-direct-lambda-call +# pylint: disable=missing-docstring,redefined-builtin, consider-using-f-string, unnecessary-direct-lambda-call, broad-exception-raised import sys diff --git a/tests/functional/u/unreachable.py b/tests/functional/u/unreachable.py index fa40e88f6..0bb35b20d 100644 --- a/tests/functional/u/unreachable.py +++ b/tests/functional/u/unreachable.py @@ -1,4 +1,4 @@ -# pylint: disable=missing-docstring +# pylint: disable=missing-docstring, broad-exception-raised def func1(): diff --git a/tests/functional/u/unused/unused_variable.py b/tests/functional/u/unused/unused_variable.py index 92a329f2f..0058516c9 100644 --- a/tests/functional/u/unused/unused_variable.py +++ b/tests/functional/u/unused/unused_variable.py @@ -1,4 +1,4 @@ -# pylint: disable=missing-docstring, invalid-name, too-few-public-methods, import-outside-toplevel, fixme, line-too-long +# pylint: disable=missing-docstring, invalid-name, too-few-public-methods, import-outside-toplevel, fixme, line-too-long, broad-exception-raised def test_regression_737(): import xml # [unused-import] diff --git a/tests/functional/u/used/used_before_assignment_comprehension_homonyms.py b/tests/functional/u/used/used_before_assignment_comprehension_homonyms.py index feae58dbe..2321afed7 100644 --- a/tests/functional/u/used/used_before_assignment_comprehension_homonyms.py +++ b/tests/functional/u/used/used_before_assignment_comprehension_homonyms.py @@ -1,4 +1,5 @@ """Homonym between filtered comprehension and assignment in except block.""" +# pylint: disable=broad-exception-raised def func(): """https://github.com/PyCQA/pylint/issues/5586""" diff --git a/tests/functional/u/used/used_before_assignment_except_handler_for_try_with_return.py b/tests/functional/u/used/used_before_assignment_except_handler_for_try_with_return.py index 086ad0554..71e142023 100644 --- a/tests/functional/u/used/used_before_assignment_except_handler_for_try_with_return.py +++ b/tests/functional/u/used/used_before_assignment_except_handler_for_try_with_return.py @@ -2,7 +2,7 @@ try blocks with return statements. See: https://github.com/PyCQA/pylint/issues/5500. """ -# pylint: disable=inconsistent-return-statements +# pylint: disable=inconsistent-return-statements,broad-exception-raised def function(): diff --git a/tests/lint/test_utils.py b/tests/lint/test_utils.py index 6cc79f18b..872919f72 100644 --- a/tests/lint/test_utils.py +++ b/tests/lint/test_utils.py @@ -18,7 +18,7 @@ def test_prepare_crash_report(tmp_path: PosixPath) -> None: with open(python_file, "w", encoding="utf8") as f: f.write(python_content) try: - raise Exception(exception_content) + raise Exception(exception_content) # pylint: disable=broad-exception-raised except Exception as ex: # pylint: disable=broad-except template_path = prepare_crash_report( ex, str(python_file), str(tmp_path / "pylint-crash-%Y.txt") diff --git a/tests/test_check_parallel.py b/tests/test_check_parallel.py index d5cdf0715..15b59304c 100644 --- a/tests/test_check_parallel.py +++ b/tests/test_check_parallel.py @@ -190,7 +190,7 @@ class TestCheckParallelFramework: def test_worker_check_single_file_uninitialised(self) -> None: pylint.lint.parallel._worker_linter = None with pytest.raises( # Objects that do not match the linter interface will fail - Exception, match="Worker linter not yet initialised" + RuntimeError, match="Worker linter not yet initialised" ): worker_check_single_file(_gen_file_data()) diff --git a/tests/test_self.py b/tests/test_self.py index ae3b41d98..3b0fe572c 100644 --- a/tests/test_self.py +++ b/tests/test_self.py @@ -729,14 +729,14 @@ a.py:1:4: E0001: Parsing failed: 'invalid syntax (<unknown>, line 1)' (syntax-er (-9, "missing-function-docstring", "fail_under_minus10.py", 22), (-5, "missing-function-docstring", "fail_under_minus10.py", 22), # --fail-under should guide whether error code as missing-function-docstring is not hit - (-10, "broad-except", "fail_under_plus7_5.py", 0), - (6, "broad-except", "fail_under_plus7_5.py", 0), - (7.5, "broad-except", "fail_under_plus7_5.py", 0), - (7.6, "broad-except", "fail_under_plus7_5.py", 16), - (-11, "broad-except", "fail_under_minus10.py", 0), - (-10, "broad-except", "fail_under_minus10.py", 0), - (-9, "broad-except", "fail_under_minus10.py", 22), - (-5, "broad-except", "fail_under_minus10.py", 22), + (-10, "broad-exception-caught", "fail_under_plus7_5.py", 0), + (6, "broad-exception-caught", "fail_under_plus7_5.py", 0), + (7.5, "broad-exception-caught", "fail_under_plus7_5.py", 0), + (7.6, "broad-exception-caught", "fail_under_plus7_5.py", 16), + (-11, "broad-exception-caught", "fail_under_minus10.py", 0), + (-10, "broad-exception-caught", "fail_under_minus10.py", 0), + (-9, "broad-exception-caught", "fail_under_minus10.py", 22), + (-5, "broad-exception-caught", "fail_under_minus10.py", 22), # Enable by message id (-10, "C0116", "fail_under_plus7_5.py", 16), # Enable by category |