diff options
Diffstat (limited to 'pylint/checkers/exceptions.py')
-rw-r--r-- | pylint/checkers/exceptions.py | 397 |
1 files changed, 228 insertions, 169 deletions
diff --git a/pylint/checkers/exceptions.py b/pylint/checkers/exceptions.py index 2a8e95e01..4eb65d923 100644 --- a/pylint/checkers/exceptions.py +++ b/pylint/checkers/exceptions.py @@ -70,74 +70,97 @@ def _is_raising(body: typing.List) -> bool: PY3K = sys.version_info >= (3, 0) -OVERGENERAL_EXCEPTIONS = ('Exception',) +OVERGENERAL_EXCEPTIONS = ("Exception",) BUILTINS_NAME = builtins.__name__ MSGS = { - 'E0701': ('Bad except clauses order (%s)', - 'bad-except-order', - 'Used when except clauses are not in the correct order (from the ' - 'more specific to the more generic). If you don\'t fix the order, ' - 'some exceptions may not be caught by the most specific handler.'), - 'E0702': ('Raising %s while only classes or instances are allowed', - 'raising-bad-type', - 'Used when something which is neither a class, an instance or a ' - 'string is raised (i.e. a `TypeError` will be raised).'), - 'E0703': ('Exception context set to something which is not an ' - 'exception, nor None', - 'bad-exception-context', - 'Used when using the syntax "raise ... from ...", ' - 'where the exception context is not an exception, ' - 'nor None.'), - 'E0704': ('The raise statement is not inside an except clause', - 'misplaced-bare-raise', - 'Used when a bare raise is not used inside an except clause. ' - 'This generates an error, since there are no active exceptions ' - 'to be reraised. An exception to this rule is represented by ' - 'a bare raise inside a finally clause, which might work, as long ' - 'as an exception is raised inside the try block, but it is ' - 'nevertheless a code smell that must not be relied upon.'), - 'E0710': ('Raising a new style class which doesn\'t inherit from BaseException', - 'raising-non-exception', - 'Used when a new style class which doesn\'t inherit from ' - 'BaseException is raised.'), - 'E0711': ('NotImplemented raised - should raise NotImplementedError', - 'notimplemented-raised', - 'Used when NotImplemented is raised instead of ' - 'NotImplementedError'), - 'E0712': ('Catching an exception which doesn\'t inherit from Exception: %s', - 'catching-non-exception', - 'Used when a class which doesn\'t inherit from ' - 'Exception is used as an exception in an except clause.'), - 'W0702': ('No exception type(s) specified', - 'bare-except', - 'Used when an except clause doesn\'t specify exceptions type to ' - 'catch.'), - 'W0703': ('Catching too general exception %s', - 'broad-except', - 'Used when an except catches a too general exception, ' - 'possibly burying unrelated errors.'), - 'W0705': ('Catching previously caught exception type %s', - 'duplicate-except', - 'Used when an except catches a type that was already caught by ' - 'a previous handler.'), - 'W0706': ('The except handler raises immediately', - 'try-except-raise', - '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 operator or the entire ' - 'try-except-raise block!'), - 'W0711': ('Exception to catch is the result of a binary "%s" operation', - 'binary-op-exception', - 'Used when the exception to catch is of the form ' - '"except A or B:". If intending to catch multiple, ' - 'rewrite as "except (A, B):"'), - 'W0715': ('Exception arguments suggest string formatting might be intended', - 'raising-format-tuple', - 'Used when passing multiple arguments to an exception ' - 'constructor, the first of them a string literal containing what ' - 'appears to be placeholders intended for formatting'), - } + "E0701": ( + "Bad except clauses order (%s)", + "bad-except-order", + "Used when except clauses are not in the correct order (from the " + "more specific to the more generic). If you don't fix the order, " + "some exceptions may not be caught by the most specific handler.", + ), + "E0702": ( + "Raising %s while only classes or instances are allowed", + "raising-bad-type", + "Used when something which is neither a class, an instance or a " + "string is raised (i.e. a `TypeError` will be raised).", + ), + "E0703": ( + "Exception context set to something which is not an " "exception, nor None", + "bad-exception-context", + 'Used when using the syntax "raise ... from ...", ' + "where the exception context is not an exception, " + "nor None.", + ), + "E0704": ( + "The raise statement is not inside an except clause", + "misplaced-bare-raise", + "Used when a bare raise is not used inside an except clause. " + "This generates an error, since there are no active exceptions " + "to be reraised. An exception to this rule is represented by " + "a bare raise inside a finally clause, which might work, as long " + "as an exception is raised inside the try block, but it is " + "nevertheless a code smell that must not be relied upon.", + ), + "E0710": ( + "Raising a new style class which doesn't inherit from BaseException", + "raising-non-exception", + "Used when a new style class which doesn't inherit from " + "BaseException is raised.", + ), + "E0711": ( + "NotImplemented raised - should raise NotImplementedError", + "notimplemented-raised", + "Used when NotImplemented is raised instead of " "NotImplementedError", + ), + "E0712": ( + "Catching an exception which doesn't inherit from Exception: %s", + "catching-non-exception", + "Used when a class which doesn't inherit from " + "Exception is used as an exception in an except clause.", + ), + "W0702": ( + "No exception type(s) specified", + "bare-except", + "Used when an except clause doesn't specify exceptions type to " "catch.", + ), + "W0703": ( + "Catching too general exception %s", + "broad-except", + "Used when an except catches a too general exception, " + "possibly burying unrelated errors.", + ), + "W0705": ( + "Catching previously caught exception type %s", + "duplicate-except", + "Used when an except catches a type that was already caught by " + "a previous handler.", + ), + "W0706": ( + "The except handler raises immediately", + "try-except-raise", + "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 operator or the entire " + "try-except-raise block!", + ), + "W0711": ( + 'Exception to catch is the result of a binary "%s" operation', + "binary-op-exception", + "Used when the exception to catch is of the form " + '"except A or B:". If intending to catch multiple, ' + 'rewrite as "except (A, B):"', + ), + "W0715": ( + "Exception arguments suggest string formatting might be intended", + "raising-format-tuple", + "Used when passing multiple arguments to an exception " + "constructor, the first of them a string literal containing what " + "appears to be placeholders intended for formatting", + ), +} class BaseVisitor: @@ -149,13 +172,13 @@ class BaseVisitor: def visit(self, node): name = node.__class__.__name__.lower() - dispatch_meth = getattr(self, 'visit_' + name, None) + dispatch_meth = getattr(self, "visit_" + name, None) if dispatch_meth: dispatch_meth(node) else: self.visit_default(node) - def visit_default(self, node): # pylint: disable=unused-argument + def visit_default(self, node): # pylint: disable=unused-argument """Default implementation for all the nodes.""" @@ -163,23 +186,20 @@ class ExceptionRaiseRefVisitor(BaseVisitor): """Visit references (anything that is not an AST leaf).""" def visit_name(self, name): - if name.name == 'NotImplemented': - self._checker.add_message( - 'notimplemented-raised', - node=self._node) + if name.name == "NotImplemented": + self._checker.add_message("notimplemented-raised", node=self._node) def visit_call(self, call): if isinstance(call.func, astroid.Name): self.visit_name(call.func) - if (len(call.args) > 1 and - isinstance(call.args[0], astroid.Const) and - isinstance(call.args[0].value, str)): + if ( + len(call.args) > 1 + and isinstance(call.args[0], astroid.Const) + and isinstance(call.args[0].value, str) + ): msg = call.args[0].value - if ('%' in msg or - ('{' in msg and '}' in msg)): - self._checker.add_message( - 'raising-format-tuple', - node=self._node) + if "%" in msg or ("{" in msg and "}" in msg): + self._checker.add_message("raising-format-tuple", node=self._node) class ExceptionRaiseLeafVisitor(BaseVisitor): @@ -188,8 +208,9 @@ class ExceptionRaiseLeafVisitor(BaseVisitor): def visit_const(self, const): if not isinstance(const.value, str): # raising-string will be emitted from python3 porting checker. - self._checker.add_message('raising-bad-type', node=self._node, - args=const.value.__class__.__name__) + self._checker.add_message( + "raising-bad-type", node=self._node, args=const.value.__class__.__name__ + ) def visit_instance(self, instance): # pylint: disable=protected-access @@ -200,18 +221,15 @@ class ExceptionRaiseLeafVisitor(BaseVisitor): visit_exceptioninstance = visit_instance def visit_classdef(self, cls): - if (not utils.inherit_from_std_ex(cls) and - utils.has_known_bases(cls)): + if not utils.inherit_from_std_ex(cls) and utils.has_known_bases(cls): if cls.newstyle: - self._checker.add_message('raising-non-exception', node=self._node) + self._checker.add_message("raising-non-exception", node=self._node) else: - self._checker.add_message('nonstandard-exception', node=self._node) + self._checker.add_message("nonstandard-exception", node=self._node) def visit_tuple(self, tuple_node): if PY3K or not tuple_node.elts: - self._checker.add_message('raising-bad-type', - node=self._node, - args='tuple') + self._checker.add_message("raising-bad-type", node=self._node, args="tuple") return # On Python 2, using the following is not an error: @@ -225,18 +243,18 @@ class ExceptionRaiseLeafVisitor(BaseVisitor): if not inferred or inferred is astroid.Uninferable: return - if (isinstance(inferred, astroid.Instance) - and inferred.__class__.__name__ != 'Instance'): + if ( + isinstance(inferred, astroid.Instance) + and inferred.__class__.__name__ != "Instance" + ): # TODO: explain why self.visit_default(tuple_node) else: self.visit(inferred) def visit_default(self, node): - name = getattr(node, 'name', node.__class__.__name__) - self._checker.add_message('raising-bad-type', - node=self._node, - args=name) + name = getattr(node, "name", node.__class__.__name__) + self._checker.add_message("raising-bad-type", node=self._node, args=name) class ExceptionsChecker(checkers.BaseChecker): @@ -244,26 +262,36 @@ class ExceptionsChecker(checkers.BaseChecker): __implements__ = interfaces.IAstroidChecker - name = 'exceptions' + name = "exceptions" msgs = MSGS priority = -4 - options = (('overgeneral-exceptions', - {'default' : OVERGENERAL_EXCEPTIONS, - 'type' : 'csv', 'metavar' : '<comma-separated class names>', - 'help' : 'Exceptions that will emit a warning ' - 'when being caught. Defaults to "%s".' % ( - ', '.join(OVERGENERAL_EXCEPTIONS),)} - ), - ) + options = ( + ( + "overgeneral-exceptions", + { + "default": OVERGENERAL_EXCEPTIONS, + "type": "csv", + "metavar": "<comma-separated class names>", + "help": "Exceptions that will emit a warning " + 'when being caught. Defaults to "%s".' + % (", ".join(OVERGENERAL_EXCEPTIONS),), + }, + ), + ) def open(self): self._builtin_exceptions = _builtin_exceptions() super(ExceptionsChecker, self).open() - @utils.check_messages('nonstandard-exception', 'misplaced-bare-raise', - 'raising-bad-type', 'raising-non-exception', - 'notimplemented-raised', 'bad-exception-context', - 'raising-format-tuple') + @utils.check_messages( + "nonstandard-exception", + "misplaced-bare-raise", + "raising-bad-type", + "raising-non-exception", + "notimplemented-raised", + "bad-exception-context", + "raising-format-tuple", + ) def visit_raise(self, node): if node.exc is None: self._check_misplaced_bare_raise(node) @@ -286,21 +314,23 @@ class ExceptionsChecker(checkers.BaseChecker): def _check_misplaced_bare_raise(self, node): # Filter out if it's present in __exit__. scope = node.scope() - if (isinstance(scope, astroid.FunctionDef) - and scope.is_method() - and scope.name == '__exit__'): + if ( + isinstance(scope, astroid.FunctionDef) + and scope.is_method() + and scope.name == "__exit__" + ): return current = node # Stop when a new scope is generated or when the raise # statement is found inside a TryFinally. - ignores = (astroid.ExceptHandler, astroid.FunctionDef,) + ignores = (astroid.ExceptHandler, astroid.FunctionDef) while current and not isinstance(current.parent, ignores): current = current.parent expected = (astroid.ExceptHandler,) if not current or not isinstance(current.parent, expected): - self.add_message('misplaced-bare-raise', node=node) + self.add_message("misplaced-bare-raise", node=node) def _check_bad_exception_context(self, node): """Verify that the exception context is properly set. @@ -313,12 +343,11 @@ class ExceptionsChecker(checkers.BaseChecker): if isinstance(cause, astroid.Const): if cause.value is not None: - self.add_message('bad-exception-context', - node=node) - elif (not isinstance(cause, astroid.ClassDef) and - not utils.inherit_from_std_ex(cause)): - self.add_message('bad-exception-context', - node=node) + self.add_message("bad-exception-context", node=node) + elif not isinstance(cause, astroid.ClassDef) and not utils.inherit_from_std_ex( + cause + ): + self.add_message("bad-exception-context", node=node) def _check_catching_non_exception(self, handler, exc, part): if isinstance(exc, astroid.Tuple): @@ -327,50 +356,59 @@ class ExceptionsChecker(checkers.BaseChecker): if any(node is astroid.Uninferable for node in inferred): # Don't emit if we don't know every component. return - if all(node and (utils.inherit_from_std_ex(node) or - not utils.has_known_bases(node)) - for node in inferred): + if all( + node + and (utils.inherit_from_std_ex(node) or not utils.has_known_bases(node)) + for node in inferred + ): return if not isinstance(exc, astroid.ClassDef): # Don't emit the warning if the infered stmt # is None, but the exception handler is something else, # maybe it was redefined. - if (isinstance(exc, astroid.Const) and - exc.value is None): - if ((isinstance(handler.type, astroid.Const) and - handler.type.value is None) or - handler.type.parent_of(exc)): + if isinstance(exc, astroid.Const) and exc.value is None: + if ( + isinstance(handler.type, astroid.Const) + and handler.type.value is None + ) or handler.type.parent_of(exc): # If the exception handler catches None or # the exception component, which is None, is # defined by the entire exception handler, then # emit a warning. - self.add_message('catching-non-exception', - node=handler.type, - args=(part.as_string(), )) + self.add_message( + "catching-non-exception", + node=handler.type, + args=(part.as_string(),), + ) else: - self.add_message('catching-non-exception', - node=handler.type, - args=(part.as_string(), )) + self.add_message( + "catching-non-exception", + node=handler.type, + args=(part.as_string(),), + ) return - if (not utils.inherit_from_std_ex(exc) and - exc.name not in self._builtin_exceptions): + if ( + not utils.inherit_from_std_ex(exc) + and exc.name not in self._builtin_exceptions + ): if utils.has_known_bases(exc): - self.add_message('catching-non-exception', - node=handler.type, - args=(exc.name, )) + self.add_message( + "catching-non-exception", node=handler.type, args=(exc.name,) + ) def _check_try_except_raise(self, node): - def gather_exceptions_from_handler(handler): exceptions = [] if handler.type: exceptions_in_handler = utils.safe_infer(handler.type) if isinstance(exceptions_in_handler, astroid.Tuple): - exceptions = {exception - for exception in exceptions_in_handler.elts - if isinstance(exception, astroid.Name)} + exceptions = { + exception + for exception in exceptions_in_handler.elts + if isinstance(exception, astroid.Name) + } elif exceptions_in_handler: exceptions = [exceptions_in_handler] return exceptions @@ -391,9 +429,12 @@ class ExceptionsChecker(checkers.BaseChecker): for exc_in_current_handler in excs_in_current_handler: inferred_current = utils.safe_infer(exc_in_current_handler) - if any(utils.is_subclass_of(utils.safe_infer(exc_in_bare_handler), - inferred_current) - for exc_in_bare_handler in excs_in_bare_handler): + if any( + utils.is_subclass_of( + utils.safe_infer(exc_in_bare_handler), inferred_current + ) + for exc_in_bare_handler in excs_in_bare_handler + ): bare_raise = False break @@ -405,11 +446,17 @@ class ExceptionsChecker(checkers.BaseChecker): handler_having_bare_raise = handler excs_in_bare_handler = gather_exceptions_from_handler(handler) if bare_raise: - self.add_message('try-except-raise', node=handler_having_bare_raise) - - @utils.check_messages('bare-except', 'broad-except', 'try-except-raise', - 'binary-op-exception', 'bad-except-order', - 'catching-non-exception', 'duplicate-except') + self.add_message("try-except-raise", node=handler_having_bare_raise) + + @utils.check_messages( + "bare-except", + "broad-except", + "try-except-raise", + "binary-op-exception", + "bad-except-order", + "catching-non-exception", + "duplicate-except", + ) def visit_tryexcept(self, node): """check for empty except""" self._check_try_except_raise(node) @@ -418,17 +465,18 @@ class ExceptionsChecker(checkers.BaseChecker): for index, handler in enumerate(node.handlers): if handler.type is None: if not _is_raising(handler.body): - self.add_message('bare-except', node=handler) + self.add_message("bare-except", node=handler) # check if an "except:" is followed by some other # except if index < (nb_handlers - 1): - msg = 'empty except clause should always appear last' - self.add_message('bad-except-order', node=node, args=msg) + msg = "empty except clause should always appear last" + self.add_message("bad-except-order", node=node, args=msg) elif isinstance(handler.type, astroid.BoolOp): - self.add_message('binary-op-exception', - node=handler, args=handler.type.op) + self.add_message( + "binary-op-exception", node=handler, args=handler.type.op + ) else: try: excs = list(_annotated_unpack_infer(handler.type)) @@ -438,8 +486,9 @@ class ExceptionsChecker(checkers.BaseChecker): for part, exc in excs: if exc is astroid.Uninferable: continue - if (isinstance(exc, astroid.Instance) - and utils.inherit_from_std_ex(exc)): + if isinstance(exc, astroid.Instance) and utils.inherit_from_std_ex( + exc + ): # pylint: disable=protected-access exc = exc._proxied @@ -448,24 +497,34 @@ class ExceptionsChecker(checkers.BaseChecker): if not isinstance(exc, astroid.ClassDef): continue - exc_ancestors = [anc for anc in exc.ancestors() - if isinstance(anc, astroid.ClassDef)] + exc_ancestors = [ + anc + for anc in exc.ancestors() + if isinstance(anc, astroid.ClassDef) + ] for previous_exc in exceptions_classes: if previous_exc in exc_ancestors: - msg = '%s is an ancestor class of %s' % ( - previous_exc.name, exc.name) - self.add_message('bad-except-order', - node=handler.type, args=msg) - if (exc.name in self.config.overgeneral_exceptions - and exc.root().name == utils.EXCEPTIONS_MODULE - and not _is_raising(handler.body)): - self.add_message('broad-except', - args=exc.name, node=handler.type) + msg = "%s is an ancestor class of %s" % ( + previous_exc.name, + exc.name, + ) + self.add_message( + "bad-except-order", node=handler.type, args=msg + ) + if ( + exc.name in self.config.overgeneral_exceptions + and exc.root().name == utils.EXCEPTIONS_MODULE + and not _is_raising(handler.body) + ): + self.add_message( + "broad-except", args=exc.name, node=handler.type + ) if exc in exceptions_classes: - self.add_message('duplicate-except', - args=exc.name, node=handler.type) + self.add_message( + "duplicate-except", args=exc.name, node=handler.type + ) exceptions_classes += [exc for _, exc in excs] |