summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorvictor <16359131+jiajunsu@users.noreply.github.com>2021-04-30 19:22:05 +0800
committerGitHub <noreply@github.com>2021-04-30 13:22:05 +0200
commit822b99e1260417c3141fa947e65237e40d1bfa8b (patch)
tree2295575de7d43af1ae5886ccb102f72b02842e66
parent0e63847122ee898e81b3258827e0c05702f68459 (diff)
downloadpylint-git-822b99e1260417c3141fa947e65237e40d1bfa8b.tar.gz
Add new warning ``deprecated-class`` #4388 (#4419)
* Add new warning ``deprecated-class``, fix pre-commit ci pipeline.
-rw-r--r--.pre-commit-config.yaml3
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--ChangeLog5
-rw-r--r--doc/whatsnew/2.8.rst1
-rw-r--r--pylint/checkers/deprecated.py55
-rw-r--r--pylint/checkers/stdlib.py47
-rw-r--r--tests/functional/d/deprecated/deprecated_class_py33.py11
-rw-r--r--tests/functional/d/deprecated/deprecated_class_py33.rc3
-rw-r--r--tests/functional/d/deprecated/deprecated_class_py33.txt3
9 files changed, 128 insertions, 2 deletions
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