summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudiu Popa <pcmanticore@gmail.com>2015-10-30 14:39:34 +0200
committerClaudiu Popa <pcmanticore@gmail.com>2015-10-30 14:39:34 +0200
commit53596950726cef80898094abe5398d7980059476 (patch)
treeaa0ea2c0a2628b1dfaf4cbddf9f187f9ee18123e
parentd00bce5417db3baf7adbef12bcbbb909d2d4c372 (diff)
parentac584cb0f525d5ba86e06925077eb82d270b8a39 (diff)
downloadpylint-53596950726cef80898094abe5398d7980059476.tar.gz
Merged in lmedioni/pylint (pull request #295)
check for class methods declared without a decorator
-rw-r--r--CONTRIBUTORS.txt3
-rw-r--r--pylint/checkers/classes.py46
-rw-r--r--pylint/test/functional/access_to_protected_members.py1
-rw-r--r--pylint/test/functional/access_to_protected_members.txt6
-rw-r--r--pylint/test/functional/bad_staticmethod_argument.py2
-rw-r--r--pylint/test/functional/no_classmethod_decorator.py35
-rw-r--r--pylint/test/functional/no_classmethod_decorator.txt3
-rw-r--r--pylint/test/functional/no_staticmethod_decorator.py35
-rw-r--r--pylint/test/functional/no_staticmethod_decorator.txt3
-rw-r--r--pylint/test/input/func_noerror_classes_protected_member_access.py1
-rw-r--r--pylint/test/input/func_noerror_static_method.py1
-rw-r--r--pylint/test/messages/func_e0203.txt1
-rw-r--r--pylint/test/messages/func_first_arg.txt4
13 files changed, 133 insertions, 8 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 5409b8f..7dc35ab 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -73,4 +73,5 @@ Order doesn't matter (not that much, at least ;)
* Dmitry Pribysh: multiple-imports, not-iterable, not-a-mapping, various patches.
-* Laura Medioni (Logilab): misplaced-comparison-constant
+* Laura Medioni (Logilab, on behalf of the CNES): misplaced-comparison-constant,
+ no-classmethod-decorator, no-staticmethod-decorator
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