OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2003-2007 LOGILAB S.A. (Paris, FRANCE). |
| 2 # http://www.logilab.fr/ -- mailto:contact@logilab.fr |
| 3 # This program is free software; you can redistribute it and/or modify it under |
| 4 # the terms of the GNU General Public License as published by the Free Software |
| 5 # Foundation; either version 2 of the License, or (at your option) any later |
| 6 # version. |
| 7 # |
| 8 # This program is distributed in the hope that it will be useful, but WITHOUT |
| 9 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 10 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
| 11 # |
| 12 # You should have received a copy of the GNU General Public License along with |
| 13 # this program; if not, write to the Free Software Foundation, Inc., |
| 14 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| 15 """exceptions handling (raising, catching, exceptions classes) checker |
| 16 """ |
| 17 import sys |
| 18 |
| 19 from logilab.common.compat import builtins |
| 20 BUILTINS_NAME = builtins.__name__ |
| 21 from logilab import astng |
| 22 from logilab.astng import YES, Instance, unpack_infer |
| 23 |
| 24 from pylint.checkers import BaseChecker |
| 25 from pylint.checkers.utils import is_empty, is_raising |
| 26 from pylint.interfaces import IASTNGChecker |
| 27 |
| 28 |
| 29 OVERGENERAL_EXCEPTIONS = ('Exception',) |
| 30 |
| 31 MSGS = { |
| 32 'E0701': ( |
| 33 'Bad except clauses order (%s)', |
| 34 'Used when except clauses are not in the correct order (from the \ |
| 35 more specific to the more generic). If you don\'t fix the order, \ |
| 36 some exceptions may not be catched by the most specific handler.'), |
| 37 'E0702': ('Raising %s while only classes, instances or string are allowed', |
| 38 'Used when something which is neither a class, an instance or a \ |
| 39 string is raised (i.e. a `TypeError` will be raised).'), |
| 40 'E0710': ('Raising a new style class which doesn\'t inherit from BaseExcepti
on', |
| 41 'Used when a new style class which doesn\'t inherit from \ |
| 42 BaseException is raised.'), |
| 43 'E0711': ('NotImplemented raised - should raise NotImplementedError', |
| 44 'Used when NotImplemented is raised instead of \ |
| 45 NotImplementedError'), |
| 46 |
| 47 'W0701': ('Raising a string exception', |
| 48 'Used when a string exception is raised.'), |
| 49 'W0702': ('No exception type(s) specified', |
| 50 'Used when an except clause doesn\'t specify exceptions type to \ |
| 51 catch.'), |
| 52 'W0703': ('Catching too general exception %s', |
| 53 'Used when an except catches a too general exception, \ |
| 54 possibly burying unrelated errors.'), |
| 55 'W0704': ('Except doesn\'t do anything', |
| 56 'Used when an except clause does nothing but "pass" and there is\ |
| 57 no "else" clause.'), |
| 58 'W0710': ('Exception doesn\'t inherit from standard "Exception" class', |
| 59 'Used when a custom exception class is raised but doesn\'t \ |
| 60 inherit from the builtin "Exception" class.'), |
| 61 } |
| 62 |
| 63 |
| 64 if sys.version_info < (3, 0): |
| 65 EXCEPTIONS_MODULE = "exceptions" |
| 66 else: |
| 67 EXCEPTIONS_MODULE = "builtins" |
| 68 |
| 69 class ExceptionsChecker(BaseChecker): |
| 70 """checks for |
| 71 * excepts without exception filter |
| 72 * type of raise argument : string, Exceptions, other values |
| 73 """ |
| 74 |
| 75 __implements__ = IASTNGChecker |
| 76 |
| 77 name = 'exceptions' |
| 78 msgs = MSGS |
| 79 priority = -4 |
| 80 options = (('overgeneral-exceptions', |
| 81 {'default' : OVERGENERAL_EXCEPTIONS, |
| 82 'type' :'csv', 'metavar' : '<comma-separated class names>', |
| 83 'help' : 'Exceptions that will emit a warning ' |
| 84 'when being caught. Defaults to "%s"' % ( |
| 85 ', '.join(OVERGENERAL_EXCEPTIONS),)} |
| 86 ), |
| 87 ) |
| 88 |
| 89 def visit_raise(self, node): |
| 90 """visit raise possibly inferring value""" |
| 91 # ignore empty raise |
| 92 if node.exc is None: |
| 93 return |
| 94 expr = node.exc |
| 95 if self._check_raise_value(node, expr): |
| 96 return |
| 97 else: |
| 98 try: |
| 99 value = unpack_infer(expr).next() |
| 100 except astng.InferenceError: |
| 101 return |
| 102 self._check_raise_value(node, value) |
| 103 |
| 104 def _check_raise_value(self, node, expr): |
| 105 """check for bad values, string exception and class inheritance |
| 106 """ |
| 107 value_found = True |
| 108 if isinstance(expr, astng.Const): |
| 109 value = expr.value |
| 110 if isinstance(value, str): |
| 111 self.add_message('W0701', node=node) |
| 112 else: |
| 113 self.add_message('E0702', node=node, |
| 114 args=value.__class__.__name__) |
| 115 elif (isinstance(expr, astng.Name) and \ |
| 116 expr.name in ('None', 'True', 'False')) or \ |
| 117 isinstance(expr, (astng.List, astng.Dict, astng.Tuple, |
| 118 astng.Module, astng.Function)): |
| 119 self.add_message('E0702', node=node, args=expr.name) |
| 120 elif ( (isinstance(expr, astng.Name) and expr.name == 'NotImplemented') |
| 121 or (isinstance(expr, astng.CallFunc) and |
| 122 isinstance(expr.func, astng.Name) and |
| 123 expr.func.name == 'NotImplemented') ): |
| 124 self.add_message('E0711', node=node) |
| 125 elif isinstance(expr, astng.BinOp) and expr.op == '%': |
| 126 self.add_message('W0701', node=node) |
| 127 elif isinstance(expr, (Instance, astng.Class)): |
| 128 if isinstance(expr, Instance): |
| 129 expr = expr._proxied |
| 130 if (isinstance(expr, astng.Class) and |
| 131 not inherit_from_std_ex(expr) and |
| 132 expr.root().name != BUILTINS_NAME): |
| 133 if expr.newstyle: |
| 134 self.add_message('E0710', node=node) |
| 135 else: |
| 136 self.add_message('W0710', node=node) |
| 137 else: |
| 138 value_found = False |
| 139 else: |
| 140 value_found = False |
| 141 return value_found |
| 142 |
| 143 |
| 144 def visit_tryexcept(self, node): |
| 145 """check for empty except""" |
| 146 exceptions_classes = [] |
| 147 nb_handlers = len(node.handlers) |
| 148 for index, handler in enumerate(node.handlers): |
| 149 # single except doing nothing but "pass" without else clause |
| 150 if nb_handlers == 1 and is_empty(handler.body) and not node.orelse: |
| 151 self.add_message('W0704', node=handler.type or handler.body[0]) |
| 152 if handler.type is None: |
| 153 if nb_handlers == 1 and not is_raising(handler.body): |
| 154 self.add_message('W0702', node=handler) |
| 155 # check if a "except:" is followed by some other |
| 156 # except |
| 157 elif index < (nb_handlers - 1): |
| 158 msg = 'empty except clause should always appear last' |
| 159 self.add_message('E0701', node=node, args=msg) |
| 160 else: |
| 161 try: |
| 162 excs = list(unpack_infer(handler.type)) |
| 163 except astng.InferenceError: |
| 164 continue |
| 165 for exc in excs: |
| 166 # XXX skip other non class nodes |
| 167 if exc is YES or not isinstance(exc, astng.Class): |
| 168 continue |
| 169 exc_ancestors = [anc for anc in exc.ancestors() |
| 170 if isinstance(anc, astng.Class)] |
| 171 for previous_exc in exceptions_classes: |
| 172 if previous_exc in exc_ancestors: |
| 173 msg = '%s is an ancestor class of %s' % ( |
| 174 previous_exc.name, exc.name) |
| 175 self.add_message('E0701', node=handler.type, args=ms
g) |
| 176 if (exc.name in self.config.overgeneral_exceptions |
| 177 and exc.root().name == EXCEPTIONS_MODULE |
| 178 and nb_handlers == 1 and not is_raising(handler.body)): |
| 179 self.add_message('W0703', args=exc.name, node=handler.ty
pe) |
| 180 exceptions_classes += excs |
| 181 |
| 182 |
| 183 def inherit_from_std_ex(node): |
| 184 """return true if the given class node is subclass of |
| 185 exceptions.Exception |
| 186 """ |
| 187 if node.name in ('Exception', 'BaseException') \ |
| 188 and node.root().name == EXCEPTIONS_MODULE: |
| 189 return True |
| 190 for parent in node.ancestors(recurs=False): |
| 191 if inherit_from_std_ex(parent): |
| 192 return True |
| 193 return False |
| 194 |
| 195 def register(linter): |
| 196 """required method to auto register this checker""" |
| 197 linter.register_checker(ExceptionsChecker(linter)) |
OLD | NEW |