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 |
(...skipping 12 matching lines...) Expand all Loading... |
23 import astroid | 23 import astroid |
24 from astroid import YES, Instance, are_exclusive, AssAttr, Class | 24 from astroid import YES, Instance, are_exclusive, AssAttr, Class |
25 from astroid.bases import Generator, BUILTINS | 25 from astroid.bases import Generator, BUILTINS |
26 from astroid.inference import InferenceContext | 26 from astroid.inference import InferenceContext |
27 | 27 |
28 from pylint.interfaces import IAstroidChecker | 28 from pylint.interfaces import IAstroidChecker |
29 from pylint.checkers import BaseChecker | 29 from pylint.checkers import BaseChecker |
30 from pylint.checkers.utils import ( | 30 from pylint.checkers.utils import ( |
31 PYMETHODS, overrides_a_method, check_messages, is_attr_private, | 31 PYMETHODS, overrides_a_method, check_messages, is_attr_private, |
32 is_attr_protected, node_frame_class, safe_infer, is_builtin_object, | 32 is_attr_protected, node_frame_class, safe_infer, is_builtin_object, |
33 decorated_with_property) | 33 decorated_with_property, unimplemented_abstract_methods) |
34 import six | 34 import six |
35 | 35 |
36 if sys.version_info >= (3, 0): | 36 if sys.version_info >= (3, 0): |
37 NEXT_METHOD = '__next__' | 37 NEXT_METHOD = '__next__' |
38 else: | 38 else: |
39 NEXT_METHOD = 'next' | 39 NEXT_METHOD = 'next' |
40 ITER_METHODS = ('__iter__', '__getitem__') | 40 ITER_METHODS = ('__iter__', '__getitem__') |
41 | 41 |
42 def _called_in_methods(func, klass, methods): | 42 def _called_in_methods(func, klass, methods): |
43 """ Check if the func was called in any of the given methods, | 43 """ Check if the func was called in any of the given methods, |
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
172 ), | 172 ), |
173 | 173 |
174 'E0221': ('Interface resolved to %s is not a class', | 174 'E0221': ('Interface resolved to %s is not a class', |
175 'interface-is-not-class', | 175 'interface-is-not-class', |
176 'Used when a class claims to implement an interface which is not \ | 176 'Used when a class claims to implement an interface which is not \ |
177 a class.'), | 177 a class.'), |
178 'E0222': ('Missing method %r from %s interface', | 178 'E0222': ('Missing method %r from %s interface', |
179 'missing-interface-method', | 179 'missing-interface-method', |
180 'Used when a method declared in an interface is missing from a \ | 180 'Used when a method declared in an interface is missing from a \ |
181 class implementing this interface'), | 181 class implementing this interface'), |
182 'W0221': ('Arguments number differs from %s method', | 182 'W0221': ('Arguments number differs from %s %r method', |
183 'arguments-differ', | 183 'arguments-differ', |
184 'Used when a method has a different number of arguments than in \ | 184 'Used when a method has a different number of arguments than in \ |
185 the implemented interface or in an overridden method.'), | 185 the implemented interface or in an overridden method.'), |
186 'W0222': ('Signature differs from %s method', | 186 'W0222': ('Signature differs from %s %r method', |
187 'signature-differs', | 187 'signature-differs', |
188 'Used when a method signature is different than in the \ | 188 'Used when a method signature is different than in the \ |
189 implemented interface or in an overridden method.'), | 189 implemented interface or in an overridden method.'), |
190 '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', |
191 'abstract-method', | 191 'abstract-method', |
192 'Used when an abstract method (i.e. raise NotImplementedError) is
\ | 192 'Used when an abstract method (i.e. raise NotImplementedError) is
\ |
193 not overridden in concrete class.' | 193 not overridden in concrete class.' |
194 ), | 194 ), |
195 'F0220': ('failed to resolve interfaces implemented by %s (%s)', # W0224 | 195 'F0220': ('failed to resolve interfaces implemented by %s (%s)', # W0224 |
196 'unresolved-interface', | 196 'unresolved-interface', |
(...skipping 292 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
489 try: | 489 try: |
490 self._check_slots_elt(elt) | 490 self._check_slots_elt(elt) |
491 except astroid.InferenceError: | 491 except astroid.InferenceError: |
492 continue | 492 continue |
493 | 493 |
494 def _check_slots_elt(self, elt): | 494 def _check_slots_elt(self, elt): |
495 for infered in elt.infer(): | 495 for infered in elt.infer(): |
496 if infered is YES: | 496 if infered is YES: |
497 continue | 497 continue |
498 if (not isinstance(infered, astroid.Const) or | 498 if (not isinstance(infered, astroid.Const) or |
499 not isinstance(infered.value, str)): | 499 not isinstance(infered.value, six.string_types)): |
500 self.add_message('invalid-slots-object', | 500 self.add_message('invalid-slots-object', |
501 args=infered.as_string(), | 501 args=infered.as_string(), |
502 node=elt) | 502 node=elt) |
503 continue | 503 continue |
504 if not infered.value: | 504 if not infered.value: |
505 self.add_message('invalid-slots-object', | 505 self.add_message('invalid-slots-object', |
506 args=infered.as_string(), | 506 args=infered.as_string(), |
507 node=elt) | 507 node=elt) |
508 | 508 |
509 def _check_iter(self, node): | 509 def _check_iter(self, node): |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
578 """ Check that the given assattr node | 578 """ Check that the given assattr node |
579 is defined in the class slots. | 579 is defined in the class slots. |
580 """ | 580 """ |
581 infered = safe_infer(node.expr) | 581 infered = safe_infer(node.expr) |
582 if infered and isinstance(infered, Instance): | 582 if infered and isinstance(infered, Instance): |
583 klass = infered._proxied | 583 klass = infered._proxied |
584 if '__slots__' not in klass.locals or not klass.newstyle: | 584 if '__slots__' not in klass.locals or not klass.newstyle: |
585 return | 585 return |
586 | 586 |
587 slots = klass.slots() | 587 slots = klass.slots() |
| 588 if slots is None: |
| 589 return |
588 # If any ancestor doesn't use slots, the slots | 590 # If any ancestor doesn't use slots, the slots |
589 # defined for this class are superfluous. | 591 # defined for this class are superfluous. |
590 if any('__slots__' not in ancestor.locals and | 592 if any('__slots__' not in ancestor.locals and |
591 ancestor.name != 'object' | 593 ancestor.name != 'object' |
592 for ancestor in klass.ancestors()): | 594 for ancestor in klass.ancestors()): |
593 return | 595 return |
594 | 596 |
595 if not any(slot.value == node.attrname for slot in slots): | 597 if not any(slot.value == node.attrname for slot in slots): |
596 # If we have a '__dict__' in slots, then | 598 # If we have a '__dict__' in slots, then |
597 # assigning any name is valid. | 599 # assigning any name is valid. |
(...skipping 193 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
791 valid = repr(config[0]) | 793 valid = repr(config[0]) |
792 else: | 794 else: |
793 valid = ', '.join(repr(v) for v in config[:-1]) | 795 valid = ', '.join(repr(v) for v in config[:-1]) |
794 valid = '%s or %r' % (valid, config[-1]) | 796 valid = '%s or %r' % (valid, config[-1]) |
795 self.add_message(message, args=(method_name, valid), node=node) | 797 self.add_message(message, args=(method_name, valid), node=node) |
796 | 798 |
797 def _check_bases_classes(self, node): | 799 def _check_bases_classes(self, node): |
798 """check that the given class node implements abstract methods from | 800 """check that the given class node implements abstract methods from |
799 base classes | 801 base classes |
800 """ | 802 """ |
| 803 def is_abstract(method): |
| 804 return method.is_abstract(pass_is_abstract=False) |
| 805 |
801 # check if this class abstract | 806 # check if this class abstract |
802 if class_is_abstract(node): | 807 if class_is_abstract(node): |
803 return | 808 return |
804 for method in node.methods(): | 809 |
| 810 methods = sorted( |
| 811 unimplemented_abstract_methods(node, is_abstract).items(), |
| 812 key=lambda item: item[0], |
| 813 ) |
| 814 for name, method in methods: |
805 owner = method.parent.frame() | 815 owner = method.parent.frame() |
806 if owner is node: | 816 if owner is node: |
807 continue | 817 continue |
808 # owner is not this class, it must be a parent class | 818 # owner is not this class, it must be a parent class |
809 # check that the ancestor's method is not abstract | 819 # check that the ancestor's method is not abstract |
810 if method.name in node.locals: | 820 if name in node.locals: |
811 # it is redefined as an attribute or with a descriptor | 821 # it is redefined as an attribute or with a descriptor |
812 continue | 822 continue |
813 if method.is_abstract(pass_is_abstract=False): | 823 self.add_message('abstract-method', node=node, |
814 self.add_message('abstract-method', node=node, | 824 args=(name, owner.name)) |
815 args=(method.name, owner.name)) | |
816 | 825 |
817 def _check_interfaces(self, node): | 826 def _check_interfaces(self, node): |
818 """check that the given class node really implements declared | 827 """check that the given class node really implements declared |
819 interfaces | 828 interfaces |
820 """ | 829 """ |
821 e0221_hack = [False] | 830 e0221_hack = [False] |
822 def iface_handler(obj): | 831 def iface_handler(obj): |
823 """filter interface objects, it should be classes""" | 832 """filter interface objects, it should be classes""" |
824 if not isinstance(obj, astroid.Class): | 833 if not isinstance(obj, astroid.Class): |
825 e0221_hack[0] = True | 834 e0221_hack[0] = True |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
923 return | 932 return |
924 # don't care about functions with unknown argument (builtins) | 933 # don't care about functions with unknown argument (builtins) |
925 if method1.args.args is None or refmethod.args.args is None: | 934 if method1.args.args is None or refmethod.args.args is None: |
926 return | 935 return |
927 # if we use *args, **kwargs, skip the below checks | 936 # if we use *args, **kwargs, skip the below checks |
928 if method1.args.vararg or method1.args.kwarg: | 937 if method1.args.vararg or method1.args.kwarg: |
929 return | 938 return |
930 if is_attr_private(method1.name): | 939 if is_attr_private(method1.name): |
931 return | 940 return |
932 if len(method1.args.args) != len(refmethod.args.args): | 941 if len(method1.args.args) != len(refmethod.args.args): |
933 self.add_message('arguments-differ', args=class_type, node=method1) | 942 self.add_message('arguments-differ', |
| 943 args=(class_type, method1.name), |
| 944 node=method1) |
934 elif len(method1.args.defaults) < len(refmethod.args.defaults): | 945 elif len(method1.args.defaults) < len(refmethod.args.defaults): |
935 self.add_message('signature-differs', args=class_type, node=method1) | 946 self.add_message('signature-differs', |
| 947 args=(class_type, method1.name), |
| 948 node=method1) |
936 | 949 |
937 def is_first_attr(self, node): | 950 def is_first_attr(self, node): |
938 """Check that attribute lookup name use first attribute variable name | 951 """Check that attribute lookup name use first attribute variable name |
939 (self for method, cls for classmethod and mcs for metaclass). | 952 (self for method, cls for classmethod and mcs for metaclass). |
940 """ | 953 """ |
941 return self._first_attrs and isinstance(node.expr, astroid.Name) and \ | 954 return self._first_attrs and isinstance(node.expr, astroid.Name) and \ |
942 node.expr.name == self._first_attrs[-1] | 955 node.expr.name == self._first_attrs[-1] |
943 | 956 |
944 def _ancestors_to_call(klass_node, method='__init__'): | 957 def _ancestors_to_call(klass_node, method='__init__'): |
945 """return a dictionary where keys are the list of base classes providing | 958 """return a dictionary where keys are the list of base classes providing |
(...skipping 13 matching lines...) Expand all Loading... |
959 is a Function node | 972 is a Function node |
960 """ | 973 """ |
961 for n in node.local_attr(method_name): | 974 for n in node.local_attr(method_name): |
962 if isinstance(n, astroid.Function): | 975 if isinstance(n, astroid.Function): |
963 return n | 976 return n |
964 raise astroid.NotFoundError(method_name) | 977 raise astroid.NotFoundError(method_name) |
965 | 978 |
966 def register(linter): | 979 def register(linter): |
967 """required method to auto register this checker """ | 980 """required method to auto register this checker """ |
968 linter.register_checker(ClassChecker(linter)) | 981 linter.register_checker(ClassChecker(linter)) |
OLD | NEW |