diff --git a/ChangeLog b/ChangeLog index 8a03b77584..13966b9c3d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,15 @@ Pylint's ChangeLog ------------------ +What's New in Pylint 2.4.2? +=========================== + +Release date: TBA + + +* ``ignored-modules`` can skip submodules. Close #3135 + + What's New in Pylint 2.4.1? =========================== diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index e119022084..53014bc541 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -43,6 +43,7 @@ import shlex import sys import types +from collections import deque from collections.abc import Sequence from functools import singledispatch @@ -99,7 +100,7 @@ def _flatten_container(iterable): yield item -def _is_owner_ignored(owner, name, ignored_classes, ignored_modules): +def _is_owner_ignored(owner, attrname, ignored_classes, ignored_modules): """Check if the given owner should be ignored This will verify if the owner's module is in *ignored_modules* @@ -114,20 +115,37 @@ def _is_owner_ignored(owner, name, ignored_classes, ignored_modules): ignored_modules = set(ignored_modules) module_name = owner.root().name module_qname = owner.root().qname() - if any( - module_name in ignored_modules - or module_qname in ignored_modules - or fnmatch.fnmatch(module_qname, ignore) - for ignore in ignored_modules - ): - return True + for ignore in ignored_modules: + # Try to match the module name / fully qualified name directly + if module_qname in ignored_modules or module_name in ignored_modules: + return True + + # Try to see if the ignores pattern match against the module name. + if fnmatch.fnmatch(module_qname, ignore): + return True + + # Otherwise we might have a root module name being ignored, + # and the qualified owner has more levels of depth. + parts = deque(module_name.split(".")) + current_module = "" + + while parts: + part = parts.popleft() + if not current_module: + current_module = part + else: + current_module += ".{}".format(part) + if current_module in ignored_modules: + return True + + # Match against ignored classes. ignored_classes = set(ignored_classes) if hasattr(owner, "qname"): qname = owner.qname() else: qname = "" - return any(ignore in (name, qname) for ignore in ignored_classes) + return any(ignore in (attrname, qname) for ignore in ignored_classes) @singledispatch diff --git a/tests/unittest_checker_typecheck.py b/tests/unittest_checker_typecheck.py index 5c55c67103..f63680cdd6 100644 --- a/tests/unittest_checker_typecheck.py +++ b/tests/unittest_checker_typecheck.py @@ -75,7 +75,7 @@ def test_no_member_in_getattr_ignored(self): with self.assertNoMessages(): self.checker.visit_attribute(node) - @set_config(ignored_classes=("xml.etree.",)) + @set_config(ignored_modules=("xml.etree.",)) def test_ignored_modules_invalid_pattern(self): node = astroid.extract_node( """ @@ -89,6 +89,18 @@ def test_ignored_modules_invalid_pattern(self): with self.assertAddsMessages(message): self.checker.visit_attribute(node) + @set_config(ignored_modules=("xml",)) + def test_ignored_modules_root_one_applies_as_well(self): + # Check that when a root module is completely ignored, submodules are skipped. + node = astroid.extract_node( + """ + import xml + xml.etree.Lala + """ + ) + with self.assertNoMessages(): + self.checker.visit_attribute(node) + @set_config(ignored_modules=("xml.etree*",)) def test_ignored_modules_patterns(self): node = astroid.extract_node(