diff options
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | pylint/checkers/typecheck.py | 34 | ||||
-rw-r--r-- | pylint/test/unittest_checker_typecheck.py | 41 | ||||
-rw-r--r-- | pylintrc | 2 |
4 files changed, 71 insertions, 10 deletions
@@ -213,6 +213,10 @@ ChangeLog for Pylint * Add a new error, 'nonlocal-and-global', which is emitted when a name is found to be both nonlocal and global in the same scope. Closes issue #581. + + * ignored-classes option can work with qualified names (ignored-classes=optparse.Values) + as well as with Unix pattern matching for ignoring recursively: + ignored-classes=optparse.*. Closes issues #244 and #297. diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index faa8baf..cbbd054 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -17,6 +17,7 @@ """ import collections +import fnmatch import re import shlex import sys @@ -47,6 +48,17 @@ def _unflatten(iterable): yield elem +def _is_ignored_class(owner, name, ignores): + if hasattr(owner, 'qname'): + qname = owner.qname() + else: + qname = '' + ignores = set(ignores) + return any(name == ignore or + qname == ignore or + fnmatch.fnmatch(qname, ignore) for ignore in ignores) + + MSGS = { 'E1101': ('%s %r has no %r member', 'no-member', @@ -115,7 +127,7 @@ SEQUENCE_TYPES = set(['str', 'unicode', 'list', 'tuple', 'bytearray', def _emit_no_member(node, owner, owner_name, attrname, - ignored_modules, ignored_mixins, ignored_classes): + ignored_modules, ignored_mixins): """Try to see if no-member should be emitted for the given owner. The following cases are ignored: @@ -129,8 +141,6 @@ def _emit_no_member(node, owner, owner_name, attrname, """ if node_ignores_exception(node, AttributeError): return False - if owner_name in ignored_classes: - return False # skip None anyway if isinstance(owner, astroid.Const) and owner.value is None: return False @@ -246,11 +256,15 @@ manipulated during runtime and thus existing member attributes cannot be \ deduced by static analysis'}, ), ('ignored-classes', - {'default' : ('SQLObject',), + {'default' : (), 'type' : 'csv', 'metavar' : '<members names>', - 'help' : 'List of classes names for which member attributes \ -should not be checked (useful for classes with attributes dynamically set).'} + 'help' : 'List of classes names for which member attributes ' + 'should not be checked (useful for classes with ' + 'attributes dynamically set). This supports ' + 'three types of names: class names, qualified names, ' + 'and names with Unix pattern matching ' + '(https://docs.python.org/3.5/library/fnmatch.html)'} ), ('zope', @@ -316,7 +330,10 @@ accessed. Python regular expressions are accepted.'} inference_failure = True continue - name = getattr(owner, 'name', 'None') + name = getattr(owner, 'name', None) + if _is_ignored_class(owner, name, self.config.ignored_classes): + continue + try: if not [n for n in owner.getattr(node.attrname) if not isinstance(n.statement(), astroid.AugAssign)]: @@ -335,8 +352,7 @@ accessed. Python regular expressions are accepted.'} # So call this only after the call has been made. if not _emit_no_member(node, owner, name, node.attrname, self.config.ignored_modules, - self.config.ignore_mixin_members, - self.config.ignored_classes): + self.config.ignore_mixin_members): continue missingattr.add((owner, name)) continue diff --git a/pylint/test/unittest_checker_typecheck.py b/pylint/test/unittest_checker_typecheck.py index 33efe5d..452e9c8 100644 --- a/pylint/test/unittest_checker_typecheck.py +++ b/pylint/test/unittest_checker_typecheck.py @@ -37,6 +37,47 @@ class TypeCheckerTest(CheckerTestCase): with self.assertNoMessages(): self.checker.visit_getattr(node) + @set_config(ignored_classes=('xml.*', )) + def test_ignored_classes_recursive_pattern(self): + """Test that ignored-classes supports patterns for ignoring.""" + node = test_utils.extract_node(''' + import xml.etree + xml.etree.Ala.Bala.Portocala + ''') + with self.assertNoMessages(): + self.checker.visit_getattr(node) + + @set_config(ignored_classes=('optparse.Values', )) + def test_ignored_classes_qualified_name(self): + """Test that ignored-classes supports qualified name for ignoring.""" + node = test_utils.extract_node(''' + import optparse + optparse.Values.lala + ''') + with self.assertNoMessages(): + self.checker.visit_getattr(node) + + @set_config(ignored_classes=('Values', )) + def test_ignored_classes_only_name(self): + """Test that ignored_classes works with the name only.""" + node = test_utils.extract_node(''' + import optparse + optparse.Values.lala + ''') + with self.assertNoMessages(): + self.checker.visit_getattr(node) + + @set_config(ignored_classes=('xml.etree.', )) + def test_ignored_classes_invalid_pattern(self): + node = test_utils.extract_node(''' + import xml + xml.etree.Lala + ''') + message = Message('no-member', node=node, + args=('Module', 'xml.etree', 'Lala')) + with self.assertAddsMessages(message): + self.checker.visit_getattr(node) + if __name__ == '__main__': unittest.main() @@ -280,7 +280,7 @@ ignored-modules= # List of classes names for which member attributes should not be checked # (useful for classes with attributes dynamically set). -ignored-classes=SQLObject, Values +ignored-classes=SQLObject, optparse.Values # When zope mode is activated, add a predefined set of Zope acquired attributes # to generated-members. |