summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudiu Popa <cpopa@cloudbasesolutions.com>2015-06-14 18:11:32 +0300
committerClaudiu Popa <cpopa@cloudbasesolutions.com>2015-06-14 18:11:32 +0300
commit693704d003ae8cf3d44fd8d07903a7c08ef46c18 (patch)
treed08fc215f37700e13377ed43216f2a26b7d5a65b
parente8c67507047efe66f8eb7b31ccb3963d7f21a97a (diff)
downloadpylint-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--ChangeLog4
-rw-r--r--pylint/checkers/classes.py38
-rw-r--r--pylint/test/functional/arguments_differ.py132
-rw-r--r--pylint/test/functional/arguments_differ.txt3
-rw-r--r--pylint/test/input/func_noerror_classes_meth_signature.py38
5 files changed, 171 insertions, 44 deletions
diff --git a/ChangeLog b/ChangeLog
index e6204fc..5a6610f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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