summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/user_guide/configuration/all-options.rst2
-rw-r--r--doc/whatsnew/fragments/7495.bugfix4
-rw-r--r--examples/pylintrc4
-rw-r--r--examples/pyproject.toml2
-rw-r--r--pylint/checkers/exceptions.py56
-rw-r--r--pylintrc2
-rw-r--r--tests/functional/b/bad_exception_cause.txt2
-rw-r--r--tests/functional/b/broad_exception_caught.py26
-rw-r--r--tests/functional/b/broad_exception_caught.rc4
-rw-r--r--tests/functional/b/broad_exception_caught.txt5
-rw-r--r--tests/functional/b/broad_exception_raised.py38
-rw-r--r--tests/functional/b/broad_exception_raised.rc4
-rw-r--r--tests/functional/b/broad_exception_raised.txt13
13 files changed, 134 insertions, 28 deletions
diff --git a/doc/user_guide/configuration/all-options.rst b/doc/user_guide/configuration/all-options.rst
index 0b38c0d2f..da0ce4669 100644
--- a/doc/user_guide/configuration/all-options.rst
+++ b/doc/user_guide/configuration/all-options.rst
@@ -812,7 +812,7 @@ Standard Checkers
.. code-block:: toml
[tool.pylint.exceptions]
- overgeneral-exceptions = ["BaseException", "Exception"]
+ overgeneral-exceptions = ["builtins.BaseException", "builtins.Exception"]
diff --git a/doc/whatsnew/fragments/7495.bugfix b/doc/whatsnew/fragments/7495.bugfix
new file mode 100644
index 000000000..cec2bcf53
--- /dev/null
+++ b/doc/whatsnew/fragments/7495.bugfix
@@ -0,0 +1,4 @@
+Allow specifying non-builtin exceptions in the ``overgeneral-exception`` option
+using an exception's qualified name.
+
+Closes #7495
diff --git a/examples/pylintrc b/examples/pylintrc
index a461b24d5..608a8f269 100644
--- a/examples/pylintrc
+++ b/examples/pylintrc
@@ -307,8 +307,8 @@ min-public-methods=2
[EXCEPTIONS]
# Exceptions that will emit a warning when caught.
-overgeneral-exceptions=BaseException,
- Exception
+overgeneral-exceptions=builtins.BaseException,
+ builtins.Exception
[FORMAT]
diff --git a/examples/pyproject.toml b/examples/pyproject.toml
index b419f83e7..c02538a7c 100644
--- a/examples/pyproject.toml
+++ b/examples/pyproject.toml
@@ -264,7 +264,7 @@ min-public-methods = 2
[tool.pylint.exceptions]
# Exceptions that will emit a warning when caught.
-overgeneral-exceptions = ["BaseException", "Exception"]
+overgeneral-exceptions = ["builtins.BaseException", "builtins.Exception"]
[tool.pylint.format]
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
diff --git a/pylint/checkers/exceptions.py b/pylint/checkers/exceptions.py
index 947d02204..a563c6eb9 100644
--- a/pylint/checkers/exceptions.py
+++ b/pylint/checkers/exceptions.py
@@ -8,6 +8,7 @@ from __future__ import annotations
import builtins
import inspect
+import warnings
from collections.abc import Generator
from typing import TYPE_CHECKING, Any
@@ -59,8 +60,6 @@ def _is_raising(body: list[nodes.NodeNG]) -> bool:
return any(isinstance(node, nodes.Raise) for node in body)
-OVERGENERAL_EXCEPTIONS = ("BaseException", "Exception")
-
MSGS: dict[str, MessageDefinitionTuple] = {
"E0701": (
"Bad except clauses order (%s)",
@@ -201,13 +200,23 @@ class ExceptionRaiseRefVisitor(BaseVisitor):
self._checker.add_message(
"notimplemented-raised", node=self._node, confidence=HIGH
)
- elif node.name in OVERGENERAL_EXCEPTIONS:
- self._checker.add_message(
- "broad-exception-raised",
- args=node.name,
- node=self._node,
- confidence=HIGH,
- )
+ return
+
+ try:
+ exceptions = list(_annotated_unpack_infer(node))
+ except astroid.InferenceError:
+ return
+
+ for _, exception in exceptions:
+ if isinstance(
+ exception, nodes.ClassDef
+ ) and self._checker._is_overgeneral_exception(exception):
+ self._checker.add_message(
+ "broad-exception-raised",
+ args=exception.name,
+ node=self._node,
+ confidence=INFERENCE,
+ )
def visit_call(self, node: nodes.Call) -> None:
if isinstance(node.func, nodes.Name):
@@ -278,7 +287,7 @@ class ExceptionsChecker(checkers.BaseChecker):
(
"overgeneral-exceptions",
{
- "default": OVERGENERAL_EXCEPTIONS,
+ "default": ("builtins.BaseException", "builtins.Exception"),
"type": "csv",
"metavar": "<comma-separated class names>",
"help": "Exceptions that will emit a warning when caught.",
@@ -288,6 +297,18 @@ class ExceptionsChecker(checkers.BaseChecker):
def open(self) -> None:
self._builtin_exceptions = _builtin_exceptions()
+ for exc_name in self.linter.config.overgeneral_exceptions:
+ if "." not in exc_name:
+ warnings.warn_explicit(
+ "Specifying exception names in the overgeneral-exceptions option"
+ " without module name is deprecated and support for it"
+ " will be removed in pylint 3.0."
+ f" Use fully qualified name (maybe 'builtins.{exc_name}' ?) instead.",
+ category=UserWarning,
+ filename="pylint: Command line or configuration file",
+ lineno=1,
+ module="pylint",
+ )
super().open()
@utils.only_required_for_messages(
@@ -592,10 +613,8 @@ class ExceptionsChecker(checkers.BaseChecker):
args=msg,
confidence=INFERENCE,
)
- if (
- exception.name in self.linter.config.overgeneral_exceptions
- and exception.root().name == utils.EXCEPTIONS_MODULE
- and not _is_raising(handler.body)
+ if self._is_overgeneral_exception(exception) and not _is_raising(
+ handler.body
):
self.add_message(
"broad-exception-caught",
@@ -614,6 +633,15 @@ class ExceptionsChecker(checkers.BaseChecker):
exceptions_classes += [exc for _, exc in exceptions]
+ def _is_overgeneral_exception(self, exception: nodes.ClassDef) -> bool:
+ return (
+ exception.qname() in self.linter.config.overgeneral_exceptions
+ # TODO: 3.0: not a qualified name, deprecated
+ or "." not in exception.name
+ and exception.name in self.linter.config.overgeneral_exceptions
+ and exception.root().name == utils.EXCEPTIONS_MODULE
+ )
+
def register(linter: PyLinter) -> None:
linter.register_checker(ExceptionsChecker(linter))
diff --git a/pylintrc b/pylintrc
index 7195a6592..cf0ee89bf 100644
--- a/pylintrc
+++ b/pylintrc
@@ -510,7 +510,7 @@ preferred-modules=
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
-overgeneral-exceptions=Exception
+overgeneral-exceptions=builtins.Exception
[TYPING]
diff --git a/tests/functional/b/bad_exception_cause.txt b/tests/functional/b/bad_exception_cause.txt
index 446a34ede..3aa50fa37 100644
--- a/tests/functional/b/bad_exception_cause.txt
+++ b/tests/functional/b/bad_exception_cause.txt
@@ -3,4 +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
+broad-exception-raised:31:4:31:28::"Raising too general exception: Exception":INFERENCE
diff --git a/tests/functional/b/broad_exception_caught.py b/tests/functional/b/broad_exception_caught.py
index 5a14cc097..0a69a7015 100644
--- a/tests/functional/b/broad_exception_caught.py
+++ b/tests/functional/b/broad_exception_caught.py
@@ -1,6 +1,14 @@
# pylint: disable=missing-docstring
__revision__ = 0
+class CustomBroadException(Exception):
+ pass
+
+
+class CustomNarrowException(CustomBroadException):
+ pass
+
+
try:
__revision__ += 1
except Exception: # [broad-exception-caught]
@@ -11,3 +19,21 @@ try:
__revision__ += 1
except BaseException: # [broad-exception-caught]
print('error')
+
+
+try:
+ __revision__ += 1
+except ValueError:
+ print('error')
+
+
+try:
+ __revision__ += 1
+except CustomBroadException: # [broad-exception-caught]
+ print('error')
+
+
+try:
+ __revision__ += 1
+except CustomNarrowException:
+ print('error')
diff --git a/tests/functional/b/broad_exception_caught.rc b/tests/functional/b/broad_exception_caught.rc
new file mode 100644
index 000000000..e0e1a7b6c
--- /dev/null
+++ b/tests/functional/b/broad_exception_caught.rc
@@ -0,0 +1,4 @@
+[EXCEPTIONS]
+overgeneral-exceptions=builtins.BaseException,
+ builtins.Exception,
+ functional.b.broad_exception_caught.CustomBroadException
diff --git a/tests/functional/b/broad_exception_caught.txt b/tests/functional/b/broad_exception_caught.txt
index 256f5986f..386423b63 100644
--- a/tests/functional/b/broad_exception_caught.txt
+++ b/tests/functional/b/broad_exception_caught.txt
@@ -1,2 +1,3 @@
-broad-exception-caught:6:7:6:16::Catching too general exception Exception:INFERENCE
-broad-exception-caught:12:7:12:20::Catching too general exception BaseException:INFERENCE
+broad-exception-caught:14:7:14:16::Catching too general exception Exception:INFERENCE
+broad-exception-caught:20:7:20:20::Catching too general exception BaseException:INFERENCE
+broad-exception-caught:32:7:32:27::Catching too general exception CustomBroadException:INFERENCE
diff --git a/tests/functional/b/broad_exception_raised.py b/tests/functional/b/broad_exception_raised.py
index 65a509fb9..c6ce64b46 100644
--- a/tests/functional/b/broad_exception_raised.py
+++ b/tests/functional/b/broad_exception_raised.py
@@ -1,4 +1,14 @@
-# pylint: disable=missing-module-docstring, missing-function-docstring, unreachable
+# pylint: disable=missing-docstring, unreachable
+
+ExceptionAlias = Exception
+
+class CustomBroadException(Exception):
+ pass
+
+
+class CustomNarrowException(CustomBroadException):
+ pass
+
def exploding_apple(apple):
print(f"{apple} is about to explode")
@@ -11,6 +21,32 @@ def raise_and_catch():
except Exception as ex: # [broad-exception-caught]
print(ex)
+
+def raise_catch_reraise():
+ try:
+ exploding_apple("apple")
+ except Exception as ex:
+ print(ex)
+ raise ex
+
+
+def raise_catch_raise():
+ try:
+ exploding_apple("apple")
+ except Exception as ex:
+ print(ex)
+ raise Exception() from None # [broad-exception-raised]
+
+
+def raise_catch_raise_using_alias():
+ try:
+ exploding_apple("apple")
+ except Exception as ex:
+ print(ex)
+ raise ExceptionAlias() from None # [broad-exception-raised]
+
raise Exception() # [broad-exception-raised]
raise BaseException() # [broad-exception-raised]
+raise CustomBroadException() # [broad-exception-raised]
raise IndexError from None
+raise CustomNarrowException() from None
diff --git a/tests/functional/b/broad_exception_raised.rc b/tests/functional/b/broad_exception_raised.rc
new file mode 100644
index 000000000..4f85d2933
--- /dev/null
+++ b/tests/functional/b/broad_exception_raised.rc
@@ -0,0 +1,4 @@
+[EXCEPTIONS]
+overgeneral-exceptions=builtins.BaseException,
+ builtins.Exception,
+ functional.b.broad_exception_raised.CustomBroadException
diff --git a/tests/functional/b/broad_exception_raised.txt b/tests/functional/b/broad_exception_raised.txt
index 9482775c3..1e27b23f9 100644
--- a/tests/functional/b/broad_exception_raised.txt
+++ b/tests/functional/b/broad_exception_raised.txt
@@ -1,5 +1,8 @@
-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:INFERENCE
-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
+broad-exception-raised:15:4:15:41:exploding_apple:"Raising too general exception: Exception":INFERENCE
+broad-exception-raised:20:8:20:34:raise_and_catch:"Raising too general exception: Exception":INFERENCE
+broad-exception-caught:21:11:21:20:raise_and_catch:Catching too general exception Exception:INFERENCE
+broad-exception-raised:38:8:38:35:raise_catch_raise:"Raising too general exception: Exception":INFERENCE
+broad-exception-raised:46:8:46:40:raise_catch_raise_using_alias:"Raising too general exception: Exception":INFERENCE
+broad-exception-raised:48:0:48:17::"Raising too general exception: Exception":INFERENCE
+broad-exception-raised:49:0:49:21::"Raising too general exception: BaseException":INFERENCE
+broad-exception-raised:50:0:50:28::"Raising too general exception: CustomBroadException":INFERENCE