diff options
author | Claudiu Popa <cpopa@cloudbasesolutions.com> | 2015-06-14 18:11:32 +0300 |
---|---|---|
committer | Claudiu Popa <cpopa@cloudbasesolutions.com> | 2015-06-14 18:11:32 +0300 |
commit | 693704d003ae8cf3d44fd8d07903a7c08ef46c18 (patch) | |
tree | d08fc215f37700e13377ed43216f2a26b7d5a65b | |
parent | e8c67507047efe66f8eb7b31ccb3963d7f21a97a (diff) | |
download | pylint-693704d003ae8cf3d44fd8d07903a7c08ef46c18.tar.gz |
arguments-differ understand differences between various type of functions.
This patch improves the way how arguments-differ understands the differences
between classmethods, staticmethods and properties. Closes issue #548.
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | pylint/checkers/classes.py | 38 | ||||
-rw-r--r-- | pylint/test/functional/arguments_differ.py | 132 | ||||
-rw-r--r-- | pylint/test/functional/arguments_differ.txt | 3 | ||||
-rw-r--r-- | pylint/test/input/func_noerror_classes_meth_signature.py | 38 |
5 files changed, 171 insertions, 44 deletions
@@ -159,6 +159,10 @@ ChangeLog for Pylint an unary operand is used on something which doesn't support that operation (for instance, using the unary bitwise inversion operator on an instance which doesn't implement __invert__). + + * Take in consideration differences between arguments of various + type of functions (classmethods, staticmethods, properties) + when checking for `arguments-differ`. Closes issue #548. diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py index 4dc00fe..6597602 100644 --- a/pylint/checkers/classes.py +++ b/pylint/checkers/classes.py @@ -25,6 +25,7 @@ from astroid import YES, Instance, are_exclusive, AssAttr, Class from astroid.bases import Generator, BUILTINS from astroid.exceptions import InconsistentMroError, DuplicateBasesError from astroid import objects +from astroid.scoped_nodes import function_to_method from pylint.interfaces import IAstroidChecker from pylint.checkers import BaseChecker @@ -43,6 +44,14 @@ else: NEXT_METHOD = 'next' ITER_METHODS = ('__iter__', '__getitem__') + +def _get_method_args(method): + args = method.args.args + if method.type == 'classmethod': + return len(args) - 1 + return len(args) + + def _called_in_methods(func, klass, methods): """ Check if the func was called in any of the given methods, belonging to the *klass*. Returns True if so, False otherwise. @@ -418,7 +427,7 @@ a metaclass class method.'} continue if not isinstance(meth_node, astroid.Function): continue - self._check_signature(node, meth_node, 'overridden') + self._check_signature(node, meth_node, 'overridden', klass) break if node.decorators: for decorator in node.decorators.nodes: @@ -832,23 +841,40 @@ a metaclass class method.'} continue self.add_message('super-init-not-called', args=klass.name, node=node) - def _check_signature(self, method1, refmethod, class_type): + def _check_signature(self, method1, refmethod, class_type, cls): """check that the signature of the two given methods match - """ + """ if not (isinstance(method1, astroid.Function) and isinstance(refmethod, astroid.Function)): self.add_message('method-check-failed', args=(method1, refmethod), node=method1) return - # don't care about functions with unknown argument (builtins) + + instance = cls.instanciate_class() + method1 = function_to_method(method1, instance) + refmethod = function_to_method(refmethod, instance) + + # Don't care about functions with unknown argument (builtins). if method1.args.args is None or refmethod.args.args is None: return - # if we use *args, **kwargs, skip the below checks + # If we use *args, **kwargs, skip the below checks. if method1.args.vararg or method1.args.kwarg: return + # Ignore private to class methods. if is_attr_private(method1.name): return - if len(method1.args.args) != len(refmethod.args.args): + # Ignore setters, they have an implicit extra argument, + # which shouldn't be taken in consideration. + if method1.decorators: + for decorator in method1.decorators.nodes: + if (isinstance(decorator, astroid.Getattr) and + decorator.attrname == 'setter'): + return + + method1_args = _get_method_args(method1) + refmethod_args = _get_method_args(refmethod) + + if method1_args != refmethod_args: self.add_message('arguments-differ', args=(class_type, method1.name), node=method1) diff --git a/pylint/test/functional/arguments_differ.py b/pylint/test/functional/arguments_differ.py new file mode 100644 index 0000000..7fa2015 --- /dev/null +++ b/pylint/test/functional/arguments_differ.py @@ -0,0 +1,132 @@ +"""Test that we are emitting arguments-differ when the arguments are different."""
+# pylint: disable=missing-docstring, too-few-public-methods
+
+class Parent(object):
+
+ def test(self):
+ pass
+
+
+class Child(Parent):
+
+ def test(self, arg): # [arguments-differ]
+ pass
+
+
+class ParentDefaults(object):
+
+ def test(self, arg=None, barg=None):
+ pass
+
+class ChildDefaults(ParentDefaults):
+
+ def test(self, arg=None): # [arguments-differ]
+ pass
+
+
+class Classmethod(object):
+
+ @classmethod
+ def func(cls, data):
+ return data
+
+ @classmethod
+ def func1(cls):
+ return cls
+
+
+class ClassmethodChild(Classmethod):
+
+ @staticmethod
+ def func(): # [arguments-differ]
+ pass
+
+ @classmethod
+ def func1(cls):
+ return cls()
+
+
+class Builtins(dict):
+ """Ignore for builtins, for which we don't know the number of required args."""
+
+ @classmethod
+ def fromkeys(cls, arg, arg1):
+ pass
+
+
+class Varargs(object):
+
+ def test(self, arg, **kwargs):
+ pass
+
+class VarargsChild(Varargs):
+
+ def test(self, arg):
+ pass
+
+
+class Super(object):
+ def __init__(self):
+ pass
+
+ def __private(self):
+ pass
+
+ def __private2_(self):
+ pass
+
+ def ___private3(self):
+ pass
+
+ def method(self, param):
+ raise NotImplementedError
+
+
+class Sub(Super):
+
+ # pylint: disable=unused-argument
+ def __init__(self, arg):
+ super(Sub, self).__init__()
+
+ def __private(self, arg):
+ pass
+
+ def __private2_(self, arg):
+ pass
+
+ def ___private3(self, arg):
+ pass
+
+ def method(self, param='abc'):
+ pass
+
+
+class Staticmethod(object):
+
+ @staticmethod
+ def func(data):
+ return data
+
+
+class StaticmethodChild(Staticmethod):
+
+ @classmethod
+ def func(cls, data):
+ return data
+
+
+class Property(object):
+
+ @property
+ def close(self):
+ pass
+
+class PropertySetter(Property):
+
+ @property
+ def close(self):
+ pass
+
+ @close.setter
+ def close(self, attr):
+ return attr
diff --git a/pylint/test/functional/arguments_differ.txt b/pylint/test/functional/arguments_differ.txt new file mode 100644 index 0000000..cec3359 --- /dev/null +++ b/pylint/test/functional/arguments_differ.txt @@ -0,0 +1,3 @@ +arguments-differ:12:Child.test:Arguments number differs from overridden 'test' method
+arguments-differ:23:ChildDefaults.test:Arguments number differs from overridden 'test' method
+arguments-differ:41:ClassmethodChild.func:Arguments number differs from overridden 'func' method
\ No newline at end of file diff --git a/pylint/test/input/func_noerror_classes_meth_signature.py b/pylint/test/input/func_noerror_classes_meth_signature.py deleted file mode 100644 index 8a0ad3f..0000000 --- a/pylint/test/input/func_noerror_classes_meth_signature.py +++ /dev/null @@ -1,38 +0,0 @@ -# pylint: disable=C0111,R0903,W0231 -"""#2485: -W0222 "Signature differs from overriden method" false positive -#18772: -no prototype consistency check for mangled methods -""" -__revision__ = 1 -class Super(object): - def __init__(self): - pass - - def __private(self): - pass - - def __private2_(self): - pass - - def ___private3(self): - pass - - def method(self, param): - raise NotImplementedError - -class Sub(Super): - def __init__(self, arg): - pass - - def __private(self, arg): - pass - - def __private2_(self, arg): - pass - - def ___private3(self, arg): - pass - - def method(self, param='abc'): - pass |