diff --git a/ChangeLog b/ChangeLog index ac5a903307..ea04a5ab93 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,6 +15,10 @@ Release date: TBA alternative to ``extension-pkg-whitelist`` and the message ``blacklisted-name`` is now emitted as ``disallowed-name``. The previous names are accepted to maintain backward compatibility. +* Move deprecated checker to ``DeprecatedMixin`` + + Closes #4086 + What's New in Pylint 2.7.3? =========================== diff --git a/pylint/checkers/deprecated.py b/pylint/checkers/deprecated.py index 9a4f636e4c..8aaa025068 100644 --- a/pylint/checkers/deprecated.py +++ b/pylint/checkers/deprecated.py @@ -32,6 +32,11 @@ class DeprecatedMixin: "deprecated-argument", "The argument is marked as deprecated and will be removed in the future.", ), + "W0402": ( + "Uses of a deprecated module %r", + "deprecated-module", + "A module marked as deprecated is imported.", + ), } @utils.check_messages( @@ -47,6 +52,18 @@ def visit_call(self, node: astroid.Call) -> None: except astroid.InferenceError: pass + @utils.check_messages("deprecated-module") + def visit_import(self, node): + """triggered when an import statement is seen""" + for name in (name for name, _ in node.names): + self.check_deprecated_module(node, name) + + @utils.check_messages("deprecated-module") + def visit_importfrom(self, node): + """triggered when a from statement is seen""" + basename = node.modname + self.check_deprecated_module(node, basename) + def deprecated_methods(self) -> Container[str]: """Callback returning the deprecated methods/functions. @@ -85,6 +102,22 @@ def bar(arg1, arg2, arg3, arg4, arg5='spam') # pylint: disable=unused-argument return () + def deprecated_modules(self) -> Iterable: + """Callback returning the deprecated modules. + + Returns: + collections.abc.Container of deprecated module names. + """ + # pylint: disable=no-self-use + return () + + 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) + def check_deprecated_method(self, node, inferred): """Executes the checker for the given node. This method should be called from the checker implementing this mixin. diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py index 82ff074487..ba14108c48 100644 --- a/pylint/checkers/imports.py +++ b/pylint/checkers/imports.py @@ -49,7 +49,7 @@ from astroid import modutils from astroid.decorators import cached -from pylint.checkers import BaseChecker +from pylint.checkers import BaseChecker, DeprecatedMixin from pylint.checkers.utils import ( check_messages, is_from_fallback_block, @@ -292,7 +292,7 @@ def _make_graph(filename, dep_info, sect, gtype): DEFAULT_PREFERRED_MODULES = () -class ImportsChecker(BaseChecker): +class ImportsChecker(DeprecatedMixin, BaseChecker): """checks for * external modules dependencies * relative / wildcard imports @@ -306,13 +306,13 @@ class ImportsChecker(BaseChecker): name = "imports" msgs = MSGS priority = -2 - deprecated_modules = ("optparse", "tkinter.tix") + default_deprecated_modules = ("optparse", "tkinter.tix") options = ( ( "deprecated-modules", { - "default": deprecated_modules, + "default": default_deprecated_modules, "type": "csv", "metavar": "", "help": "Deprecated modules which should not be used," @@ -487,6 +487,10 @@ def close(self): for cycle in get_cycles(graph, vertices=vertices): self.add_message("cyclic-import", args=" -> ".join(cycle)) + def deprecated_modules(self): + """Callback returning the deprecated modules.""" + return self.config.deprecated_modules + @check_messages(*MSGS) def visit_import(self, node): """triggered when an import statement is seen""" @@ -499,7 +503,7 @@ def visit_import(self, node): self.add_message("multiple-imports", args=", ".join(names), node=node) for name in names: - self._check_deprecated_module(node, name) + self.check_deprecated_module(node, name) self._check_preferred_module(node, name) imported_module = self._get_imported_module(node, name) if isinstance(node.parent, astroid.Module): @@ -521,7 +525,7 @@ def visit_importfrom(self, node): self._check_import_as_rename(node) self._check_misplaced_future(node) - self._check_deprecated_module(node, basename) + self.check_deprecated_module(node, basename) self._check_preferred_module(node, basename) self._check_wildcard_imports(node, imported_module) self._check_same_line_imports(node) @@ -832,12 +836,6 @@ def _add_imported_module(self, node, importedmodname): if not self.linter.is_message_enabled("cyclic-import", line=node.lineno): self._excluded_edges[context_name].add(importedmodname) - def _check_deprecated_module(self, node, mod_path): - """check if the module is deprecated""" - for mod_name in self.config.deprecated_modules: - if mod_path == mod_name or mod_path.startswith(mod_name + "."): - self.add_message("deprecated-module", node=node, args=mod_path) - def _check_preferred_module(self, node, mod_path): """check if the module has a preferred replacement""" if mod_path in self.preferred_modules: diff --git a/tests/checkers/unittest_deprecated.py b/tests/checkers/unittest_deprecated.py index f7e8c13092..f9b45cd6d5 100644 --- a/tests/checkers/unittest_deprecated.py +++ b/tests/checkers/unittest_deprecated.py @@ -12,6 +12,9 @@ class _DeprecatedChecker(DeprecatedMixin, BaseChecker): def deprecated_methods(self): return {"deprecated_func", ".Deprecated.deprecated_method"} + def deprecated_modules(self): + return {"deprecated_module"} + def deprecated_arguments(self, method): if method == "myfunction1": # def myfunction1(arg1, deprecated_arg1='spam') @@ -355,3 +358,37 @@ def mymethod2(self, arg1, deprecated_arg1, arg2='foo', deprecated_arg2='spam'): ), ): self.checker.visit_call(node) + + def test_deprecated_module(self): + # Tests detecting deprecated module + node = astroid.extract_node( + """ + import deprecated_module + """ + ) + with self.assertAddsMessages( + Message( + msg_id="deprecated-module", + args="deprecated_module", + node=node, + confidence=UNDEFINED, + ) + ): + self.checker.visit_import(node) + + def test_deprecated_module_from(self): + # Tests detecting deprecated module + node = astroid.extract_node( + """ + from deprecated_module import myfunction + """ + ) + with self.assertAddsMessages( + Message( + msg_id="deprecated-module", + args="deprecated_module", + node=node, + confidence=UNDEFINED, + ) + ): + self.checker.visit_importfrom(node)