From 0f8212f43c60b41438a5485faec6cbec143f57c1 Mon Sep 17 00:00:00 2001 From: Matus Valo Date: Tue, 6 Jul 2021 20:15:20 +0200 Subject: [PATCH] Use absolute import qualified module name for deprecated module check (#4678) * Use absolute import qualified module name for deprecated module check * Improved docstring of get_import_name --- ChangeLog | 3 +++ pylint/checkers/deprecated.py | 4 +-- pylint/checkers/imports.py | 21 ++-------------- pylint/checkers/utils.py | 25 +++++++++++++++++++ tests/checkers/unittest_deprecated.py | 2 +- .../deprecated_relative_import/__init__.py | 0 .../dot_relative_import.py | 7 ++++++ .../dot_relative_import.rc | 2 ++ .../dot_relative_import.txt | 1 + .../subpackage/__init__.py | 0 .../subpackage/dot_dot_relative_import.py | 7 ++++++ .../subpackage/dot_dot_relative_import.rc | 2 ++ .../subpackage/dot_dot_relative_import.txt | 1 + 13 files changed, 53 insertions(+), 22 deletions(-) create mode 100644 tests/functional/d/deprecated/deprecated_relative_import/__init__.py create mode 100644 tests/functional/d/deprecated/deprecated_relative_import/dot_relative_import.py create mode 100644 tests/functional/d/deprecated/deprecated_relative_import/dot_relative_import.rc create mode 100644 tests/functional/d/deprecated/deprecated_relative_import/dot_relative_import.txt create mode 100644 tests/functional/d/deprecated/deprecated_relative_import/subpackage/__init__.py create mode 100644 tests/functional/d/deprecated/deprecated_relative_import/subpackage/dot_dot_relative_import.py create mode 100644 tests/functional/d/deprecated/deprecated_relative_import/subpackage/dot_dot_relative_import.rc create mode 100644 tests/functional/d/deprecated/deprecated_relative_import/subpackage/dot_dot_relative_import.txt diff --git a/ChangeLog b/ChangeLog index b38b0d51b7..ca15b4a784 100644 --- a/ChangeLog +++ b/ChangeLog @@ -17,6 +17,9 @@ Release date: TBA .. Put bug fixes that should not wait for a new minor version here +* Fix false-positive ``deprecated-module`` when relative import uses deprecated module name. + + Closes #4629 * Fix false-positive ``consider-using-with`` (R1732) if ``contextlib.ExitStack`` takes care of calling the ``__exit__`` method diff --git a/pylint/checkers/deprecated.py b/pylint/checkers/deprecated.py index a20b09e8e1..fb2e6abfde 100644 --- a/pylint/checkers/deprecated.py +++ b/pylint/checkers/deprecated.py @@ -8,7 +8,7 @@ import astroid from pylint.checkers import utils -from pylint.checkers.utils import safe_infer +from pylint.checkers.utils import get_import_name, safe_infer ACCEPTABLE_NODES = ( astroid.BoundMethod, @@ -109,6 +109,7 @@ def visit_decorators(self, node): def visit_importfrom(self, node): """triggered when a from statement is seen""" basename = node.modname + basename = get_import_name(node, basename) self.check_deprecated_module(node, basename) class_names = (name for name, _ in node.names) self.check_deprecated_class(node, basename, class_names) @@ -175,7 +176,6 @@ def deprecated_classes(self, module: str) -> Iterable: def check_deprecated_module(self, node, mod_path): """Checks if the module is deprecated""" - for mod_name in self.deprecated_modules(): if mod_path == mod_name or mod_path.startswith(mod_name + "."): self.add_message("deprecated-module", node=node, args=mod_path) diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py index be5acb517b..e5e1844218 100644 --- a/pylint/checkers/imports.py +++ b/pylint/checkers/imports.py @@ -56,6 +56,7 @@ from pylint.checkers import BaseChecker, DeprecatedMixin from pylint.checkers.utils import ( check_messages, + get_import_name, is_from_fallback_block, node_ignores_exception, ) @@ -78,24 +79,6 @@ def _qualified_names(modname): return [".".join(names[0 : i + 1]) for i in range(len(names))] -def _get_import_name(importnode, modname): - """Get a prepared module name from the given import node - - In the case of relative imports, this will return the - absolute qualified module name, which might be useful - for debugging. Otherwise, the initial module name - is returned unchanged. - """ - if isinstance(importnode, astroid.ImportFrom): - if importnode.level: - root = importnode.root() - if isinstance(root, astroid.Module): - modname = root.relative_to_absolute_name( - modname, level=importnode.level - ) - return modname - - def _get_first_import(node, context, name, base, level, alias): """return the node where [base.] is imported or None if not found""" fullname = f"{base}.{name}" if base else name @@ -826,7 +809,7 @@ def _get_imported_module(self, importnode, modname): ): return None - dotted_modname = _get_import_name(importnode, modname) + dotted_modname = get_import_name(importnode, modname) self.add_message("import-error", args=repr(dotted_modname), node=importnode) return None diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index 7779ddc4c9..0eec36fb1a 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -1510,3 +1510,28 @@ def get_subscript_const_value(node: astroid.Subscript) -> astroid.Const: ) return inferred + + +def get_import_name( + importnode: Union[astroid.Import, astroid.ImportFrom], modname: str +) -> str: + """Get a prepared module name from the given import node + + In the case of relative imports, this will return the + absolute qualified module name, which might be useful + for debugging. Otherwise, the initial module name + is returned unchanged. + + :param importnode: node representing import statement. + :param modname: module name from import statement. + :returns: absolute qualified module name of the module + used in import. + """ + if isinstance(importnode, astroid.ImportFrom): + if importnode.level: + root = importnode.root() + if isinstance(root, astroid.Module): + modname = root.relative_to_absolute_name( + modname, level=importnode.level + ) + return modname diff --git a/tests/checkers/unittest_deprecated.py b/tests/checkers/unittest_deprecated.py index 31160e8e64..f0142a1e6f 100644 --- a/tests/checkers/unittest_deprecated.py +++ b/tests/checkers/unittest_deprecated.py @@ -452,7 +452,7 @@ def test_deprecated_class_import_from(self): # Tests detecting deprecated class via import from node = astroid.extract_node( """ - from .deprecated import DeprecatedClass + from deprecated import DeprecatedClass """ ) with self.assertAddsMessages( diff --git a/tests/functional/d/deprecated/deprecated_relative_import/__init__.py b/tests/functional/d/deprecated/deprecated_relative_import/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/d/deprecated/deprecated_relative_import/dot_relative_import.py b/tests/functional/d/deprecated/deprecated_relative_import/dot_relative_import.py new file mode 100644 index 0000000000..db07f976be --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_relative_import/dot_relative_import.py @@ -0,0 +1,7 @@ +# pylint: disable=import-error, missing-module-docstring, unused-import + +# from import of stdlib optparse which should yield deprecated-module error +from optparse import OptionParser # [deprecated-module] +# from import of module internal optparse module inside this package. +# This should not yield deprecated-module error +from .optparse import Bar diff --git a/tests/functional/d/deprecated/deprecated_relative_import/dot_relative_import.rc b/tests/functional/d/deprecated/deprecated_relative_import/dot_relative_import.rc new file mode 100644 index 0000000000..d425e7c476 --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_relative_import/dot_relative_import.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.2 diff --git a/tests/functional/d/deprecated/deprecated_relative_import/dot_relative_import.txt b/tests/functional/d/deprecated/deprecated_relative_import/dot_relative_import.txt new file mode 100644 index 0000000000..8f1b95f941 --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_relative_import/dot_relative_import.txt @@ -0,0 +1 @@ +deprecated-module:4:0::Uses of a deprecated module 'optparse':HIGH diff --git a/tests/functional/d/deprecated/deprecated_relative_import/subpackage/__init__.py b/tests/functional/d/deprecated/deprecated_relative_import/subpackage/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/functional/d/deprecated/deprecated_relative_import/subpackage/dot_dot_relative_import.py b/tests/functional/d/deprecated/deprecated_relative_import/subpackage/dot_dot_relative_import.py new file mode 100644 index 0000000000..359a4c46d9 --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_relative_import/subpackage/dot_dot_relative_import.py @@ -0,0 +1,7 @@ +# pylint: disable=import-error, unused-import, missing-module-docstring + +# from import of stdlib optparse which should yield deprecated-module error +from optparse import OptionParser # [deprecated-module] +# from import of module internal optparse module inside this package. +# This should not yield deprecated-module error +from ..optparse import Bar diff --git a/tests/functional/d/deprecated/deprecated_relative_import/subpackage/dot_dot_relative_import.rc b/tests/functional/d/deprecated/deprecated_relative_import/subpackage/dot_dot_relative_import.rc new file mode 100644 index 0000000000..d425e7c476 --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_relative_import/subpackage/dot_dot_relative_import.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.2 diff --git a/tests/functional/d/deprecated/deprecated_relative_import/subpackage/dot_dot_relative_import.txt b/tests/functional/d/deprecated/deprecated_relative_import/subpackage/dot_dot_relative_import.txt new file mode 100644 index 0000000000..8f1b95f941 --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_relative_import/subpackage/dot_dot_relative_import.txt @@ -0,0 +1 @@ +deprecated-module:4:0::Uses of a deprecated module 'optparse':HIGH