diff options
author | Laura M?dioni <laura.medioni@logilab.fr> | 2015-10-29 09:24:52 +0100 |
---|---|---|
committer | Laura M?dioni <laura.medioni@logilab.fr> | 2015-10-29 09:24:52 +0100 |
commit | e2da1f9f3dbd9a6462e0549da75b37249467afb8 (patch) | |
tree | 94ec39fb735cc02832fe5853460af07b0ea13b30 | |
parent | 486091452ad54d6bac7cc0100c62652e858aa8c8 (diff) | |
download | pylint-e2da1f9f3dbd9a6462e0549da75b37249467afb8.tar.gz |
check for class methods declared without a decorator
related to the issue #675
-rw-r--r-- | pylint/checkers/classes.py | 39 | ||||
-rw-r--r-- | pylint/test/functional/access_to_protected_members.py | 1 | ||||
-rw-r--r-- | pylint/test/functional/access_to_protected_members.txt | 6 | ||||
-rw-r--r-- | pylint/test/functional/no_classmethod_decorator.py | 28 | ||||
-rw-r--r-- | pylint/test/functional/no_classmethod_decorator.txt | 1 | ||||
-rw-r--r-- | pylint/test/input/func_noerror_classes_protected_member_access.py | 1 | ||||
-rw-r--r-- | pylint/test/input/func_noerror_static_method.py | 1 | ||||
-rw-r--r-- | pylint/test/messages/func_e0203.txt | 1 | ||||
-rw-r--r-- | pylint/test/messages/func_first_arg.txt | 4 |
9 files changed, 76 insertions, 6 deletions
diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py index 86ccb19..98a13bf 100644 --- a/pylint/checkers/classes.py +++ b/pylint/checkers/classes.py @@ -262,7 +262,10 @@ MSGS = { 'E0241': ('Duplicate bases for class %r', 'duplicate-bases', 'Used when a class has duplicate bases.'), - + 'R0202': ('Consider using a decorator instead of calling classmethod', + 'no-classmethod-decorator', + 'Used when a class method is defined without using the decorator ' + 'syntax.'), } @@ -613,17 +616,47 @@ a metaclass class method.'} self.add_message('assigning-non-slot', args=(node.attrname, ), node=node) - @check_messages('protected-access') + @check_messages('protected-access', 'no-classmethod-decorator') def visit_assign(self, assign_node): + self._check_classmethod_declaration(assign_node) node = assign_node.targets[0] if not isinstance(node, astroid.AssignAttr): return if self.is_first_attr(node): return - self._check_protected_attribute_access(node) + def _check_classmethod_declaration(self, node): + """checks for uses of classmethod() instead of @classmethod decorator + + A message will be emitted only if the assignment is at a class scope + and only if the classmethod's argument belongs to the class where it + is defined. + `node` is an assign node. + """ + if not isinstance(node.value, astroid.Call): + return + # check the function called is "classmethod" + func = node.value.func + if not isinstance(func, astroid.Name) or not func.name == 'classmethod': + return + # assignment must be at a class scope + parent_class = node.parent + if not isinstance(parent_class, astroid.ClassDef): + return + # Check if the arg passed to classmethod is a class member + classmeth_arg = node.value.args[0] + if not isinstance(classmeth_arg, astroid.Name): + return + method_name = classmeth_arg.name + for member in parent_class.get_children(): + if (isinstance(member, astroid.FunctionDef) and + method_name == member.name): + self.add_message('no-classmethod-decorator', + node=node.targets[0]) + break + def _check_protected_attribute_access(self, node): '''Given an attribute access node (set or get), check if attribute access is legitimate. Call _check_first_attr with node before calling diff --git a/pylint/test/functional/access_to_protected_members.py b/pylint/test/functional/access_to_protected_members.py index 9c92306..fd96baf 100644 --- a/pylint/test/functional/access_to_protected_members.py +++ b/pylint/test/functional/access_to_protected_members.py @@ -1,4 +1,5 @@ # pylint: disable=too-few-public-methods, W0231, print-statement +# pylint: disable=no-classmethod-decorator """Test external access to protected class members.""" from __future__ import print_function diff --git a/pylint/test/functional/access_to_protected_members.txt b/pylint/test/functional/access_to_protected_members.txt index 123c1dd..7ba601b 100644 --- a/pylint/test/functional/access_to_protected_members.txt +++ b/pylint/test/functional/access_to_protected_members.txt @@ -1,5 +1,5 @@ -protected-access:18:MyClass.test:Access to a protected member _haha of a client class -protected-access:40::Access to a protected member _protected of a client class +protected-access:19:MyClass.test:Access to a protected member _haha of a client class protected-access:41::Access to a protected member _protected of a client class -protected-access:42::Access to a protected member _cls_protected of a client class +protected-access:42::Access to a protected member _protected of a client class protected-access:43::Access to a protected member _cls_protected of a client class +protected-access:44::Access to a protected member _cls_protected of a client class diff --git a/pylint/test/functional/no_classmethod_decorator.py b/pylint/test/functional/no_classmethod_decorator.py new file mode 100644 index 0000000..205594d --- /dev/null +++ b/pylint/test/functional/no_classmethod_decorator.py @@ -0,0 +1,28 @@ +"""Checks classes methods are declared with a decorator if whithin the class +scope and if classmethod's argument is a member of the class +""" + +# pylint: disable=too-few-public-methods + +class MyClass(object): + """Some class""" + def __init__(self): + pass + + def cmethod(cls): + """class method-to-be""" + cmethod = classmethod(cmethod) # [no-classmethod-decorator] + + @classmethod + def my_second_method(cls): + """correct class method definition""" + +def helloworld(): + """says hello""" + print 'hello world' + +MyClass.new_class_method = classmethod(helloworld) + +class MyOtherClass(object): + """Some other class""" + _make = classmethod(tuple.__new__) diff --git a/pylint/test/functional/no_classmethod_decorator.txt b/pylint/test/functional/no_classmethod_decorator.txt new file mode 100644 index 0000000..8c1060f --- /dev/null +++ b/pylint/test/functional/no_classmethod_decorator.txt @@ -0,0 +1 @@ +no-classmethod-decorator:14:MyClass:Consider using a decorator instead of calling classmethod diff --git a/pylint/test/input/func_noerror_classes_protected_member_access.py b/pylint/test/input/func_noerror_classes_protected_member_access.py index eeff97d..670e3e8 100644 --- a/pylint/test/input/func_noerror_classes_protected_member_access.py +++ b/pylint/test/input/func_noerror_classes_protected_member_access.py @@ -3,6 +3,7 @@ """ __revision__ = 1 +# pylint: disable=no-classmethod-decorator class A3123(object): """oypuee""" _protected = 1 diff --git a/pylint/test/input/func_noerror_static_method.py b/pylint/test/input/func_noerror_static_method.py index ef21cb9..8a7a0a2 100644 --- a/pylint/test/input/func_noerror_static_method.py +++ b/pylint/test/input/func_noerror_static_method.py @@ -3,6 +3,7 @@ from __future__ import print_function __revision__ = '' +#pylint: disable=no-classmethod-decorator class MyClass(object): """doc """ diff --git a/pylint/test/messages/func_e0203.txt b/pylint/test/messages/func_e0203.txt index c330ffa..96769de 100644 --- a/pylint/test/messages/func_e0203.txt +++ b/pylint/test/messages/func_e0203.txt @@ -1 +1,2 @@ C: 12:Abcd.abcd: Class method abcd should have 'cls' as first argument +R: 15:Abcd: Consider using a decorator instead of calling classmethod diff --git a/pylint/test/messages/func_first_arg.txt b/pylint/test/messages/func_first_arg.txt index 75090dd..ba4efb8 100644 --- a/pylint/test/messages/func_first_arg.txt +++ b/pylint/test/messages/func_first_arg.txt @@ -3,3 +3,7 @@ C: 18:Obj.class2: Class method class2 should have 'cls' as first argument C: 25:Meta.__new__: Metaclass class method __new__ should have 'mcs' as first argument C: 32:Meta.method2: Metaclass method method2 should have 'cls' as first argument C: 40:Meta.class2: Metaclass class method class2 should have 'mcs' as first argument +R: 16:Obj: Consider using a decorator instead of calling classmethod +R: 20:Obj: Consider using a decorator instead of calling classmethod +R: 38:Meta: Consider using a decorator instead of calling classmethod +R: 42:Meta: Consider using a decorator instead of calling classmethod |