summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Mouritzen <dmrtzn@gmail.com>2023-04-04 05:44:15 +0900
committerGitHub <noreply@github.com>2023-04-03 22:44:15 +0200
commit9fa662ca9c1b20bd2781e564d6a78d31111e5bb0 (patch)
treecdc16d5c2dda6736e2727f8a1aa500a38d07dbf2
parent2e6468f7f986032dad0e1ce5e5c8aa7f5d9e55fb (diff)
downloadpylint-git-9fa662ca9c1b20bd2781e564d6a78d31111e5bb0.tar.gz
Handle patterns in ignored-modules when checking for no-name-in-module (#7579)
Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com> Co-authored-by: Jared Proffitt <jaredproffitt@gmail.com>
-rw-r--r--doc/whatsnew/fragments/7578.bugfix3
-rw-r--r--pylint/checkers/imports.py22
-rw-r--r--pylint/checkers/typecheck.py2
-rw-r--r--pylint/checkers/utils.py46
-rw-r--r--pylint/checkers/variables.py5
-rw-r--r--tests/functional/i/import_error.py6
-rw-r--r--tests/functional/i/import_error.rc2
-rw-r--r--tests/functional/i/import_error.txt2
8 files changed, 40 insertions, 48 deletions
diff --git a/doc/whatsnew/fragments/7578.bugfix b/doc/whatsnew/fragments/7578.bugfix
new file mode 100644
index 000000000..2b01149ca
--- /dev/null
+++ b/doc/whatsnew/fragments/7578.bugfix
@@ -0,0 +1,3 @@
+The ``ignored-modules`` option will now be correctly taken into account for ``no-name-in-module``.
+
+Closes #7578
diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py
index 9f4b0e379..6cc5d2164 100644
--- a/pylint/checkers/imports.py
+++ b/pylint/checkers/imports.py
@@ -23,6 +23,7 @@ from pylint.checkers.utils import (
get_import_name,
in_type_checking_block,
is_from_fallback_block,
+ is_module_ignored,
is_sys_guard,
node_ignores_exception,
)
@@ -84,18 +85,6 @@ DEPRECATED_MODULES = {
}
-def _qualified_names(modname: str | None) -> list[str]:
- """Split the names of the given module into subparts.
-
- For example,
- _qualified_names('pylint.checkers.ImportsChecker')
- returns
- ['pylint', 'pylint.checkers', 'pylint.checkers.ImportsChecker']
- """
- names = modname.split(".") if modname is not None else ""
- return [".".join(names[0 : i + 1]) for i in range(len(names))]
-
-
def _get_first_import(
node: ImportNode,
context: nodes.LocalsDictNodeNG,
@@ -153,12 +142,11 @@ def _get_first_import(
def _ignore_import_failure(
node: ImportNode,
- modname: str | None,
+ modname: str,
ignored_modules: Sequence[str],
) -> bool:
- for submodule in _qualified_names(modname):
- if submodule in ignored_modules:
- return True
+ if is_module_ignored(modname, ignored_modules):
+ return True
# Ignore import failure if part of guarded import block
# I.e. `sys.version_info` or `typing.TYPE_CHECKING`
@@ -852,7 +840,7 @@ class ImportsChecker(DeprecatedMixin, BaseChecker):
return std_imports, external_imports, local_imports
def _get_imported_module(
- self, importnode: ImportNode, modname: str | None
+ self, importnode: ImportNode, modname: str
) -> nodes.Module | None:
try:
return importnode.do_import_module(modname)
diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py
index 90604261d..42131f936 100644
--- a/pylint/checkers/typecheck.py
+++ b/pylint/checkers/typecheck.py
@@ -124,7 +124,7 @@ def _is_owner_ignored(
matches any name from the *ignored_classes* or if its qualified
name can be found in *ignored_classes*.
"""
- if is_module_ignored(owner.root(), ignored_modules):
+ if is_module_ignored(owner.root().qname(), ignored_modules):
return True
# Match against ignored classes.
diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py
index 4b316909e..38321bc11 100644
--- a/pylint/checkers/utils.py
+++ b/pylint/checkers/utils.py
@@ -12,7 +12,6 @@ import itertools
import numbers
import re
import string
-from collections import deque
from collections.abc import Iterable, Iterator
from functools import lru_cache, partial
from re import Match
@@ -2050,37 +2049,30 @@ def is_augmented_assign(node: nodes.Assign) -> tuple[bool, str]:
return False, ""
+def _qualified_name_parts(qualified_module_name: str) -> list[str]:
+ """Split the names of the given module into subparts.
+
+ For example,
+ _qualified_name_parts('pylint.checkers.ImportsChecker')
+ returns
+ ['pylint', 'pylint.checkers', 'pylint.checkers.ImportsChecker']
+ """
+ names = qualified_module_name.split(".")
+ return [".".join(names[0 : i + 1]) for i in range(len(names))]
+
+
def is_module_ignored(
- module: nodes.Module,
- ignored_modules: Iterable[str],
+ qualified_module_name: str, ignored_modules: Iterable[str]
) -> bool:
ignored_modules = set(ignored_modules)
- module_name = module.name
- module_qname = module.qname()
-
- 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:
+ for current_module in _qualified_name_parts(qualified_module_name):
+ # Try to match the module name directly
+ if current_module 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 += f".{part}"
- if current_module in ignored_modules:
+ for ignore in ignored_modules:
+ # Try to see if the ignores pattern match against the module name.
+ if fnmatch.fnmatch(current_module, ignore):
return True
-
return False
diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py
index 0a24de370..79bdb6a97 100644
--- a/pylint/checkers/variables.py
+++ b/pylint/checkers/variables.py
@@ -25,6 +25,7 @@ from astroid.typing import InferenceResult
from pylint.checkers import BaseChecker, utils
from pylint.checkers.utils import (
in_type_checking_block,
+ is_module_ignored,
is_postponed_evaluation_enabled,
is_sys_guard,
overridden_method,
@@ -2941,7 +2942,9 @@ class VariablesChecker(BaseChecker):
if not isinstance(module, nodes.Module):
return None
except astroid.NotFoundError:
- if module.name in self._ignored_modules:
+ # Unable to import `name` from `module`. Since `name` may itself be a
+ # module, we first check if it matches the ignored modules.
+ if is_module_ignored(f"{module.qname()}.{name}", self._ignored_modules):
return None
self.add_message(
"no-name-in-module", args=(name, module.name), node=node
diff --git a/tests/functional/i/import_error.py b/tests/functional/i/import_error.py
index 97e9dddbf..474a53b5a 100644
--- a/tests/functional/i/import_error.py
+++ b/tests/functional/i/import_error.py
@@ -80,6 +80,12 @@ import foo, bar # [multiple-imports]
import foo
import bar
+import mymodule_ignored
+import mymodule.something_ignored
+from mymodule.something_ignored import anything
+import sys.something_ignored
+from sys.something_ignored import anything
+
# Issues with contextlib.suppress reported in
# https://github.com/pylint-dev/pylint/issues/7270
import contextlib
diff --git a/tests/functional/i/import_error.rc b/tests/functional/i/import_error.rc
index d8f2b25e2..7278ff8f1 100644
--- a/tests/functional/i/import_error.rc
+++ b/tests/functional/i/import_error.rc
@@ -3,7 +3,7 @@ disable=C,R,W
enable=multiple-imports
[TYPECHECK]
-ignored-modules=external_module,fake_module.submodule,foo,bar
+ignored-modules=external_module,fake_module.submodule,foo,bar,*_ignored
[testoptions]
# TODO: PY3.9: This does pass on PyPy 3.9
diff --git a/tests/functional/i/import_error.txt b/tests/functional/i/import_error.txt
index 80a5732f0..49cc0a673 100644
--- a/tests/functional/i/import_error.txt
+++ b/tests/functional/i/import_error.txt
@@ -3,4 +3,4 @@ import-error:21:4:21:26::Unable to import 'maybe_missing_2':UNDEFINED
no-name-in-module:33:0:33:49::No name 'syntax_error' in module 'functional.s.syntax':UNDEFINED
syntax-error:33:0:None:None::Cannot import 'functional.s.syntax.syntax_error' due to 'invalid syntax (<unknown>, line 1)':HIGH
multiple-imports:78:0:78:15::Multiple imports on one line (foo, bar):UNDEFINED
-import-error:90:4:90:15::Unable to import 'foo2':UNDEFINED
+import-error:96:4:96:15::Unable to import 'foo2':UNDEFINED