third_party.pylibs.pylint.src/checkers/exceptions.py
root 4becf6f9e5 forget the past.
forget the past.
2006-04-26 10:48:09 +00:00

168 lines
7.1 KiB
Python

# 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.,
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
""" Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
http://www.logilab.fr/ -- mailto:contact@logilab.fr
exceptions checkers for Python code
"""
__revision__ = '$Id: exceptions.py,v 1.27 2006-03-08 15:53:42 syt Exp $'
from logilab.common.compat import enumerate
from logilab import astng
from logilab.astng.inference import unpack_infer
from pylint.checkers import BaseChecker
from pylint.checkers.utils import is_empty
from pylint.interfaces import IASTNGChecker
MSGS = {
'E0701': (
'Bad except clauses order (%s)',
'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, instances or string are allowed',
'Used when something which is neither a class, an instance or a \
string is raised (i.e. a `TypeError` will be raised).'),
'W0701': ('Raising a string exception',
'Used when a string exception is raised.'),
'W0702': ('No exception\'s type specified',
'Used when an except clause doesn\'t specify exceptions type to \
catch.'),
'W0703': ('Catch "Exception"',
'Used when an except catch Exception instances.'),
'W0704': ('Except doesn\'t do anything',
'Used when an except clause does nothing but "pass" and there is\
no "else" clause.'),
'W0706': (
'Identifier %s used to raise an exception is assigned to %s',
'Used when a variable used to raise an exception is initially \
assigned to a value which can\'t be used as an exception.'),
}
def is_raising(stmt):
"""return true if the given statement node raise an exception
"""
for node in stmt.nodes:
if isinstance(node, astng.Raise):
return 1
return 0
class ExceptionsChecker(BaseChecker):
"""checks for
* excepts without exception filter
* string exceptions
"""
__implements__ = IASTNGChecker
name = 'exceptions'
msgs = MSGS
priority = -4
options = ()
def visit_raise(self, node):
"""check for string exception
"""
# ignore empty raise
if node.expr1 is None:
return
expr = node.expr1
if isinstance(expr, astng.Const):
value = expr.value
if isinstance(value, str):
self.add_message('W0701', node=node)
else:
self.add_message('E0702', node=node,
args=value.__class__.__name__)
elif isinstance(expr, astng.Name) and \
expr.name in ('None', 'True', 'False'):
self.add_message('E0702', node=node, args=expr.name)
elif isinstance(expr, astng.Mod):
self.add_message('W0701', node=node)
else:
try:
value = unpack_infer(expr).next()
except astng.InferenceError:
return
if value is astng.YES:
return
# must to be carefull since Const, Dict, .. inherit from
# Instance now
if isinstance(value, (astng.Class, astng.Module)):
return
if isinstance(value, astng.Instance) and \
isinstance(value._proxied, astng.Class):
return
if isinstance(value, astng.Const) and \
(value.value is None or
value.value is True or value.value is False):
# this Const has been generated by resolve
# since None, True and False are represented by Name
# nodes in the ast, and so this const node doesn't
# have the necessary parent, lineno and so on attributes
assinfo = value.as_string()
else:
assinfo = '%s line %s' % (value.as_string(),
value.source_line())
self.add_message('W0706', node=node,
args=(expr.as_string(), assinfo))
def visit_tryexcept(self, node):
"""check for empty except
"""
exceptions_classes = []
nb_handlers = len(node.handlers)
for index, handler in enumerate(node.handlers):
exc_type = handler[0]
stmt = handler[2]
# single except doing nothing but "pass" without else clause
if nb_handlers == 1 and is_empty(stmt) and not node.else_:
self.add_message('W0704', node=exc_type)
if exc_type is None:
if nb_handlers == 1 and not is_raising(stmt):
self.add_message('W0702', node=stmt.nodes[0])
# check if a "except:" is followed by some other
# except
elif index < (nb_handlers - 1):
msg = 'empty except clause should always appears last'
self.add_message('E0701', node=node, args=msg)
else:
try:
excs = list(unpack_infer(exc_type))
except astng.InferenceError:
continue
for exc in excs:
if exc is astng.YES:
continue
if not isinstance(exc, astng.Class):
continue # XXX
exc_ancestors = [anc for anc in exc.ancestors()
if isinstance(anc, astng.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('E0701', node=exc_type, args=msg)
if (exc.name == 'Exception'
and exc.root().name == 'exceptions'
and nb_handlers == 1 and not is_raising(stmt)):
self.add_message('W0703', node=exc_type)
exceptions_classes += excs
def register(linter):
"""required method to auto register this checker"""
linter.register_checker(ExceptionsChecker(linter))