Adding new stuff
This commit is contained in:
parent
cb028acf05
commit
7b21428a6d
451 changed files with 6 additions and 1 deletions
|
|
@ -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))
|
||||
Loading…
Add table
Add a link
Reference in a new issue