diff options
author | Claudiu Popa <pcmanticore@gmail.com> | 2015-10-30 14:39:34 +0200 |
---|---|---|
committer | Claudiu Popa <pcmanticore@gmail.com> | 2015-10-30 14:39:34 +0200 |
commit | 53596950726cef80898094abe5398d7980059476 (patch) | |
tree | aa0ea2c0a2628b1dfaf4cbddf9f187f9ee18123e /pylint | |
parent | d00bce5417db3baf7adbef12bcbbb909d2d4c372 (diff) | |
parent | ac584cb0f525d5ba86e06925077eb82d270b8a39 (diff) | |
download | pylint-53596950726cef80898094abe5398d7980059476.tar.gz |
Merged in lmedioni/pylint (pull request #295)
check for class methods declared without a decorator
Diffstat (limited to 'pylint')
-rw-r--r-- | pylint/checkers/classes.py | 46 | ||||
-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/bad_staticmethod_argument.py | 2 | ||||
-rw-r--r-- | pylint/test/functional/no_classmethod_decorator.py | 35 | ||||
-rw-r--r-- | pylint/test/functional/no_classmethod_decorator.txt | 3 | ||||
-rw-r--r-- | pylint/test/functional/no_staticmethod_decorator.py | 35 | ||||
-rw-r--r-- | pylint/test/functional/no_staticmethod_decorator.txt | 3 | ||||
-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 |
12 files changed, 131 insertions, 7 deletions
diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py index c65e10c..ae9c727 100644 --- a/pylint/checkers/classes.py +++ b/pylint/checkers/classes.py @@ -253,7 +253,14 @@ 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.'), + 'R0203': ('Consider using a decorator instead of calling staticmethod', + 'no-staticmethod-decorator', + 'Used when a static method is defined without using the decorator ' + 'syntax.'), } @@ -604,17 +611,50 @@ 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', + 'no-staticmethod-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() or staticmethod() + + When a @classmethod or @staticmethod decorator should be used instead. + 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" or "staticmethod" + func = node.value.func + if (not isinstance(func, astroid.Name) or + func.name not in ('classmethod', 'staticmethod')): + return + msg = ('no-classmethod-decorator' if func.name == 'classmethod' else + 'no-staticmethod-decorator') + # assignment must be at a class scope + parent_class = node.scope() + 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.mymethods(): + if method_name == member.name: + self.add_message(msg, 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/bad_staticmethod_argument.py b/pylint/test/functional/bad_staticmethod_argument.py index 0ff5d9b..a71a40e 100644 --- a/pylint/test/functional/bad_staticmethod_argument.py +++ b/pylint/test/functional/bad_staticmethod_argument.py @@ -1,4 +1,4 @@ -# pylint: disable=missing-docstring +# pylint: disable=missing-docstring, no-staticmethod-decorator class Abcd(object): diff --git a/pylint/test/functional/no_classmethod_decorator.py b/pylint/test/functional/no_classmethod_decorator.py new file mode 100644 index 0000000..2f91fde --- /dev/null +++ b/pylint/test/functional/no_classmethod_decorator.py @@ -0,0 +1,35 @@ +"""Checks class methods are declared with a decorator if within the class +scope and if classmethod's argument is a member of the class +""" + +# pylint: disable=too-few-public-methods, using-constant-test, no-self-argument + +class MyClass(object): + """Some class""" + def __init__(self): + pass + + def cmethod(cls): + """class method-to-be""" + cmethod = classmethod(cmethod) # [no-classmethod-decorator] + + if True: + cmethod = classmethod(cmethod) # [no-classmethod-decorator] + + @classmethod + def my_second_method(cls): + """correct class method definition""" + + def other_method(cls): + """some method""" + cmethod2 = classmethod(other_method) # [no-classmethod-decorator] + +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..ba51f0b --- /dev/null +++ b/pylint/test/functional/no_classmethod_decorator.txt @@ -0,0 +1,3 @@ +no-classmethod-decorator:14:MyClass:Consider using a decorator instead of calling classmethod +no-classmethod-decorator:17:MyClass:Consider using a decorator instead of calling classmethod +no-classmethod-decorator:25:MyClass:Consider using a decorator instead of calling classmethod diff --git a/pylint/test/functional/no_staticmethod_decorator.py b/pylint/test/functional/no_staticmethod_decorator.py new file mode 100644 index 0000000..a64cd7c --- /dev/null +++ b/pylint/test/functional/no_staticmethod_decorator.py @@ -0,0 +1,35 @@ +"""Checks static methods are declared with a decorator if within the class +scope and if static method's argument is a member of the class +""" + +# pylint: disable=too-few-public-methods, using-constant-test, no-method-argument + +class MyClass(object): + """Some class""" + def __init__(self): + pass + + def smethod(): + """static method-to-be""" + smethod = staticmethod(smethod) # [no-staticmethod-decorator] + + if True: + smethod = staticmethod(smethod) # [no-staticmethod-decorator] + + @staticmethod + def my_second_method(): + """correct static method definition""" + + def other_method(): + """some method""" + smethod2 = staticmethod(other_method) # [no-staticmethod-decorator] + +def helloworld(): + """says hello""" + print 'hello world' + +MyClass.new_static_method = staticmethod(helloworld) + +class MyOtherClass(object): + """Some other class""" + _make = staticmethod(tuple.__new__) diff --git a/pylint/test/functional/no_staticmethod_decorator.txt b/pylint/test/functional/no_staticmethod_decorator.txt new file mode 100644 index 0000000..c0aea0e --- /dev/null +++ b/pylint/test/functional/no_staticmethod_decorator.txt @@ -0,0 +1,3 @@ +no-staticmethod-decorator:14:MyClass:Consider using a decorator instead of calling staticmethod +no-staticmethod-decorator:17:MyClass:Consider using a decorator instead of calling staticmethod +no-staticmethod-decorator:25:MyClass:Consider using a decorator instead of calling staticmethod 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..2ffd9d1 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, no-staticmethod-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..7457f45 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, no-staticmethod-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 |