| 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 |