Index: third_party/pylint/pylint/checkers/exceptions.py |
diff --git a/third_party/pylint/pylint/checkers/exceptions.py b/third_party/pylint/pylint/checkers/exceptions.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..88a8f225e2e80304f8959fbcbe1a39a0aed02267 |
--- /dev/null |
+++ b/third_party/pylint/pylint/checkers/exceptions.py |
@@ -0,0 +1,332 @@ |
+# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). |
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr |
+# This program is free software; you can redistribute it and/or modify it under |
+# the terms of the GNU General Public License as published by the Free Software |
+# Foundation; either version 2 of the License, or (at your option) any later |
+# version. |
+# |
+# This program is distributed in the hope that it will be useful, but WITHOUT |
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
+# |
+# You should have received a copy of the GNU General Public License along with |
+# this program; if not, write to the Free Software Foundation, Inc., |
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
+"""exceptions handling (raising, catching, exceptions classes) checker |
+""" |
+import sys |
+ |
+import astroid |
+from astroid import YES, Instance, unpack_infer, List, Tuple |
+from logilab.common.compat import builtins |
+ |
+from pylint.checkers import BaseChecker |
+from pylint.checkers.utils import ( |
+ is_empty, |
+ is_raising, |
+ check_messages, |
+ inherit_from_std_ex, |
+ EXCEPTIONS_MODULE, |
+ has_known_bases, |
+ safe_infer) |
+from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE |
+ |
+ |
+def _annotated_unpack_infer(stmt, context=None): |
+ """ |
+ Recursively generate nodes inferred by the given statement. |
+ If the inferred value is a list or a tuple, recurse on the elements. |
+ Returns an iterator which yields tuples in the format |
+ ('original node', 'infered node'). |
+ """ |
+ if isinstance(stmt, (List, Tuple)): |
+ for elt in stmt.elts: |
+ inferred = safe_infer(elt) |
+ if inferred and inferred is not YES: |
+ yield elt, inferred |
+ return |
+ for infered in stmt.infer(context): |
+ if infered is YES: |
+ continue |
+ yield stmt, infered |
+ |
+ |
+PY3K = sys.version_info >= (3, 0) |
+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 catched 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.', |
+ {'minversion': (3, 0)}), |
+ '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 BaseException: %s', |
+ 'catching-non-exception', |
+ 'Used when a class which doesn\'t inherit from \ |
+ BaseException 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.'), |
+ 'W0704': ('Except doesn\'t do anything', |
+ 'pointless-except', |
+ 'Used when an except clause does nothing but "pass" and there is\ |
+ no "else" clause.'), |
+ 'W0710': ('Exception doesn\'t inherit from standard "Exception" class', |
+ 'nonstandard-exception', |
+ 'Used when a custom exception class is raised but doesn\'t \ |
+ inherit from the builtin "Exception" class.', |
+ {'maxversion': (3, 0)}), |
+ '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):"'), |
+ } |
+ |
+ |
+class ExceptionsChecker(BaseChecker): |
+ """checks for |
+ * excepts without exception filter |
+ * type of raise argument : string, Exceptions, other values |
+ """ |
+ |
+ __implements__ = IAstroidChecker |
+ |
+ 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),)} |
+ ), |
+ ) |
+ |
+ @check_messages('nonstandard-exception', |
+ 'raising-bad-type', 'raising-non-exception', |
+ 'notimplemented-raised', 'bad-exception-context') |
+ def visit_raise(self, node): |
+ """visit raise possibly inferring value""" |
+ # ignore empty raise |
+ if node.exc is None: |
+ return |
+ if PY3K and node.cause: |
+ self._check_bad_exception_context(node) |
+ |
+ expr = node.exc |
+ if self._check_raise_value(node, expr): |
+ return |
+ else: |
+ try: |
+ value = next(unpack_infer(expr)) |
+ except astroid.InferenceError: |
+ return |
+ self._check_raise_value(node, value) |
+ |
+ def _check_bad_exception_context(self, node): |
+ """Verify that the exception context is properly set. |
+ |
+ An exception context can be only `None` or an exception. |
+ """ |
+ cause = safe_infer(node.cause) |
+ if cause in (YES, None): |
+ return |
+ if isinstance(cause, astroid.Const): |
+ if cause.value is not None: |
+ self.add_message('bad-exception-context', |
+ node=node) |
+ elif (not isinstance(cause, astroid.Class) and |
+ not inherit_from_std_ex(cause)): |
+ self.add_message('bad-exception-context', |
+ node=node) |
+ |
+ def _check_raise_value(self, node, expr): |
+ """check for bad values, string exception and class inheritance |
+ """ |
+ value_found = True |
+ if isinstance(expr, astroid.Const): |
+ value = expr.value |
+ if not isinstance(value, str): |
+ # raising-string will be emitted from python3 porting checker. |
+ self.add_message('raising-bad-type', node=node, |
+ args=value.__class__.__name__) |
+ elif ((isinstance(expr, astroid.Name) and |
+ expr.name in ('None', 'True', 'False')) or |
+ isinstance(expr, (astroid.List, astroid.Dict, astroid.Tuple, |
+ astroid.Module, astroid.Function))): |
+ emit = True |
+ if not PY3K and isinstance(expr, astroid.Tuple): |
+ # On Python 2, using the following is not an error: |
+ # raise (ZeroDivisionError, None) |
+ # raise (ZeroDivisionError, ) |
+ # What's left to do is to check that the first |
+ # argument is indeed an exception. |
+ # Verifying the other arguments is not |
+ # the scope of this check. |
+ first = expr.elts[0] |
+ inferred = safe_infer(first) |
+ if isinstance(inferred, Instance): |
+ # pylint: disable=protected-access |
+ inferred = inferred._proxied |
+ if (inferred is YES or |
+ isinstance(inferred, astroid.Class) |
+ and inherit_from_std_ex(inferred)): |
+ emit = False |
+ if emit: |
+ self.add_message('raising-bad-type', |
+ node=node, |
+ args=expr.name) |
+ elif ((isinstance(expr, astroid.Name) and expr.name == 'NotImplemented') |
+ or (isinstance(expr, astroid.CallFunc) and |
+ isinstance(expr.func, astroid.Name) and |
+ expr.func.name == 'NotImplemented')): |
+ self.add_message('notimplemented-raised', node=node) |
+ elif isinstance(expr, (Instance, astroid.Class)): |
+ if isinstance(expr, Instance): |
+ # pylint: disable=protected-access |
+ expr = expr._proxied |
+ if (isinstance(expr, astroid.Class) and |
+ not inherit_from_std_ex(expr)): |
+ if expr.newstyle: |
+ self.add_message('raising-non-exception', node=node) |
+ else: |
+ if has_known_bases(expr): |
+ confidence = INFERENCE |
+ else: |
+ confidence = INFERENCE_FAILURE |
+ self.add_message( |
+ 'nonstandard-exception', node=node, |
+ confidence=confidence) |
+ else: |
+ value_found = False |
+ else: |
+ value_found = False |
+ return value_found |
+ |
+ def _check_catching_non_exception(self, handler, exc, part): |
+ if isinstance(exc, astroid.Tuple): |
+ # Check if it is a tuple of exceptions. |
+ inferred = [safe_infer(elt) for elt in exc.elts] |
+ if any(node is astroid.YES for node in inferred): |
+ # Don't emit if we don't know every component. |
+ return |
+ if all(node and inherit_from_std_ex(node) |
+ for node in inferred): |
+ return |
+ |
+ if not isinstance(exc, astroid.Class): |
+ # 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 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(), )) |
+ else: |
+ self.add_message('catching-non-exception', |
+ node=handler.type, |
+ args=(part.as_string(), )) |
+ return |
+ if (not inherit_from_std_ex(exc) and |
+ exc.root().name != BUILTINS_NAME): |
+ if has_known_bases(exc): |
+ self.add_message('catching-non-exception', |
+ node=handler.type, |
+ args=(exc.name, )) |
+ |
+ @check_messages('bare-except', 'broad-except', 'pointless-except', |
+ 'binary-op-exception', 'bad-except-order', |
+ 'catching-non-exception') |
+ def visit_tryexcept(self, node): |
+ """check for empty except""" |
+ exceptions_classes = [] |
+ nb_handlers = len(node.handlers) |
+ for index, handler in enumerate(node.handlers): |
+ # single except doing nothing but "pass" without else clause |
+ if is_empty(handler.body) and not node.orelse: |
+ self.add_message('pointless-except', |
+ node=handler.type or handler.body[0]) |
+ if handler.type is None: |
+ if not is_raising(handler.body): |
+ self.add_message('bare-except', node=handler) |
+ # check if a "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) |
+ |
+ elif isinstance(handler.type, astroid.BoolOp): |
+ self.add_message('binary-op-exception', |
+ node=handler, args=handler.type.op) |
+ else: |
+ try: |
+ excs = list(_annotated_unpack_infer(handler.type)) |
+ except astroid.InferenceError: |
+ continue |
+ for part, exc in excs: |
+ if exc is YES: |
+ continue |
+ if (isinstance(exc, astroid.Instance) |
+ and inherit_from_std_ex(exc)): |
+ # pylint: disable=protected-access |
+ exc = exc._proxied |
+ |
+ self._check_catching_non_exception(handler, exc, part) |
+ |
+ if not isinstance(exc, astroid.Class): |
+ continue |
+ |
+ exc_ancestors = [anc for anc in exc.ancestors() |
+ if isinstance(anc, astroid.Class)] |
+ 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 == EXCEPTIONS_MODULE |
+ and not is_raising(handler.body)): |
+ self.add_message('broad-except', |
+ args=exc.name, node=handler.type) |
+ |
+ exceptions_classes += [exc for _, exc in excs] |
+ |
+ |
+def register(linter): |
+ """required method to auto register this checker""" |
+ linter.register_checker(ExceptionsChecker(linter)) |