From 822b99e1260417c3141fa947e65237e40d1bfa8b Mon Sep 17 00:00:00 2001 From: victor <16359131+jiajunsu@users.noreply.github.com> Date: Fri, 30 Apr 2021 19:22:05 +0800 Subject: Add new warning ``deprecated-class`` #4388 (#4419) * Add new warning ``deprecated-class``, fix pre-commit ci pipeline. --- .pre-commit-config.yaml | 3 ++ CONTRIBUTORS.txt | 2 + ChangeLog | 5 ++ doc/whatsnew/2.8.rst | 1 + pylint/checkers/deprecated.py | 55 +++++++++++++++++++++- pylint/checkers/stdlib.py | 47 ++++++++++++++++++ .../d/deprecated/deprecated_class_py33.py | 11 +++++ .../d/deprecated/deprecated_class_py33.rc | 3 ++ .../d/deprecated/deprecated_class_py33.txt | 3 ++ 9 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 tests/functional/d/deprecated/deprecated_class_py33.py create mode 100644 tests/functional/d/deprecated/deprecated_class_py33.rc create mode 100644 tests/functional/d/deprecated/deprecated_class_py33.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 15d8ee555..a2f73f345 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,6 @@ +ci: + skip: [pylint] + repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v3.4.0 diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 62bcc2004..5cf6602e6 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -478,3 +478,5 @@ contributors: * manderj: contributor * qwiddle: contributor + +* Jiajunsu (victor): contributor diff --git a/ChangeLog b/ChangeLog index 15ba23760..24b82920e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -32,6 +32,11 @@ Release date: 2021-04-26 * Pylint's tags are now the standard form ``vX.Y.Z`` and not ``pylint-X.Y.Z`` anymore. +* New warning message ``deprecated-class``. This message is emitted if import or call deprecated class of the +standard library (like ``collections.Iterable`` that will be removed in Python 3.10). + +Closes #4388 + What's New in Pylint 2.8.1? =========================== diff --git a/doc/whatsnew/2.8.rst b/doc/whatsnew/2.8.rst index 571a21a98..0344e981c 100644 --- a/doc/whatsnew/2.8.rst +++ b/doc/whatsnew/2.8.rst @@ -39,6 +39,7 @@ New checkers (For example, 'typing.Dict' can be replaced by 'dict', and 'typing.Unions' by '|', etc.) Make sure to check the config options if you plan on using it! +* Add ``deprecated-class`` check for deprecated classes. Other Changes ============= diff --git a/pylint/checkers/deprecated.py b/pylint/checkers/deprecated.py index 74ee5db83..38ba5d546 100644 --- a/pylint/checkers/deprecated.py +++ b/pylint/checkers/deprecated.py @@ -37,32 +37,51 @@ class DeprecatedMixin: "deprecated-module", "A module marked as deprecated is imported.", ), + "W1512": ( + "Using deprecated class %s of module %s", + "deprecated-class", + "The class is marked as deprecated and will be removed in the future.", + ), } @utils.check_messages( "deprecated-method", "deprecated-argument", + "deprecated-class", ) def visit_call(self, node: astroid.Call) -> None: """Called when a :class:`.astroid.node_classes.Call` node is visited.""" try: + self.check_deprecated_class_in_call(node) for inferred in node.func.infer(): # Calling entry point for deprecation check logic. self.check_deprecated_method(node, inferred) except astroid.InferenceError: pass - @utils.check_messages("deprecated-module") + @utils.check_messages( + "deprecated-module", + "deprecated-class", + ) 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) + if "." in name: + # Checking deprecation for import module with class + mod_name, class_name = name.split(".", 1) + self.check_deprecated_class(node, mod_name, (class_name,)) - @utils.check_messages("deprecated-module") + @utils.check_messages( + "deprecated-module", + "deprecated-class", + ) def visit_importfrom(self, node): """triggered when a from statement is seen""" basename = node.modname self.check_deprecated_module(node, basename) + class_names = (name for name, _ in node.names) + self.check_deprecated_class(node, basename, class_names) def deprecated_methods(self) -> Container[str]: """Callback returning the deprecated methods/functions. @@ -111,6 +130,19 @@ class DeprecatedMixin: # pylint: disable=no-self-use return () + def deprecated_classes(self, module: str) -> Iterable: + """Callback returning the deprecated classes of module. + + Args: + module (str): name of module checked for deprecated classes + + Returns: + collections.abc.Container of deprecated class names. + """ + # pylint: disable=no-self-use + # pylint: disable=unused-argument + return () + def check_deprecated_module(self, node, mod_path): """Checks if the module is deprecated""" @@ -154,3 +186,22 @@ class DeprecatedMixin: self.add_message( "deprecated-argument", node=node, args=(arg_name, func_name) ) + + def check_deprecated_class(self, node, mod_name, class_names): + """Checks if the class is deprecated""" + + for class_name in class_names: + if class_name in self.deprecated_classes(mod_name): + self.add_message( + "deprecated-class", node=node, args=(class_name, mod_name) + ) + + def check_deprecated_class_in_call(self, node): + """Checks if call the deprecated class""" + + if isinstance(node.func, astroid.Attribute) and isinstance( + node.func.expr, astroid.Name + ): + mod_name = node.func.expr.name + class_name = node.func.attrname + self.check_deprecated_class(node, mod_name, (class_name,)) diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index dc1c1b2d6..7b60a9a10 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -201,6 +201,39 @@ DEPRECATED_METHODS = { } +DEPRECATED_CLASSES = { + (3, 3, 0): { + "collections": { + "Awaitable", + "Coroutine", + "AsyncIterable", + "AsyncIterator", + "AsyncGenerator", + "Hashable", + "Iterable", + "Iterator", + "Generator", + "Reversible", + "Sized", + "Container", + "Callable", + "Collection", + "Set", + "MutableSet", + "Mapping", + "MutableMapping", + "MappingView", + "KeysView", + "ItemsView", + "ValuesView", + "Sequence", + "MutableSequence", + "ByteString", + }, + } +} + + def _check_mode_str(mode): # check type if not isinstance(mode, str): @@ -316,6 +349,11 @@ class StdlibChecker(DeprecatedMixin, BaseChecker): "deprecated-argument", "The argument is marked as deprecated and will be removed in the future.", ), + "W1512": ( + "Using deprecated class %s of module %s", + "deprecated-class", + "The class is marked as deprecated and will be removed in the future.", + ), } def __init__(self, linter=None): @@ -329,6 +367,10 @@ class StdlibChecker(DeprecatedMixin, BaseChecker): for since_vers, func_list in DEPRECATED_ARGUMENTS.items(): if since_vers <= sys.version_info: self._deprecated_attributes.update(func_list) + self._deprecated_classes = dict() + for since_vers, class_list in DEPRECATED_CLASSES.items(): + if since_vers <= sys.version_info: + self._deprecated_classes.update(class_list) def _check_bad_thread_instantiation(self, node): if not node.kwargs and not node.keywords and len(node.args) <= 1: @@ -363,10 +405,12 @@ class StdlibChecker(DeprecatedMixin, BaseChecker): "invalid-envvar-default", "subprocess-popen-preexec-fn", "subprocess-run-check", + "deprecated-class", ) def visit_call(self, node): """Visit a Call node.""" try: + self.check_deprecated_class_in_call(node) for inferred in node.func.infer(): if inferred is astroid.Uninferable: continue @@ -511,6 +555,9 @@ class StdlibChecker(DeprecatedMixin, BaseChecker): def deprecated_arguments(self, method: str): return self._deprecated_attributes.get(method, ()) + def deprecated_classes(self, module: str): + return self._deprecated_classes.get(module, ()) + def register(linter): """required method to auto register this checker""" diff --git a/tests/functional/d/deprecated/deprecated_class_py33.py b/tests/functional/d/deprecated/deprecated_class_py33.py new file mode 100644 index 000000000..470e467ba --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_class_py33.py @@ -0,0 +1,11 @@ +"""Test deprecated classes from Python 3.3.""" +# pylint: disable=unused-import,import-error,no-name-in-module,abstract-class-instantiated + +from collections import Iterable # [deprecated-class] + +import collections.Set # [deprecated-class] + +import collections + + +_ = collections.Awaitable() # [deprecated-class] diff --git a/tests/functional/d/deprecated/deprecated_class_py33.rc b/tests/functional/d/deprecated/deprecated_class_py33.rc new file mode 100644 index 000000000..692104e97 --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_class_py33.rc @@ -0,0 +1,3 @@ +[testoptions] +min_pyver=3.3 +max_pyver=3.10 diff --git a/tests/functional/d/deprecated/deprecated_class_py33.txt b/tests/functional/d/deprecated/deprecated_class_py33.txt new file mode 100644 index 000000000..9a31c0efb --- /dev/null +++ b/tests/functional/d/deprecated/deprecated_class_py33.txt @@ -0,0 +1,3 @@ +deprecated-class:4:0::Using deprecated class Iterable of module collections +deprecated-class:6:0::Using deprecated class Set of module collections +deprecated-class:11:4::Using deprecated class Awaitable of module collections -- cgit v1.2.1