OLD | NEW |
1 # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). | 1 # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). |
2 # http://www.logilab.fr/ -- mailto:contact@logilab.fr | 2 # http://www.logilab.fr/ -- mailto:contact@logilab.fr |
3 # This program is free software; you can redistribute it and/or modify it under | 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 | 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 | 5 # Foundation; either version 2 of the License, or (at your option) any later |
6 # version. | 6 # version. |
7 # | 7 # |
8 # This program is distributed in the hope that it will be useful, but WITHOUT | 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 | 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. | 10 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
11 # | 11 # |
12 # You should have received a copy of the GNU General Public License along with | 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., | 13 # this program; if not, write to the Free Software Foundation, Inc., |
14 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | 14 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
15 """exceptions handling (raising, catching, exceptions classes) checker | 15 """exceptions handling (raising, catching, exceptions classes) checker |
16 """ | 16 """ |
17 import sys | 17 import sys |
18 | 18 |
19 from logilab.common.compat import builtins | 19 from logilab.common.compat import builtins |
20 BUILTINS_NAME = builtins.__name__ | 20 BUILTINS_NAME = builtins.__name__ |
21 import astroid | 21 import astroid |
22 from astroid import YES, Instance, unpack_infer | 22 from astroid import YES, Instance, unpack_infer, List, Tuple |
23 | 23 |
24 from pylint.checkers import BaseChecker | 24 from pylint.checkers import BaseChecker |
25 from pylint.checkers.utils import is_empty, is_raising, check_messages | 25 from pylint.checkers.utils import ( |
26 from pylint.interfaces import IAstroidChecker | 26 is_empty, is_raising, |
| 27 check_messages, inherit_from_std_ex, |
| 28 EXCEPTIONS_MODULE, has_known_bases) |
| 29 from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE |
27 | 30 |
28 def infer_bases(klass): | 31 def _annotated_unpack_infer(stmt, context=None): |
29 """ Fully infer the bases of the klass node. | 32 """ |
| 33 Recursively generate nodes inferred by the given statement. |
| 34 If the inferred value is a list or a tuple, recurse on the elements. |
| 35 Returns an iterator which yields tuples in the format |
| 36 ('original node', 'infered node'). |
| 37 """ |
| 38 # TODO: the same code as unpack_infer, except for the annotated |
| 39 # return. We need this type of annotation only here and |
| 40 # there is no point in complicating the API for unpack_infer. |
| 41 # If the need arises, this behaviour can be promoted to unpack_infer |
| 42 # as well. |
| 43 if isinstance(stmt, (List, Tuple)): |
| 44 for elt in stmt.elts: |
| 45 for infered_elt in unpack_infer(elt, context): |
| 46 yield elt, infered_elt |
| 47 return |
| 48 # if infered is a final node, return it and stop |
| 49 infered = next(stmt.infer(context)) |
| 50 if infered is stmt: |
| 51 yield stmt, infered |
| 52 return |
| 53 # else, infer recursivly, except YES object that should be returned as is |
| 54 for infered in stmt.infer(context): |
| 55 if infered is YES: |
| 56 yield stmt, infered |
| 57 else: |
| 58 for inf_inf in unpack_infer(infered, context): |
| 59 yield stmt, inf_inf |
30 | 60 |
31 This doesn't use .ancestors(), because we need | |
32 the non-inferable nodes (YES nodes), | |
33 which can't be retrieved from .ancestors() | |
34 """ | |
35 for base in klass.bases: | |
36 try: | |
37 inferit = base.infer().next() | |
38 except astroid.InferenceError: | |
39 continue | |
40 if inferit is YES: | |
41 yield inferit | |
42 else: | |
43 for base in infer_bases(inferit): | |
44 yield base | |
45 | 61 |
46 PY3K = sys.version_info >= (3, 0) | 62 PY3K = sys.version_info >= (3, 0) |
47 OVERGENERAL_EXCEPTIONS = ('Exception',) | 63 OVERGENERAL_EXCEPTIONS = ('Exception',) |
48 | 64 |
49 MSGS = { | 65 MSGS = { |
50 'E0701': ('Bad except clauses order (%s)', | 66 'E0701': ('Bad except clauses order (%s)', |
51 'bad-except-order', | 67 'bad-except-order', |
52 'Used when except clauses are not in the correct order (from the ' | 68 'Used when except clauses are not in the correct order (from the ' |
53 'more specific to the more generic). If you don\'t fix the order,
' | 69 'more specific to the more generic). If you don\'t fix the order,
' |
54 'some exceptions may not be catched by the most specific handler.'
), | 70 'some exceptions may not be catched by the most specific handler.'
), |
55 'E0702': ('Raising %s while only classes, instances or string are allowed', | 71 'E0702': ('Raising %s while only classes or instances are allowed', |
56 'raising-bad-type', | 72 'raising-bad-type', |
57 'Used when something which is neither a class, an instance or a \ | 73 'Used when something which is neither a class, an instance or a \ |
58 string is raised (i.e. a `TypeError` will be raised).'), | 74 string is raised (i.e. a `TypeError` will be raised).'), |
59 'E0703': ('Exception context set to something which is not an ' | 75 'E0703': ('Exception context set to something which is not an ' |
60 'exception, nor None', | 76 'exception, nor None', |
61 'bad-exception-context', | 77 'bad-exception-context', |
62 'Used when using the syntax "raise ... from ...", ' | 78 'Used when using the syntax "raise ... from ...", ' |
63 'where the exception context is not an exception, ' | 79 'where the exception context is not an exception, ' |
64 'nor None.', | 80 'nor None.', |
65 {'minversion': (3, 0)}), | 81 {'minversion': (3, 0)}), |
66 'E0710': ('Raising a new style class which doesn\'t inherit from BaseExcepti
on', | 82 'E0710': ('Raising a new style class which doesn\'t inherit from BaseExcepti
on', |
67 'raising-non-exception', | 83 'raising-non-exception', |
68 'Used when a new style class which doesn\'t inherit from \ | 84 'Used when a new style class which doesn\'t inherit from \ |
69 BaseException is raised.'), | 85 BaseException is raised.'), |
70 'E0711': ('NotImplemented raised - should raise NotImplementedError', | 86 'E0711': ('NotImplemented raised - should raise NotImplementedError', |
71 'notimplemented-raised', | 87 'notimplemented-raised', |
72 'Used when NotImplemented is raised instead of \ | 88 'Used when NotImplemented is raised instead of \ |
73 NotImplementedError'), | 89 NotImplementedError'), |
74 'E0712': ('Catching an exception which doesn\'t inherit from BaseException:
%s', | 90 'E0712': ('Catching an exception which doesn\'t inherit from BaseException:
%s', |
75 'catching-non-exception', | 91 'catching-non-exception', |
76 'Used when a class which doesn\'t inherit from \ | 92 'Used when a class which doesn\'t inherit from \ |
77 BaseException is used as an exception in an except clause.'), | 93 BaseException is used as an exception in an except clause.'), |
78 | |
79 'W0701': ('Raising a string exception', | |
80 'raising-string', | |
81 'Used when a string exception is raised.'), | |
82 'W0702': ('No exception type(s) specified', | 94 'W0702': ('No exception type(s) specified', |
83 'bare-except', | 95 'bare-except', |
84 'Used when an except clause doesn\'t specify exceptions type to \ | 96 'Used when an except clause doesn\'t specify exceptions type to \ |
85 catch.'), | 97 catch.'), |
86 'W0703': ('Catching too general exception %s', | 98 'W0703': ('Catching too general exception %s', |
87 'broad-except', | 99 'broad-except', |
88 'Used when an except catches a too general exception, \ | 100 'Used when an except catches a too general exception, \ |
89 possibly burying unrelated errors.'), | 101 possibly burying unrelated errors.'), |
90 'W0704': ('Except doesn\'t do anything', | 102 'W0704': ('Except doesn\'t do anything', |
91 'pointless-except', | 103 'pointless-except', |
92 'Used when an except clause does nothing but "pass" and there is\ | 104 'Used when an except clause does nothing but "pass" and there is\ |
93 no "else" clause.'), | 105 no "else" clause.'), |
94 'W0710': ('Exception doesn\'t inherit from standard "Exception" class', | 106 'W0710': ('Exception doesn\'t inherit from standard "Exception" class', |
95 'nonstandard-exception', | 107 'nonstandard-exception', |
96 'Used when a custom exception class is raised but doesn\'t \ | 108 'Used when a custom exception class is raised but doesn\'t \ |
97 inherit from the builtin "Exception" class.', | 109 inherit from the builtin "Exception" class.', |
98 {'maxversion': (3, 0)}), | 110 {'maxversion': (3, 0)}), |
99 'W0711': ('Exception to catch is the result of a binary "%s" operation', | 111 'W0711': ('Exception to catch is the result of a binary "%s" operation', |
100 'binary-op-exception', | 112 'binary-op-exception', |
101 'Used when the exception to catch is of the form \ | 113 'Used when the exception to catch is of the form \ |
102 "except A or B:". If intending to catch multiple, \ | 114 "except A or B:". If intending to catch multiple, \ |
103 rewrite as "except (A, B):"'), | 115 rewrite as "except (A, B):"'), |
104 'W0712': ('Implicit unpacking of exceptions is not supported in Python 3', | |
105 'unpacking-in-except', | |
106 'Python3 will not allow implicit unpacking of exceptions in except
' | |
107 'clauses. ' | |
108 'See http://www.python.org/dev/peps/pep-3110/', | |
109 {'maxversion': (3, 0)}), | |
110 'W0713': ('Indexing exceptions will not work on Python 3', | |
111 'indexing-exception', | |
112 'Indexing exceptions will not work on Python 3. Use ' | |
113 '`exception.args[index]` instead.', | |
114 {'maxversion': (3, 0)}), | |
115 } | 116 } |
116 | 117 |
117 | 118 |
118 if sys.version_info < (3, 0): | |
119 EXCEPTIONS_MODULE = "exceptions" | |
120 else: | |
121 EXCEPTIONS_MODULE = "builtins" | |
122 | |
123 class ExceptionsChecker(BaseChecker): | 119 class ExceptionsChecker(BaseChecker): |
124 """checks for | 120 """checks for |
125 * excepts without exception filter | 121 * excepts without exception filter |
126 * type of raise argument : string, Exceptions, other values | 122 * type of raise argument : string, Exceptions, other values |
127 """ | 123 """ |
128 | 124 |
129 __implements__ = IAstroidChecker | 125 __implements__ = IAstroidChecker |
130 | 126 |
131 name = 'exceptions' | 127 name = 'exceptions' |
132 msgs = MSGS | 128 msgs = MSGS |
133 priority = -4 | 129 priority = -4 |
134 options = (('overgeneral-exceptions', | 130 options = (('overgeneral-exceptions', |
135 {'default' : OVERGENERAL_EXCEPTIONS, | 131 {'default' : OVERGENERAL_EXCEPTIONS, |
136 'type' :'csv', 'metavar' : '<comma-separated class names>', | 132 'type' :'csv', 'metavar' : '<comma-separated class names>', |
137 'help' : 'Exceptions that will emit a warning ' | 133 'help' : 'Exceptions that will emit a warning ' |
138 'when being caught. Defaults to "%s"' % ( | 134 'when being caught. Defaults to "%s"' % ( |
139 ', '.join(OVERGENERAL_EXCEPTIONS),)} | 135 ', '.join(OVERGENERAL_EXCEPTIONS),)} |
140 ), | 136 ), |
141 ) | 137 ) |
142 | 138 |
143 @check_messages('raising-string', 'nonstandard-exception', 'raising-bad-type
', | 139 @check_messages('nonstandard-exception', |
144 'raising-non-exception', 'notimplemented-raised', 'bad-excep
tion-context') | 140 'raising-bad-type', 'raising-non-exception', |
| 141 'notimplemented-raised', 'bad-exception-context') |
145 def visit_raise(self, node): | 142 def visit_raise(self, node): |
146 """visit raise possibly inferring value""" | 143 """visit raise possibly inferring value""" |
147 # ignore empty raise | 144 # ignore empty raise |
148 if node.exc is None: | 145 if node.exc is None: |
149 return | 146 return |
150 if PY3K and node.cause: | 147 if PY3K and node.cause: |
151 try: | 148 try: |
152 cause = node.cause.infer().next() | 149 cause = next(node.cause.infer()) |
153 except astroid.InferenceError: | 150 except astroid.InferenceError: |
154 pass | 151 pass |
155 else: | 152 else: |
156 if cause is YES: | 153 if cause is YES: |
157 return | 154 return |
158 if isinstance(cause, astroid.Const): | 155 if isinstance(cause, astroid.Const): |
159 if cause.value is not None: | 156 if cause.value is not None: |
160 self.add_message('bad-exception-context', | 157 self.add_message('bad-exception-context', |
161 node=node) | 158 node=node) |
162 elif (not isinstance(cause, astroid.Class) and | 159 elif (not isinstance(cause, astroid.Class) and |
163 not inherit_from_std_ex(cause)): | 160 not inherit_from_std_ex(cause)): |
164 self.add_message('bad-exception-context', | 161 self.add_message('bad-exception-context', |
165 node=node) | 162 node=node) |
166 expr = node.exc | 163 expr = node.exc |
167 if self._check_raise_value(node, expr): | 164 if self._check_raise_value(node, expr): |
168 return | 165 return |
169 else: | 166 else: |
170 try: | 167 try: |
171 value = unpack_infer(expr).next() | 168 value = next(unpack_infer(expr)) |
172 except astroid.InferenceError: | 169 except astroid.InferenceError: |
173 return | 170 return |
174 self._check_raise_value(node, value) | 171 self._check_raise_value(node, value) |
175 | 172 |
176 def _check_raise_value(self, node, expr): | 173 def _check_raise_value(self, node, expr): |
177 """check for bad values, string exception and class inheritance | 174 """check for bad values, string exception and class inheritance |
178 """ | 175 """ |
179 value_found = True | 176 value_found = True |
180 if isinstance(expr, astroid.Const): | 177 if isinstance(expr, astroid.Const): |
181 value = expr.value | 178 value = expr.value |
182 if isinstance(value, str): | 179 if isinstance(value, str): |
183 self.add_message('raising-string', node=node) | 180 # raising-string will be emitted from python3 porting checker. |
| 181 pass |
184 else: | 182 else: |
185 self.add_message('raising-bad-type', node=node, | 183 self.add_message('raising-bad-type', node=node, |
186 args=value.__class__.__name__) | 184 args=value.__class__.__name__) |
187 elif (isinstance(expr, astroid.Name) and \ | 185 elif (isinstance(expr, astroid.Name) and \ |
188 expr.name in ('None', 'True', 'False')) or \ | 186 expr.name in ('None', 'True', 'False')) or \ |
189 isinstance(expr, (astroid.List, astroid.Dict, astroid.Tuple, | 187 isinstance(expr, (astroid.List, astroid.Dict, astroid.Tuple, |
190 astroid.Module, astroid.Function)): | 188 astroid.Module, astroid.Function)): |
191 self.add_message('raising-bad-type', node=node, args=expr.name) | 189 self.add_message('raising-bad-type', node=node, args=expr.name) |
192 elif ((isinstance(expr, astroid.Name) and expr.name == 'NotImplemented') | 190 elif ((isinstance(expr, astroid.Name) and expr.name == 'NotImplemented') |
193 or (isinstance(expr, astroid.CallFunc) and | 191 or (isinstance(expr, astroid.CallFunc) and |
194 isinstance(expr.func, astroid.Name) and | 192 isinstance(expr.func, astroid.Name) and |
195 expr.func.name == 'NotImplemented')): | 193 expr.func.name == 'NotImplemented')): |
196 self.add_message('notimplemented-raised', node=node) | 194 self.add_message('notimplemented-raised', node=node) |
197 elif isinstance(expr, astroid.BinOp) and expr.op == '%': | |
198 self.add_message('raising-string', node=node) | |
199 elif isinstance(expr, (Instance, astroid.Class)): | 195 elif isinstance(expr, (Instance, astroid.Class)): |
200 if isinstance(expr, Instance): | 196 if isinstance(expr, Instance): |
201 expr = expr._proxied | 197 expr = expr._proxied |
202 if (isinstance(expr, astroid.Class) and | 198 if (isinstance(expr, astroid.Class) and |
203 not inherit_from_std_ex(expr) and | 199 not inherit_from_std_ex(expr) and |
204 expr.root().name != BUILTINS_NAME): | 200 expr.root().name != BUILTINS_NAME): |
205 if expr.newstyle: | 201 if expr.newstyle: |
206 self.add_message('raising-non-exception', node=node) | 202 self.add_message('raising-non-exception', node=node) |
207 else: | 203 else: |
208 self.add_message('nonstandard-exception', node=node) | 204 self.add_message( |
| 205 'nonstandard-exception', node=node, |
| 206 confidence=INFERENCE if has_known_bases(expr) else INFER
ENCE_FAILURE) |
209 else: | 207 else: |
210 value_found = False | 208 value_found = False |
211 else: | 209 else: |
212 value_found = False | 210 value_found = False |
213 return value_found | 211 return value_found |
214 | 212 |
215 @check_messages('unpacking-in-except') | |
216 def visit_excepthandler(self, node): | |
217 """Visit an except handler block and check for exception unpacking.""" | |
218 if isinstance(node.name, (astroid.Tuple, astroid.List)): | |
219 self.add_message('unpacking-in-except', node=node) | |
220 | |
221 @check_messages('indexing-exception') | |
222 def visit_subscript(self, node): | |
223 """ Look for indexing exceptions. """ | |
224 try: | |
225 for infered in node.value.infer(): | |
226 if not isinstance(infered, astroid.Instance): | |
227 continue | |
228 if inherit_from_std_ex(infered): | |
229 self.add_message('indexing-exception', node=node) | |
230 except astroid.InferenceError: | |
231 return | |
232 | |
233 @check_messages('bare-except', 'broad-except', 'pointless-except', | 213 @check_messages('bare-except', 'broad-except', 'pointless-except', |
234 'binary-op-exception', 'bad-except-order', | 214 'binary-op-exception', 'bad-except-order', |
235 'catching-non-exception') | 215 'catching-non-exception') |
236 def visit_tryexcept(self, node): | 216 def visit_tryexcept(self, node): |
237 """check for empty except""" | 217 """check for empty except""" |
238 exceptions_classes = [] | 218 exceptions_classes = [] |
239 nb_handlers = len(node.handlers) | 219 nb_handlers = len(node.handlers) |
240 for index, handler in enumerate(node.handlers): | 220 for index, handler in enumerate(node.handlers): |
241 # single except doing nothing but "pass" without else clause | 221 # single except doing nothing but "pass" without else clause |
242 if is_empty(handler.body) and not node.orelse: | 222 if is_empty(handler.body) and not node.orelse: |
243 self.add_message('pointless-except', node=handler.type or handle
r.body[0]) | 223 self.add_message('pointless-except', |
| 224 node=handler.type or handler.body[0]) |
244 if handler.type is None: | 225 if handler.type is None: |
245 if not is_raising(handler.body): | 226 if not is_raising(handler.body): |
246 self.add_message('bare-except', node=handler) | 227 self.add_message('bare-except', node=handler) |
247 # check if a "except:" is followed by some other | 228 # check if a "except:" is followed by some other |
248 # except | 229 # except |
249 if index < (nb_handlers - 1): | 230 if index < (nb_handlers - 1): |
250 msg = 'empty except clause should always appear last' | 231 msg = 'empty except clause should always appear last' |
251 self.add_message('bad-except-order', node=node, args=msg) | 232 self.add_message('bad-except-order', node=node, args=msg) |
252 | 233 |
253 elif isinstance(handler.type, astroid.BoolOp): | 234 elif isinstance(handler.type, astroid.BoolOp): |
254 self.add_message('binary-op-exception', node=handler, args=handl
er.type.op) | 235 self.add_message('binary-op-exception', |
| 236 node=handler, args=handler.type.op) |
255 else: | 237 else: |
256 try: | 238 try: |
257 excs = list(unpack_infer(handler.type)) | 239 excs = list(_annotated_unpack_infer(handler.type)) |
258 except astroid.InferenceError: | 240 except astroid.InferenceError: |
259 continue | 241 continue |
260 for exc in excs: | 242 for part, exc in excs: |
261 # XXX skip other non class nodes | 243 if exc is YES: |
262 if exc is YES or not isinstance(exc, astroid.Class): | |
263 continue | 244 continue |
| 245 if isinstance(exc, astroid.Instance) and inherit_from_std_ex
(exc): |
| 246 exc = exc._proxied |
| 247 if not isinstance(exc, astroid.Class): |
| 248 # Don't emit the warning if the infered stmt |
| 249 # is None, but the exception handler is something else, |
| 250 # maybe it was redefined. |
| 251 if (isinstance(exc, astroid.Const) and |
| 252 exc.value is None): |
| 253 if ((isinstance(handler.type, astroid.Const) and |
| 254 handler.type.value is None) or |
| 255 handler.type.parent_of(exc)): |
| 256 # If the exception handler catches None or |
| 257 # the exception component, which is None, is |
| 258 # defined by the entire exception handler, then |
| 259 # emit a warning. |
| 260 self.add_message('catching-non-exception', |
| 261 node=handler.type, |
| 262 args=(part.as_string(), )) |
| 263 else: |
| 264 self.add_message('catching-non-exception', |
| 265 node=handler.type, |
| 266 args=(part.as_string(), )) |
| 267 continue |
| 268 |
264 exc_ancestors = [anc for anc in exc.ancestors() | 269 exc_ancestors = [anc for anc in exc.ancestors() |
265 if isinstance(anc, astroid.Class)] | 270 if isinstance(anc, astroid.Class)] |
266 for previous_exc in exceptions_classes: | 271 for previous_exc in exceptions_classes: |
267 if previous_exc in exc_ancestors: | 272 if previous_exc in exc_ancestors: |
268 msg = '%s is an ancestor class of %s' % ( | 273 msg = '%s is an ancestor class of %s' % ( |
269 previous_exc.name, exc.name) | 274 previous_exc.name, exc.name) |
270 self.add_message('bad-except-order', node=handler.ty
pe, args=msg) | 275 self.add_message('bad-except-order', |
| 276 node=handler.type, args=msg) |
271 if (exc.name in self.config.overgeneral_exceptions | 277 if (exc.name in self.config.overgeneral_exceptions |
272 and exc.root().name == EXCEPTIONS_MODULE | 278 and exc.root().name == EXCEPTIONS_MODULE |
273 and not is_raising(handler.body)): | 279 and not is_raising(handler.body)): |
274 self.add_message('broad-except', args=exc.name, node=han
dler.type) | 280 self.add_message('broad-except', |
| 281 args=exc.name, node=handler.type) |
275 | 282 |
276 if (not inherit_from_std_ex(exc) and | 283 if (not inherit_from_std_ex(exc) and |
277 exc.root().name != BUILTINS_NAME): | 284 exc.root().name != BUILTINS_NAME): |
278 # try to see if the exception is based on a C based | 285 if has_known_bases(exc): |
279 # exception, by infering all the base classes and | |
280 # looking for inference errors | |
281 bases = infer_bases(exc) | |
282 fully_infered = all(inferit is not YES | |
283 for inferit in bases) | |
284 if fully_infered: | |
285 self.add_message('catching-non-exception', | 286 self.add_message('catching-non-exception', |
286 node=handler.type, | 287 node=handler.type, |
287 args=(exc.name, )) | 288 args=(exc.name, )) |
288 | 289 |
289 exceptions_classes += excs | 290 exceptions_classes += [exc for _, exc in excs] |
290 | 291 |
291 | 292 |
292 def inherit_from_std_ex(node): | |
293 """return true if the given class node is subclass of | |
294 exceptions.Exception | |
295 """ | |
296 if node.name in ('Exception', 'BaseException') \ | |
297 and node.root().name == EXCEPTIONS_MODULE: | |
298 return True | |
299 for parent in node.ancestors(recurs=False): | |
300 if inherit_from_std_ex(parent): | |
301 return True | |
302 return False | |
303 | |
304 def register(linter): | 293 def register(linter): |
305 """required method to auto register this checker""" | 294 """required method to auto register this checker""" |
306 linter.register_checker(ExceptionsChecker(linter)) | 295 linter.register_checker(ExceptionsChecker(linter)) |
OLD | NEW |