OLD | NEW |
1 # Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). | 1 # Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). |
2 # http://www.logilab.fr/ -- mailto:contact@logilab.fr | 2 # http://www.logilab.fr/ -- mailto:contact@logilab.fr |
3 # | 3 # |
4 # This program is free software; you can redistribute it and/or modify it under | 4 # This program is free software; you can redistribute it and/or modify it under |
5 # the terms of the GNU General Public License as published by the Free Software | 5 # the terms of the GNU General Public License as published by the Free Software |
6 # Foundation; either version 2 of the License, or (at your option) any later | 6 # Foundation; either version 2 of the License, or (at your option) any later |
7 # version. | 7 # version. |
8 # | 8 # |
9 # This program is distributed in the hope that it will be useful, but WITHOUT | 9 # This program is distributed in the hope that it will be useful, but WITHOUT |
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | 10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
11 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | 11 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
12 # | 12 # |
13 # You should have received a copy of the GNU General Public License along with | 13 # You should have received a copy of the GNU General Public License along with |
14 # this program; if not, write to the Free Software Foundation, Inc., | 14 # this program; if not, write to the Free Software Foundation, Inc., |
15 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | 15 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 """classes checker for Python code | 16 """classes checker for Python code |
17 """ | 17 """ |
18 from __future__ import generators | 18 from __future__ import generators |
19 | 19 |
20 import sys | 20 import sys |
| 21 from collections import defaultdict |
21 | 22 |
22 import astroid | 23 import astroid |
23 from astroid import YES, Instance, are_exclusive, AssAttr, Class | 24 from astroid import YES, Instance, are_exclusive, AssAttr, Class |
24 from astroid.bases import Generator | 25 from astroid.bases import Generator, BUILTINS |
| 26 from astroid.inference import InferenceContext |
25 | 27 |
26 from pylint.interfaces import IAstroidChecker | 28 from pylint.interfaces import IAstroidChecker |
27 from pylint.checkers import BaseChecker | 29 from pylint.checkers import BaseChecker |
28 from pylint.checkers.utils import ( | 30 from pylint.checkers.utils import ( |
29 PYMETHODS, overrides_a_method, check_messages, is_attr_private, | 31 PYMETHODS, overrides_a_method, check_messages, is_attr_private, |
30 is_attr_protected, node_frame_class, safe_infer) | 32 is_attr_protected, node_frame_class, safe_infer, is_builtin_object, |
| 33 decorated_with_property) |
| 34 import six |
31 | 35 |
32 if sys.version_info >= (3, 0): | 36 if sys.version_info >= (3, 0): |
33 NEXT_METHOD = '__next__' | 37 NEXT_METHOD = '__next__' |
34 else: | 38 else: |
35 NEXT_METHOD = 'next' | 39 NEXT_METHOD = 'next' |
36 ITER_METHODS = ('__iter__', '__getitem__') | 40 ITER_METHODS = ('__iter__', '__getitem__') |
37 | 41 |
| 42 def _called_in_methods(func, klass, methods): |
| 43 """ Check if the func was called in any of the given methods, |
| 44 belonging to the *klass*. Returns True if so, False otherwise. |
| 45 """ |
| 46 if not isinstance(func, astroid.Function): |
| 47 return False |
| 48 for method in methods: |
| 49 try: |
| 50 infered = klass.getattr(method) |
| 51 except astroid.NotFoundError: |
| 52 continue |
| 53 for infer_method in infered: |
| 54 for callfunc in infer_method.nodes_of_class(astroid.CallFunc): |
| 55 try: |
| 56 bound = next(callfunc.func.infer()) |
| 57 except (astroid.InferenceError, StopIteration): |
| 58 continue |
| 59 if not isinstance(bound, astroid.BoundMethod): |
| 60 continue |
| 61 func_obj = bound._proxied |
| 62 if isinstance(func_obj, astroid.UnboundMethod): |
| 63 func_obj = func_obj._proxied |
| 64 if func_obj.name == func.name: |
| 65 return True |
| 66 return False |
| 67 |
38 def class_is_abstract(node): | 68 def class_is_abstract(node): |
39 """return true if the given class node should be considered as an abstract | 69 """return true if the given class node should be considered as an abstract |
40 class | 70 class |
41 """ | 71 """ |
42 for method in node.methods(): | 72 for method in node.methods(): |
43 if method.parent.frame() is node: | 73 if method.parent.frame() is node: |
44 if method.is_abstract(pass_is_abstract=False): | 74 if method.is_abstract(pass_is_abstract=False): |
45 return True | 75 return True |
46 return False | 76 return False |
47 | 77 |
| 78 def _is_attribute_property(name, klass): |
| 79 """ Check if the given attribute *name* is a property |
| 80 in the given *klass*. |
| 81 |
| 82 It will look for `property` calls or for functions |
| 83 with the given name, decorated by `property` or `property` |
| 84 subclasses. |
| 85 Returns ``True`` if the name is a property in the given klass, |
| 86 ``False`` otherwise. |
| 87 """ |
| 88 |
| 89 try: |
| 90 attributes = klass.getattr(name) |
| 91 except astroid.NotFoundError: |
| 92 return False |
| 93 property_name = "{0}.property".format(BUILTINS) |
| 94 for attr in attributes: |
| 95 try: |
| 96 infered = next(attr.infer()) |
| 97 except astroid.InferenceError: |
| 98 continue |
| 99 if (isinstance(infered, astroid.Function) and |
| 100 decorated_with_property(infered)): |
| 101 return True |
| 102 if infered.pytype() == property_name: |
| 103 return True |
| 104 return False |
| 105 |
48 | 106 |
49 MSGS = { | 107 MSGS = { |
50 'F0202': ('Unable to check methods signature (%s / %s)', | 108 'F0202': ('Unable to check methods signature (%s / %s)', |
51 'method-check-failed', | 109 'method-check-failed', |
52 'Used when PyLint has been unable to check methods signature \ | 110 'Used when Pylint has been unable to check methods signature \ |
53 compatibility for an unexpected reason. Please report this kind \ | 111 compatibility for an unexpected reason. Please report this kind \ |
54 if you don\'t make sense of it.'), | 112 if you don\'t make sense of it.'), |
55 | 113 |
56 'E0202': ('An attribute defined in %s line %s hides this method', | 114 'E0202': ('An attribute defined in %s line %s hides this method', |
57 'method-hidden', | 115 'method-hidden', |
58 'Used when a class defines a method which is hidden by an ' | 116 'Used when a class defines a method which is hidden by an ' |
59 'instance attribute from an ancestor class or set by some ' | 117 'instance attribute from an ancestor class or set by some ' |
60 'client code.'), | 118 'client code.'), |
61 'E0203': ('Access to member %r before its definition line %s', | 119 'E0203': ('Access to member %r before its definition line %s', |
62 'access-member-before-definition', | 120 'access-member-before-definition', |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
129 'signature-differs', | 187 'signature-differs', |
130 'Used when a method signature is different than in the \ | 188 'Used when a method signature is different than in the \ |
131 implemented interface or in an overridden method.'), | 189 implemented interface or in an overridden method.'), |
132 'W0223': ('Method %r is abstract in class %r but is not overridden', | 190 'W0223': ('Method %r is abstract in class %r but is not overridden', |
133 'abstract-method', | 191 'abstract-method', |
134 'Used when an abstract method (i.e. raise NotImplementedError) is
\ | 192 'Used when an abstract method (i.e. raise NotImplementedError) is
\ |
135 not overridden in concrete class.' | 193 not overridden in concrete class.' |
136 ), | 194 ), |
137 'F0220': ('failed to resolve interfaces implemented by %s (%s)', # W0224 | 195 'F0220': ('failed to resolve interfaces implemented by %s (%s)', # W0224 |
138 'unresolved-interface', | 196 'unresolved-interface', |
139 'Used when a PyLint as failed to find interfaces implemented by \ | 197 'Used when a Pylint as failed to find interfaces implemented by \ |
140 a class'), | 198 a class'), |
141 | 199 |
142 | 200 |
143 'W0231': ('__init__ method from base class %r is not called', | 201 'W0231': ('__init__ method from base class %r is not called', |
144 'super-init-not-called', | 202 'super-init-not-called', |
145 'Used when an ancestor class method has an __init__ method \ | 203 'Used when an ancestor class method has an __init__ method \ |
146 which is not called by a derived class.'), | 204 which is not called by a derived class.'), |
147 'W0232': ('Class has no __init__ method', | 205 'W0232': ('Class has no __init__ method', |
148 'no-init', | 206 'no-init', |
149 'Used when a class has no __init__ method, neither its parent \ | 207 'Used when a class has no __init__ method, neither its parent \ |
(...skipping 15 matching lines...) Expand all Loading... |
165 'only non empty strings', | 223 'only non empty strings', |
166 'invalid-slots-object', | 224 'invalid-slots-object', |
167 'Used when an invalid (non-string) object occurs in __slots__.'), | 225 'Used when an invalid (non-string) object occurs in __slots__.'), |
168 'E0237': ('Assigning to attribute %r not defined in class slots', | 226 'E0237': ('Assigning to attribute %r not defined in class slots', |
169 'assigning-non-slot', | 227 'assigning-non-slot', |
170 'Used when assigning to an attribute not defined ' | 228 'Used when assigning to an attribute not defined ' |
171 'in the class slots.'), | 229 'in the class slots.'), |
172 'E0238': ('Invalid __slots__ object', | 230 'E0238': ('Invalid __slots__ object', |
173 'invalid-slots', | 231 'invalid-slots', |
174 'Used when an invalid __slots__ is found in class. ' | 232 'Used when an invalid __slots__ is found in class. ' |
175 'Only a string, an iterable or a sequence is permitted.') | 233 'Only a string, an iterable or a sequence is permitted.'), |
| 234 'E0239': ('Inheriting %r, which is not a class.', |
| 235 'inherit-non-class', |
| 236 'Used when a class inherits from something which is not a ' |
| 237 'class.'), |
176 | 238 |
177 | 239 |
178 } | 240 } |
179 | 241 |
180 | 242 |
181 class ClassChecker(BaseChecker): | 243 class ClassChecker(BaseChecker): |
182 """checks for : | 244 """checks for : |
183 * methods without self as first argument | 245 * methods without self as first argument |
184 * overridden methods signature | 246 * overridden methods signature |
185 * access only to existent members via self | 247 * access only to existent members via self |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
227 'help' : 'List of valid names for the first argument in \ | 289 'help' : 'List of valid names for the first argument in \ |
228 a class method.'} | 290 a class method.'} |
229 ), | 291 ), |
230 ('valid-metaclass-classmethod-first-arg', | 292 ('valid-metaclass-classmethod-first-arg', |
231 {'default' : ('mcs',), | 293 {'default' : ('mcs',), |
232 'type' : 'csv', | 294 'type' : 'csv', |
233 'metavar' : '<argument names>', | 295 'metavar' : '<argument names>', |
234 'help' : 'List of valid names for the first argument in \ | 296 'help' : 'List of valid names for the first argument in \ |
235 a metaclass class method.'} | 297 a metaclass class method.'} |
236 ), | 298 ), |
237 ) | 299 ('exclude-protected', |
| 300 { |
| 301 'default': ( |
| 302 # namedtuple public API. |
| 303 '_asdict', '_fields', '_replace', '_source', '_make'), |
| 304 'type': 'csv', |
| 305 'metavar': '<protected access exclusions>', |
| 306 'help': ('List of member names, which should be excluded ' |
| 307 'from the protected access warning.')} |
| 308 )) |
238 | 309 |
239 def __init__(self, linter=None): | 310 def __init__(self, linter=None): |
240 BaseChecker.__init__(self, linter) | 311 BaseChecker.__init__(self, linter) |
241 self._accessed = [] | 312 self._accessed = [] |
242 self._first_attrs = [] | 313 self._first_attrs = [] |
243 self._meth_could_be_func = None | 314 self._meth_could_be_func = None |
244 | 315 |
245 def visit_class(self, node): | 316 def visit_class(self, node): |
246 """init visit variable _accessed and check interfaces | 317 """init visit variable _accessed and check interfaces |
247 """ | 318 """ |
248 self._accessed.append({}) | 319 self._accessed.append(defaultdict(list)) |
249 self._check_bases_classes(node) | 320 self._check_bases_classes(node) |
250 self._check_interfaces(node) | 321 self._check_interfaces(node) |
251 # if not an interface, exception, metaclass | 322 # if not an interface, exception, metaclass |
252 if node.type == 'class': | 323 if node.type == 'class': |
253 try: | 324 try: |
254 node.local_attr('__init__') | 325 node.local_attr('__init__') |
255 except astroid.NotFoundError: | 326 except astroid.NotFoundError: |
256 self.add_message('no-init', args=node, node=node) | 327 self.add_message('no-init', args=node, node=node) |
257 self._check_slots(node) | 328 self._check_slots(node) |
| 329 self._check_proper_bases(node) |
258 | 330 |
259 @check_messages('access-member-before-definition', 'attribute-defined-outsid
e-init') | 331 @check_messages('inherit-non-class') |
| 332 def _check_proper_bases(self, node): |
| 333 """ |
| 334 Detect that a class inherits something which is not |
| 335 a class or a type. |
| 336 """ |
| 337 for base in node.bases: |
| 338 ancestor = safe_infer(base) |
| 339 if ancestor in (YES, None): |
| 340 continue |
| 341 if (isinstance(ancestor, astroid.Instance) and |
| 342 ancestor.is_subtype_of('%s.type' % (BUILTINS,))): |
| 343 continue |
| 344 if not isinstance(ancestor, astroid.Class): |
| 345 self.add_message('inherit-non-class', |
| 346 args=base.as_string(), node=node) |
| 347 |
| 348 @check_messages('access-member-before-definition', |
| 349 'attribute-defined-outside-init') |
260 def leave_class(self, cnode): | 350 def leave_class(self, cnode): |
261 """close a class node: | 351 """close a class node: |
262 check that instance attributes are defined in __init__ and check | 352 check that instance attributes are defined in __init__ and check |
263 access to existent members | 353 access to existent members |
264 """ | 354 """ |
265 # check access to existent members on non metaclass classes | 355 # check access to existent members on non metaclass classes |
266 accessed = self._accessed.pop() | 356 accessed = self._accessed.pop() |
267 if cnode.type != 'metaclass': | 357 if cnode.type != 'metaclass': |
268 self._check_accessed_members(cnode, accessed) | 358 self._check_accessed_members(cnode, accessed) |
269 # checks attributes are defined in an allowed method such as __init__ | 359 # checks attributes are defined in an allowed method such as __init__ |
270 if not self.linter.is_message_enabled('attribute-defined-outside-init'): | 360 if not self.linter.is_message_enabled('attribute-defined-outside-init'): |
271 return | 361 return |
272 defining_methods = self.config.defining_attr_methods | 362 defining_methods = self.config.defining_attr_methods |
273 current_module = cnode.root() | 363 current_module = cnode.root() |
274 for attr, nodes in cnode.instance_attrs.iteritems(): | 364 for attr, nodes in six.iteritems(cnode.instance_attrs): |
275 # skip nodes which are not in the current module and it may screw up | 365 # skip nodes which are not in the current module and it may screw up |
276 # the output, while it's not worth it | 366 # the output, while it's not worth it |
277 nodes = [n for n in nodes if not | 367 nodes = [n for n in nodes if not |
278 isinstance(n.statement(), (astroid.Delete, astroid.AugAssig
n)) | 368 isinstance(n.statement(), (astroid.Delete, astroid.AugAssig
n)) |
279 and n.root() is current_module] | 369 and n.root() is current_module] |
280 if not nodes: | 370 if not nodes: |
281 continue # error detected by typechecking | 371 continue # error detected by typechecking |
282 # check if any method attr is defined in is a defining method | 372 # check if any method attr is defined in is a defining method |
283 if any(node.frame().name in defining_methods | 373 if any(node.frame().name in defining_methods |
284 for node in nodes): | 374 for node in nodes): |
285 continue | 375 continue |
286 | 376 |
287 # check attribute is defined in a parent's __init__ | 377 # check attribute is defined in a parent's __init__ |
288 for parent in cnode.instance_attr_ancestors(attr): | 378 for parent in cnode.instance_attr_ancestors(attr): |
289 attr_defined = False | 379 attr_defined = False |
290 # check if any parent method attr is defined in is a defining me
thod | 380 # check if any parent method attr is defined in is a defining me
thod |
291 for node in parent.instance_attrs[attr]: | 381 for node in parent.instance_attrs[attr]: |
292 if node.frame().name in defining_methods: | 382 if node.frame().name in defining_methods: |
293 attr_defined = True | 383 attr_defined = True |
294 if attr_defined: | 384 if attr_defined: |
295 # we're done :) | 385 # we're done :) |
296 break | 386 break |
297 else: | 387 else: |
298 # check attribute is defined as a class attribute | 388 # check attribute is defined as a class attribute |
299 try: | 389 try: |
300 cnode.local_attr(attr) | 390 cnode.local_attr(attr) |
301 except astroid.NotFoundError: | 391 except astroid.NotFoundError: |
302 for node in nodes: | 392 for node in nodes: |
303 if node.frame().name not in defining_methods: | 393 if node.frame().name not in defining_methods: |
| 394 # If the attribute was set by a callfunc in any |
| 395 # of the defining methods, then don't emit |
| 396 # the warning. |
| 397 if _called_in_methods(node.frame(), cnode, |
| 398 defining_methods): |
| 399 continue |
304 self.add_message('attribute-defined-outside-init', | 400 self.add_message('attribute-defined-outside-init', |
305 args=attr, node=node) | 401 args=attr, node=node) |
306 | 402 |
307 def visit_function(self, node): | 403 def visit_function(self, node): |
308 """check method arguments, overriding""" | 404 """check method arguments, overriding""" |
309 # ignore actual functions | 405 # ignore actual functions |
310 if not node.is_method(): | 406 if not node.is_method(): |
311 return | 407 return |
312 klass = node.parent.frame() | 408 klass = node.parent.frame() |
313 self._meth_could_be_func = True | 409 self._meth_could_be_func = True |
(...skipping 27 matching lines...) Expand all Loading... |
341 # an attribute error, anyway not hiding the function | 437 # an attribute error, anyway not hiding the function |
342 return | 438 return |
343 # check if the method is hidden by an attribute | 439 # check if the method is hidden by an attribute |
344 try: | 440 try: |
345 overridden = klass.instance_attr(node.name)[0] # XXX | 441 overridden = klass.instance_attr(node.name)[0] # XXX |
346 overridden_frame = overridden.frame() | 442 overridden_frame = overridden.frame() |
347 if (isinstance(overridden_frame, astroid.Function) | 443 if (isinstance(overridden_frame, astroid.Function) |
348 and overridden_frame.type == 'method'): | 444 and overridden_frame.type == 'method'): |
349 overridden_frame = overridden_frame.parent.frame() | 445 overridden_frame = overridden_frame.parent.frame() |
350 if (isinstance(overridden_frame, Class) | 446 if (isinstance(overridden_frame, Class) |
351 and klass._is_subtype_of(overridden_frame.qname())): | 447 and klass.is_subtype_of(overridden_frame.qname())): |
352 args = (overridden.root().name, overridden.fromlineno) | 448 args = (overridden.root().name, overridden.fromlineno) |
353 self.add_message('method-hidden', args=args, node=node) | 449 self.add_message('method-hidden', args=args, node=node) |
354 except astroid.NotFoundError: | 450 except astroid.NotFoundError: |
355 pass | 451 pass |
356 | 452 |
357 # check non-iterators in __iter__ | 453 # check non-iterators in __iter__ |
358 if node.name == '__iter__': | 454 if node.name == '__iter__': |
359 self._check_iter(node) | 455 self._check_iter(node) |
360 elif node.name == '__exit__': | 456 elif node.name == '__exit__': |
361 self._check_exit(node) | 457 self._check_exit(node) |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
459 | 555 |
460 def visit_getattr(self, node): | 556 def visit_getattr(self, node): |
461 """check if the getattr is an access to a class member | 557 """check if the getattr is an access to a class member |
462 if so, register it. Also check for access to protected | 558 if so, register it. Also check for access to protected |
463 class member from outside its class (but ignore __special__ | 559 class member from outside its class (but ignore __special__ |
464 methods) | 560 methods) |
465 """ | 561 """ |
466 attrname = node.attrname | 562 attrname = node.attrname |
467 # Check self | 563 # Check self |
468 if self.is_first_attr(node): | 564 if self.is_first_attr(node): |
469 self._accessed[-1].setdefault(attrname, []).append(node) | 565 self._accessed[-1][attrname].append(node) |
470 return | 566 return |
471 if not self.linter.is_message_enabled('protected-access'): | 567 if not self.linter.is_message_enabled('protected-access'): |
472 return | 568 return |
473 | 569 |
474 self._check_protected_attribute_access(node) | 570 self._check_protected_attribute_access(node) |
475 | 571 |
476 def visit_assattr(self, node): | 572 def visit_assattr(self, node): |
477 if isinstance(node.ass_type(), astroid.AugAssign) and self.is_first_attr
(node): | 573 if isinstance(node.ass_type(), astroid.AugAssign) and self.is_first_attr
(node): |
478 self._accessed[-1].setdefault(node.attrname, []).append(node) | 574 self._accessed[-1][node.attrname].append(node) |
479 self._check_in_slots(node) | 575 self._check_in_slots(node) |
480 | 576 |
481 def _check_in_slots(self, node): | 577 def _check_in_slots(self, node): |
482 """ Check that the given assattr node | 578 """ Check that the given assattr node |
483 is defined in the class slots. | 579 is defined in the class slots. |
484 """ | 580 """ |
485 infered = safe_infer(node.expr) | 581 infered = safe_infer(node.expr) |
486 if infered and isinstance(infered, Instance): | 582 if infered and isinstance(infered, Instance): |
487 klass = infered._proxied | 583 klass = infered._proxied |
488 if '__slots__' not in klass.locals or not klass.newstyle: | 584 if '__slots__' not in klass.locals or not klass.newstyle: |
489 return | 585 return |
490 | 586 |
491 slots = klass.slots() | 587 slots = klass.slots() |
492 # If any ancestor doesn't use slots, the slots | 588 # If any ancestor doesn't use slots, the slots |
493 # defined for this class are superfluous. | 589 # defined for this class are superfluous. |
494 if any('__slots__' not in ancestor.locals and | 590 if any('__slots__' not in ancestor.locals and |
495 ancestor.name != 'object' | 591 ancestor.name != 'object' |
496 for ancestor in klass.ancestors()): | 592 for ancestor in klass.ancestors()): |
497 return | 593 return |
498 | 594 |
499 if not any(slot.value == node.attrname for slot in slots): | 595 if not any(slot.value == node.attrname for slot in slots): |
500 # If we have a '__dict__' in slots, then | 596 # If we have a '__dict__' in slots, then |
501 # assigning any name is valid. | 597 # assigning any name is valid. |
502 if not any(slot.value == '__dict__' for slot in slots): | 598 if not any(slot.value == '__dict__' for slot in slots): |
| 599 if _is_attribute_property(node.attrname, klass): |
| 600 # Properties circumvent the slots mechanism, |
| 601 # so we should not emit a warning for them. |
| 602 return |
503 self.add_message('assigning-non-slot', | 603 self.add_message('assigning-non-slot', |
504 args=(node.attrname, ), node=node) | 604 args=(node.attrname, ), node=node) |
505 | 605 |
506 @check_messages('protected-access') | 606 @check_messages('protected-access') |
507 def visit_assign(self, assign_node): | 607 def visit_assign(self, assign_node): |
508 node = assign_node.targets[0] | 608 node = assign_node.targets[0] |
509 if not isinstance(node, AssAttr): | 609 if not isinstance(node, AssAttr): |
510 return | 610 return |
511 | 611 |
512 if self.is_first_attr(node): | 612 if self.is_first_attr(node): |
513 return | 613 return |
514 | 614 |
515 self._check_protected_attribute_access(node) | 615 self._check_protected_attribute_access(node) |
516 | 616 |
517 def _check_protected_attribute_access(self, node): | 617 def _check_protected_attribute_access(self, node): |
518 '''Given an attribute access node (set or get), check if attribute | 618 '''Given an attribute access node (set or get), check if attribute |
519 access is legitimate. Call _check_first_attr with node before calling | 619 access is legitimate. Call _check_first_attr with node before calling |
520 this method. Valid cases are: | 620 this method. Valid cases are: |
521 * self._attr in a method or cls._attr in a classmethod. Checked by | 621 * self._attr in a method or cls._attr in a classmethod. Checked by |
522 _check_first_attr. | 622 _check_first_attr. |
523 * Klass._attr inside "Klass" class. | 623 * Klass._attr inside "Klass" class. |
524 * Klass2._attr inside "Klass" class when Klass2 is a base class of | 624 * Klass2._attr inside "Klass" class when Klass2 is a base class of |
525 Klass. | 625 Klass. |
526 ''' | 626 ''' |
527 attrname = node.attrname | 627 attrname = node.attrname |
528 | 628 |
529 if is_attr_protected(attrname): | 629 if (is_attr_protected(attrname) and |
| 630 attrname not in self.config.exclude_protected): |
530 | 631 |
531 klass = node_frame_class(node) | 632 klass = node_frame_class(node) |
532 | 633 |
533 # XXX infer to be more safe and less dirty ?? | 634 # XXX infer to be more safe and less dirty ?? |
534 # in classes, check we are not getting a parent method | 635 # in classes, check we are not getting a parent method |
535 # through the class object or through super | 636 # through the class object or through super |
536 callee = node.expr.as_string() | 637 callee = node.expr.as_string() |
537 | 638 |
538 # We are not in a class, no remaining valid case | 639 # We are not in a class, no remaining valid case |
539 if klass is None: | 640 if klass is None: |
540 self.add_message('protected-access', node=node, args=attrname) | 641 self.add_message('protected-access', node=node, args=attrname) |
541 return | 642 return |
542 | 643 |
543 # If the expression begins with a call to super, that's ok. | 644 # If the expression begins with a call to super, that's ok. |
544 if isinstance(node.expr, astroid.CallFunc) and \ | 645 if isinstance(node.expr, astroid.CallFunc) and \ |
545 isinstance(node.expr.func, astroid.Name) and \ | 646 isinstance(node.expr.func, astroid.Name) and \ |
546 node.expr.func.name == 'super': | 647 node.expr.func.name == 'super': |
547 return | 648 return |
548 | 649 |
549 # We are in a class, one remaining valid cases, Klass._attr inside | 650 # We are in a class, one remaining valid cases, Klass._attr inside |
550 # Klass | 651 # Klass |
551 if not (callee == klass.name or callee in klass.basenames): | 652 if not (callee == klass.name or callee in klass.basenames): |
| 653 # Detect property assignments in the body of the class. |
| 654 # This is acceptable: |
| 655 # |
| 656 # class A: |
| 657 # b = property(lambda: self._b) |
| 658 |
| 659 stmt = node.parent.statement() |
| 660 try: |
| 661 if (isinstance(stmt, astroid.Assign) and |
| 662 (stmt in klass.body or klass.parent_of(stmt)) and |
| 663 isinstance(stmt.value, astroid.CallFunc) and |
| 664 isinstance(stmt.value.func, astroid.Name) and |
| 665 stmt.value.func.name == 'property' and |
| 666 is_builtin_object(next(stmt.value.func.infer(), None
))): |
| 667 return |
| 668 except astroid.InferenceError: |
| 669 pass |
552 self.add_message('protected-access', node=node, args=attrname) | 670 self.add_message('protected-access', node=node, args=attrname) |
553 | 671 |
554 def visit_name(self, node): | 672 def visit_name(self, node): |
555 """check if the name handle an access to a class member | 673 """check if the name handle an access to a class member |
556 if so, register it | 674 if so, register it |
557 """ | 675 """ |
558 if self._first_attrs and (node.name == self._first_attrs[-1] or | 676 if self._first_attrs and (node.name == self._first_attrs[-1] or |
559 not self._first_attrs[-1]): | 677 not self._first_attrs[-1]): |
560 self._meth_could_be_func = False | 678 self._meth_could_be_func = False |
561 | 679 |
562 def _check_accessed_members(self, node, accessed): | 680 def _check_accessed_members(self, node, accessed): |
563 """check that accessed members are defined""" | 681 """check that accessed members are defined""" |
564 # XXX refactor, probably much simpler now that E0201 is in type checker | 682 # XXX refactor, probably much simpler now that E0201 is in type checker |
565 for attr, nodes in accessed.iteritems(): | 683 for attr, nodes in six.iteritems(accessed): |
566 # deactivate "except doesn't do anything", that's expected | 684 # deactivate "except doesn't do anything", that's expected |
567 # pylint: disable=W0704 | 685 # pylint: disable=W0704 |
568 try: | 686 try: |
569 # is it a class attribute ? | 687 # is it a class attribute ? |
570 node.local_attr(attr) | 688 node.local_attr(attr) |
571 # yes, stop here | 689 # yes, stop here |
572 continue | 690 continue |
573 except astroid.NotFoundError: | 691 except astroid.NotFoundError: |
574 pass | 692 pass |
575 # is it an instance attribute of a parent class ? | 693 # is it an instance attribute of a parent class ? |
576 try: | 694 try: |
577 node.instance_attr_ancestors(attr).next() | 695 next(node.instance_attr_ancestors(attr)) |
578 # yes, stop here | 696 # yes, stop here |
579 continue | 697 continue |
580 except StopIteration: | 698 except StopIteration: |
581 pass | 699 pass |
582 # is it an instance attribute ? | 700 # is it an instance attribute ? |
583 try: | 701 try: |
584 defstmts = node.instance_attr(attr) | 702 defstmts = node.instance_attr(attr) |
585 except astroid.NotFoundError: | 703 except astroid.NotFoundError: |
586 pass | 704 pass |
587 else: | 705 else: |
(...skipping 11 matching lines...) Expand all Loading... |
599 # if there are still more than one, don't attempt to be smarter | 717 # if there are still more than one, don't attempt to be smarter |
600 # than we can be | 718 # than we can be |
601 if len(defstmts) == 1: | 719 if len(defstmts) == 1: |
602 defstmt = defstmts[0] | 720 defstmt = defstmts[0] |
603 # check that if the node is accessed in the same method as | 721 # check that if the node is accessed in the same method as |
604 # it's defined, it's accessed after the initial assignment | 722 # it's defined, it's accessed after the initial assignment |
605 frame = defstmt.frame() | 723 frame = defstmt.frame() |
606 lno = defstmt.fromlineno | 724 lno = defstmt.fromlineno |
607 for _node in nodes: | 725 for _node in nodes: |
608 if _node.frame() is frame and _node.fromlineno < lno \ | 726 if _node.frame() is frame and _node.fromlineno < lno \ |
609 and not are_exclusive(_node.statement(), defstmt, ('A
ttributeError', 'Exception', 'BaseException')): | 727 and not are_exclusive(_node.statement(), defstmt, |
| 728 ('AttributeError', 'Exception',
'BaseException')): |
610 self.add_message('access-member-before-definition', | 729 self.add_message('access-member-before-definition', |
611 node=_node, args=(attr, lno)) | 730 node=_node, args=(attr, lno)) |
612 | 731 |
613 def _check_first_arg_for_type(self, node, metaclass=0): | 732 def _check_first_arg_for_type(self, node, metaclass=0): |
614 """check the name of first argument, expect: | 733 """check the name of first argument, expect: |
615 | 734 |
616 * 'self' for a regular method | 735 * 'self' for a regular method |
617 * 'cls' for a class method or a metaclass regular method (actually | 736 * 'cls' for a class method or a metaclass regular method (actually |
618 valid-classmethod-first-arg value) | 737 valid-classmethod-first-arg value) |
619 * 'mcs' for a metaclass class method (actually | 738 * 'mcs' for a metaclass class method (actually |
(...skipping 22 matching lines...) Expand all Loading... |
642 # metaclass __new__ or classmethod | 761 # metaclass __new__ or classmethod |
643 if node.type == 'classmethod': | 762 if node.type == 'classmethod': |
644 self._check_first_arg_config( | 763 self._check_first_arg_config( |
645 first, | 764 first, |
646 self.config.valid_metaclass_classmethod_first_arg, node, | 765 self.config.valid_metaclass_classmethod_first_arg, node, |
647 'bad-mcs-classmethod-argument', node.name) | 766 'bad-mcs-classmethod-argument', node.name) |
648 # metaclass regular method | 767 # metaclass regular method |
649 else: | 768 else: |
650 self._check_first_arg_config( | 769 self._check_first_arg_config( |
651 first, | 770 first, |
652 self.config.valid_classmethod_first_arg, node, 'bad-mcs-meth
od-argument', | 771 self.config.valid_classmethod_first_arg, node, |
| 772 'bad-mcs-method-argument', |
653 node.name) | 773 node.name) |
654 # regular class | 774 # regular class |
655 else: | 775 else: |
656 # class method | 776 # class method |
657 if node.type == 'classmethod': | 777 if node.type == 'classmethod': |
658 self._check_first_arg_config( | 778 self._check_first_arg_config( |
659 first, | 779 first, |
660 self.config.valid_classmethod_first_arg, node, 'bad-classmet
hod-argument', | 780 self.config.valid_classmethod_first_arg, node, |
| 781 'bad-classmethod-argument', |
661 node.name) | 782 node.name) |
662 # regular method without self as argument | 783 # regular method without self as argument |
663 elif first != 'self': | 784 elif first != 'self': |
664 self.add_message('no-self-argument', node=node) | 785 self.add_message('no-self-argument', node=node) |
665 | 786 |
666 def _check_first_arg_config(self, first, config, node, message, | 787 def _check_first_arg_config(self, first, config, node, message, |
667 method_name): | 788 method_name): |
668 if first not in config: | 789 if first not in config: |
669 if len(config) == 1: | 790 if len(config) == 1: |
670 valid = repr(config[0]) | 791 valid = repr(config[0]) |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
712 for imethod in iface.methods(): | 833 for imethod in iface.methods(): |
713 name = imethod.name | 834 name = imethod.name |
714 if name.startswith('_') or name in ignore_iface_methods: | 835 if name.startswith('_') or name in ignore_iface_methods: |
715 # don't check method beginning with an underscore, | 836 # don't check method beginning with an underscore, |
716 # usually belonging to the interface implementation | 837 # usually belonging to the interface implementation |
717 continue | 838 continue |
718 # get class method astroid | 839 # get class method astroid |
719 try: | 840 try: |
720 method = node_method(node, name) | 841 method = node_method(node, name) |
721 except astroid.NotFoundError: | 842 except astroid.NotFoundError: |
722 self.add_message('missing-interface-method', args=(name,
iface.name), | 843 self.add_message('missing-interface-method', |
| 844 args=(name, iface.name), |
723 node=node) | 845 node=node) |
724 continue | 846 continue |
725 # ignore inherited methods | 847 # ignore inherited methods |
726 if method.parent.frame() is not node: | 848 if method.parent.frame() is not node: |
727 continue | 849 continue |
728 # check signature | 850 # check signature |
729 self._check_signature(method, imethod, | 851 self._check_signature(method, imethod, |
730 '%s interface' % iface.name) | 852 '%s interface' % iface.name) |
731 except astroid.InferenceError: | 853 except astroid.InferenceError: |
732 if e0221_hack[0]: | 854 if e0221_hack[0]: |
(...skipping 22 matching lines...) Expand all Loading... |
755 expr = stmt.func | 877 expr = stmt.func |
756 if not isinstance(expr, astroid.Getattr) \ | 878 if not isinstance(expr, astroid.Getattr) \ |
757 or expr.attrname != '__init__': | 879 or expr.attrname != '__init__': |
758 continue | 880 continue |
759 # skip the test if using super | 881 # skip the test if using super |
760 if isinstance(expr.expr, astroid.CallFunc) and \ | 882 if isinstance(expr.expr, astroid.CallFunc) and \ |
761 isinstance(expr.expr.func, astroid.Name) and \ | 883 isinstance(expr.expr.func, astroid.Name) and \ |
762 expr.expr.func.name == 'super': | 884 expr.expr.func.name == 'super': |
763 return | 885 return |
764 try: | 886 try: |
765 klass = expr.expr.infer().next() | 887 klass = next(expr.expr.infer()) |
766 if klass is YES: | 888 if klass is YES: |
767 continue | 889 continue |
| 890 # The infered klass can be super(), which was |
| 891 # assigned to a variable and the `__init__` was called later. |
| 892 # |
| 893 # base = super() |
| 894 # base.__init__(...) |
| 895 |
| 896 if (isinstance(klass, astroid.Instance) and |
| 897 isinstance(klass._proxied, astroid.Class) and |
| 898 is_builtin_object(klass._proxied) and |
| 899 klass._proxied.name == 'super'): |
| 900 return |
768 try: | 901 try: |
769 del not_called_yet[klass] | 902 del not_called_yet[klass] |
770 except KeyError: | 903 except KeyError: |
771 if klass not in to_call: | 904 if klass not in to_call: |
772 self.add_message('non-parent-init-called', node=expr, ar
gs=klass.name) | 905 self.add_message('non-parent-init-called', |
| 906 node=expr, args=klass.name) |
773 except astroid.InferenceError: | 907 except astroid.InferenceError: |
774 continue | 908 continue |
775 for klass, method in not_called_yet.iteritems(): | 909 for klass, method in six.iteritems(not_called_yet): |
776 if klass.name == 'object' or method.parent.name == 'object': | 910 if klass.name == 'object' or method.parent.name == 'object': |
777 continue | 911 continue |
778 self.add_message('super-init-not-called', args=klass.name, node=node
) | 912 self.add_message('super-init-not-called', args=klass.name, node=node
) |
779 | 913 |
780 def _check_signature(self, method1, refmethod, class_type): | 914 def _check_signature(self, method1, refmethod, class_type): |
781 """check that the signature of the two given methods match | 915 """check that the signature of the two given methods match |
782 | 916 |
783 class_type is in 'class', 'interface' | 917 class_type is in 'class', 'interface' |
784 """ | 918 """ |
785 if not (isinstance(method1, astroid.Function) | 919 if not (isinstance(method1, astroid.Function) |
786 and isinstance(refmethod, astroid.Function)): | 920 and isinstance(refmethod, astroid.Function)): |
787 self.add_message('method-check-failed', args=(method1, refmethod), n
ode=method1) | 921 self.add_message('method-check-failed', |
| 922 args=(method1, refmethod), node=method1) |
788 return | 923 return |
789 # don't care about functions with unknown argument (builtins) | 924 # don't care about functions with unknown argument (builtins) |
790 if method1.args.args is None or refmethod.args.args is None: | 925 if method1.args.args is None or refmethod.args.args is None: |
791 return | 926 return |
792 # if we use *args, **kwargs, skip the below checks | 927 # if we use *args, **kwargs, skip the below checks |
793 if method1.args.vararg or method1.args.kwarg: | 928 if method1.args.vararg or method1.args.kwarg: |
794 return | 929 return |
795 if is_attr_private(method1.name): | 930 if is_attr_private(method1.name): |
796 return | 931 return |
797 if len(method1.args.args) != len(refmethod.args.args): | 932 if len(method1.args.args) != len(refmethod.args.args): |
798 self.add_message('arguments-differ', args=class_type, node=method1) | 933 self.add_message('arguments-differ', args=class_type, node=method1) |
799 elif len(method1.args.defaults) < len(refmethod.args.defaults): | 934 elif len(method1.args.defaults) < len(refmethod.args.defaults): |
800 self.add_message('signature-differs', args=class_type, node=method1) | 935 self.add_message('signature-differs', args=class_type, node=method1) |
801 | 936 |
802 def is_first_attr(self, node): | 937 def is_first_attr(self, node): |
803 """Check that attribute lookup name use first attribute variable name | 938 """Check that attribute lookup name use first attribute variable name |
804 (self for method, cls for classmethod and mcs for metaclass). | 939 (self for method, cls for classmethod and mcs for metaclass). |
805 """ | 940 """ |
806 return self._first_attrs and isinstance(node.expr, astroid.Name) and \ | 941 return self._first_attrs and isinstance(node.expr, astroid.Name) and \ |
807 node.expr.name == self._first_attrs[-1] | 942 node.expr.name == self._first_attrs[-1] |
808 | 943 |
809 def _ancestors_to_call(klass_node, method='__init__'): | 944 def _ancestors_to_call(klass_node, method='__init__'): |
810 """return a dictionary where keys are the list of base classes providing | 945 """return a dictionary where keys are the list of base classes providing |
811 the queried method, and so that should/may be called from the method node | 946 the queried method, and so that should/may be called from the method node |
812 """ | 947 """ |
813 to_call = {} | 948 to_call = {} |
814 for base_node in klass_node.ancestors(recurs=False): | 949 for base_node in klass_node.ancestors(recurs=False): |
815 try: | 950 try: |
816 to_call[base_node] = base_node.igetattr(method).next() | 951 to_call[base_node] = next(base_node.igetattr(method)) |
817 except astroid.InferenceError: | 952 except astroid.InferenceError: |
818 continue | 953 continue |
819 return to_call | 954 return to_call |
820 | 955 |
821 | 956 |
822 def node_method(node, method_name): | 957 def node_method(node, method_name): |
823 """get astroid for <method_name> on the given class node, ensuring it | 958 """get astroid for <method_name> on the given class node, ensuring it |
824 is a Function node | 959 is a Function node |
825 """ | 960 """ |
826 for n in node.local_attr(method_name): | 961 for n in node.local_attr(method_name): |
827 if isinstance(n, astroid.Function): | 962 if isinstance(n, astroid.Function): |
828 return n | 963 return n |
829 raise astroid.NotFoundError(method_name) | 964 raise astroid.NotFoundError(method_name) |
830 | 965 |
831 def register(linter): | 966 def register(linter): |
832 """required method to auto register this checker """ | 967 """required method to auto register this checker """ |
833 linter.register_checker(ClassChecker(linter)) | 968 linter.register_checker(ClassChecker(linter)) |
OLD | NEW |