summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcpopa <devnull@localhost>2014-08-14 15:29:44 +0300
committercpopa <devnull@localhost>2014-08-14 15:29:44 +0300
commit86a9ed29402d9396bce7eb1a6dff8dba6e941e12 (patch)
tree5145370e7b8690e5ebda6bfaee7edd221f36bce2
parentc407bc2e3218a6cab9d8c6eaf371eaa6fa7d8f61 (diff)
downloadpylint-86a9ed29402d9396bce7eb1a6dff8dba6e941e12.tar.gz
Look in the metaclass, if defined, for members not found in the current class. Closes issue #306.
-rw-r--r--ChangeLog3
-rw-r--r--checkers/typecheck.py14
-rw-r--r--test/functional/class_members.py51
-rw-r--r--test/functional/class_members.txt6
-rw-r--r--test/input/func_class_members.py32
-rw-r--r--test/messages/func_class_members.txt4
6 files changed, 74 insertions, 36 deletions
diff --git a/ChangeLog b/ChangeLog
index d533162..7b70fa7 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -59,6 +59,9 @@ ChangeLog for Pylint
* Don't emit 'unused-import' when a special object is imported
(__all__, __doc__ etc.). Closes issue #309.
+
+ * Look in the metaclass, if defined, for members not found in the current
+ class. Closes issue #306.
2014-07-26 -- 1.3.0
diff --git a/checkers/typecheck.py b/checkers/typecheck.py
index 537a220..a8851a2 100644
--- a/checkers/typecheck.py
+++ b/checkers/typecheck.py
@@ -249,6 +249,20 @@ accessed. Python regular expressions are accepted.'}
# explicit skipping of module member access
if owner.root().name in self.config.ignored_modules:
continue
+ if isinstance(owner, astroid.Class):
+ # Look up in the metaclass only if the owner is itself
+ # a class.
+ # TODO: getattr doesn't return by default members
+ # from the metaclass, because handling various cases
+ # of methods accessible from the metaclass itself
+ # and/or subclasses only is too complicated for little to
+ # no benefit.
+ metaclass = owner.metaclass()
+ try:
+ if metaclass and metaclass.getattr(node.attrname):
+ continue
+ except NotFoundError:
+ pass
missingattr.add((owner, name))
continue
# stop on the first found
diff --git a/test/functional/class_members.py b/test/functional/class_members.py
new file mode 100644
index 0000000..d60458b
--- /dev/null
+++ b/test/functional/class_members.py
@@ -0,0 +1,51 @@
+""" Various tests for class members access. """
+# pylint: disable=R0903
+
+class MyClass(object):
+ """class docstring"""
+
+ def __init__(self):
+ """init"""
+ self.correct = 1
+
+ def test(self):
+ """test"""
+ self.correct += 2
+ self.incorrect += 2 # [no-member]
+ del self.havenot # [no-member]
+ self.nonexistent1.truc() # [no-member]
+ self.nonexistent2[1] = 'hehe' # [no-member]
+
+class XYZMixin(object):
+ """access to undefined members should be ignored in mixin classes by
+ default
+ """
+ def __init__(self):
+ print self.nonexistent
+
+
+class NewClass(object):
+ """use object.__setattr__"""
+ def __init__(self):
+ self.__setattr__('toto', 'tutu')
+
+from abc import ABCMeta
+
+class TestMetaclass(object):
+ """ Test attribute access for metaclasses. """
+ __metaclass__ = ABCMeta
+
+class Metaclass(type):
+ """ metaclass """
+ @classmethod
+ def test(mcs):
+ """ classmethod """
+
+class UsingMetaclass(object):
+ """ empty """
+ __metaclass__ = Metaclass
+
+TestMetaclass.register(int)
+UsingMetaclass.test()
+TestMetaclass().register(int) # [no-member]
+UsingMetaclass().test() # [no-member]
diff --git a/test/functional/class_members.txt b/test/functional/class_members.txt
new file mode 100644
index 0000000..0cb808f
--- /dev/null
+++ b/test/functional/class_members.txt
@@ -0,0 +1,6 @@
+no-member:14:MyClass.test:Instance of 'MyClass' has no 'incorrect' member
+no-member:15:MyClass.test:Instance of 'MyClass' has no 'havenot' member
+no-member:16:MyClass.test:Instance of 'MyClass' has no 'nonexistent1' member
+no-member:17:MyClass.test:Instance of 'MyClass' has no 'nonexistent2' member
+no-member:48::Instance of 'TestMetaclass' has no 'register' member
+no-member:49::Instance of 'UsingMetaclass' has no 'test' member
diff --git a/test/input/func_class_members.py b/test/input/func_class_members.py
deleted file mode 100644
index ad147e5..0000000
--- a/test/input/func_class_members.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# pylint: disable=R0903
-"""test class members"""
-
-__revision__ = ''
-
-class MyClass(object):
- """class docstring"""
-
- def __init__(self):
- """init"""
- self.correct = 1
-
- def test(self):
- """test"""
- self.correct += 2
- self.incorrect += 2
- del self.havenot
- self.nonexistent1.truc()
- self.nonexistent2[1] = 'hehe'
-
-class XYZMixin(object):
- """access to undefined members should be ignored in mixin classes by
- default
- """
- def __init__(self):
- print self.nonexistent
-
-
-class NewClass(object):
- """use object.__setattr__"""
- def __init__(self):
- self.__setattr__('toto', 'tutu')
diff --git a/test/messages/func_class_members.txt b/test/messages/func_class_members.txt
deleted file mode 100644
index 054e21a..0000000
--- a/test/messages/func_class_members.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-E: 16:MyClass.test: Instance of 'MyClass' has no 'incorrect' member
-E: 17:MyClass.test: Instance of 'MyClass' has no 'havenot' member
-E: 18:MyClass.test: Instance of 'MyClass' has no 'nonexistent1' member
-E: 19:MyClass.test: Instance of 'MyClass' has no 'nonexistent2' member