summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatus Valo <matusvalo@gmail.com>2021-01-29 06:36:51 +0100
committerPierre Sassoulas <pierre.sassoulas@gmail.com>2021-02-21 11:41:20 +0100
commit5aa8f18b6681711f683e5d56c0f569a2a4145571 (patch)
tree20f796677ce31c7e6df816895eed6d1c333ba3ab
parent45c824549f9a7fa38df04ae8648d1647c82ba9b4 (diff)
downloadpylint-git-5aa8f18b6681711f683e5d56c0f569a2a4145571.tar.gz
Introduced DeprecatedMixin
-rw-r--r--ChangeLog4
-rw-r--r--pylint/checkers/__init__.py2
-rw-r--r--pylint/checkers/deprecated.py49
-rw-r--r--pylint/checkers/stdlib.py46
-rw-r--r--tests/checkers/unittest_deprecated.py99
5 files changed, 166 insertions, 34 deletions
diff --git a/ChangeLog b/ChangeLog
index ed2e2398d..8c8a83153 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -5,6 +5,10 @@ Pylint's ChangeLog
What's New in Pylint 2.7.0?
===========================
+* Introduce DeprecationMixin for reusable deprecation checks.
+
+ Closes #4049
+
* Fix false positive for ``builtin-not-iterating`` when ``map`` receives iterable
Closes #4078
diff --git a/pylint/checkers/__init__.py b/pylint/checkers/__init__.py
index 31d2df522..b459b0497 100644
--- a/pylint/checkers/__init__.py
+++ b/pylint/checkers/__init__.py
@@ -44,6 +44,7 @@ messages nor reports. XXX not true, emit a 07 report !
"""
from pylint.checkers.base_checker import BaseChecker, BaseTokenChecker
+from pylint.checkers.deprecated import DeprecatedMixin
from pylint.checkers.mapreduce_checker import MapReduceMixin
from pylint.utils import register_plugins
@@ -71,5 +72,6 @@ __all__ = [
"BaseTokenChecker",
"initialize",
"MapReduceMixin",
+ "DeprecatedMixin",
"register_plugins",
]
diff --git a/pylint/checkers/deprecated.py b/pylint/checkers/deprecated.py
new file mode 100644
index 000000000..0bbad05ee
--- /dev/null
+++ b/pylint/checkers/deprecated.py
@@ -0,0 +1,49 @@
+# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+# For details: https://github.com/PyCQA/pylint/blob/master/COPYING
+
+"""Checker mixin for deprecated functionality."""
+
+import abc
+
+import astroid
+
+ACCEPTABLE_NODES = (
+ astroid.BoundMethod,
+ astroid.UnboundMethod,
+ astroid.FunctionDef,
+)
+
+
+class DeprecatedMixin(metaclass=abc.ABCMeta):
+ """A mixin implementing logic for checking deprecated symbols.
+ A class imlementing mixin must define "deprecated-method" Message.
+ """
+
+ @abc.abstractmethod
+ def deprecated_methods(self):
+ """Callback returning the deprecated methods/functions.
+
+ Returns:
+ collections.abc.Container of deprecated function/method names.
+ """
+
+ 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.
+ """
+
+ # Reject nodes which aren't of interest to us.
+ if not isinstance(inferred, ACCEPTABLE_NODES):
+ return
+
+ if isinstance(node.func, astroid.Attribute):
+ func_name = node.func.attrname
+ elif isinstance(node.func, astroid.Name):
+ func_name = node.func.name
+ else:
+ # Not interested in other nodes.
+ return
+
+ qname = inferred.qname()
+ if any(name in self.deprecated_methods() for name in (qname, func_name)):
+ self.add_message("deprecated-method", node=node, args=(func_name,))
diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py
index 270623f09..ad6e4f855 100644
--- a/pylint/checkers/stdlib.py
+++ b/pylint/checkers/stdlib.py
@@ -36,7 +36,7 @@ import astroid
from astroid.bases import Instance
from astroid.node_classes import Const
-from pylint.checkers import BaseChecker, utils
+from pylint.checkers import BaseChecker, DeprecatedMixin, utils
from pylint.interfaces import IAstroidChecker
OPEN_FILES = {"open", "file"}
@@ -80,7 +80,7 @@ def _check_mode_str(mode):
return True
-class StdlibChecker(BaseChecker):
+class StdlibChecker(BaseChecker, DeprecatedMixin):
__implements__ = (IAstroidChecker,)
name = "stdlib"
@@ -336,7 +336,7 @@ class StdlibChecker(BaseChecker):
self._check_env_function(node, inferred)
elif name == SUBPROCESS_RUN:
self._check_for_check_kw_in_run(node)
- self._check_deprecated_method(node, inferred)
+ self.check_deprecated_method(node, inferred)
except astroid.InferenceError:
return
@@ -358,37 +358,6 @@ class StdlibChecker(BaseChecker):
for value in node.values:
self._check_datetime(value)
- def _check_deprecated_method(self, node, inferred):
- py_vers = sys.version_info[0]
-
- if isinstance(node.func, astroid.Attribute):
- func_name = node.func.attrname
- elif isinstance(node.func, astroid.Name):
- func_name = node.func.name
- else:
- # Not interested in other nodes.
- return
-
- # Reject nodes which aren't of interest to us.
- acceptable_nodes = (
- astroid.BoundMethod,
- astroid.UnboundMethod,
- astroid.FunctionDef,
- )
- if not isinstance(inferred, acceptable_nodes):
- return
-
- qname = inferred.qname()
- if any(name in self.deprecated[0] for name in (qname, func_name)):
- self.add_message("deprecated-method", node=node, args=(func_name,))
- else:
- for since_vers, func_list in self.deprecated[py_vers].items():
- if since_vers <= sys.version_info and any(
- name in func_list for name in (qname, func_name)
- ):
- self.add_message("deprecated-method", node=node, args=(func_name,))
- break
-
def _check_redundant_assert(self, node, infer):
if (
isinstance(infer, astroid.BoundMethod)
@@ -481,6 +450,15 @@ class StdlibChecker(BaseChecker):
else:
self.add_message(message, node=node, args=(name, call_arg.pytype()))
+ def deprecated_methods(self):
+ py_vers = sys.version_info[0]
+ deprecated_methods = set()
+ deprecated_methods.update(self.deprecated[0])
+ for since_vers, func_list in self.deprecated[py_vers].items():
+ if since_vers <= sys.version_info:
+ deprecated_methods.update(func_list)
+ return deprecated_methods
+
def register(linter):
"""required method to auto register this checker """
diff --git a/tests/checkers/unittest_deprecated.py b/tests/checkers/unittest_deprecated.py
new file mode 100644
index 000000000..e4da246ff
--- /dev/null
+++ b/tests/checkers/unittest_deprecated.py
@@ -0,0 +1,99 @@
+import astroid
+
+from pylint.checkers import BaseChecker, DeprecatedMixin, utils
+from pylint.interfaces import UNDEFINED, IAstroidChecker
+from pylint.testutils import CheckerTestCase, Message
+
+
+class _DeprecatedChecker(BaseChecker, DeprecatedMixin):
+ __implements__ = (IAstroidChecker,)
+ name = "deprecated"
+
+ msgs = {
+ "W1505": (
+ "Using deprecated method %s()",
+ "deprecated-method",
+ "The method is marked as deprecated and will be removed in "
+ "a future version of Python. Consider looking for an "
+ "alternative in the documentation.",
+ )
+ }
+
+ @utils.check_messages(
+ "deprecated-method",
+ )
+ def visit_call(self, node):
+ """Visit a Call node."""
+ try:
+ for inferred in node.func.infer():
+ self.check_deprecated_method(node, inferred)
+ except astroid.InferenceError:
+ return
+
+ def deprecated_methods(self):
+ return {"deprecated_func", ".Deprecated.deprecated_method"}
+
+
+class TestDeprecatedChecker(CheckerTestCase):
+ CHECKER_CLASS = _DeprecatedChecker
+
+ def test_deprecated_function(self):
+ # Tests detecting deprecated function
+ node = astroid.extract_node(
+ """
+ def deprecated_func():
+ pass
+
+ deprecated_func()
+ """
+ )
+ with self.assertAddsMessages(
+ Message(
+ msg_id="deprecated-method",
+ args=("deprecated_func",),
+ node=node,
+ confidence=UNDEFINED,
+ )
+ ):
+ self.checker.visit_call(node)
+
+ def test_deprecated_method(self):
+ # Tests detecting deprecated method
+ node = astroid.extract_node(
+ """
+ class Deprecated:
+ def deprecated_method():
+ pass
+
+ d = Deprecated()
+ d.deprecated_method()
+ """
+ )
+ with self.assertAddsMessages(
+ Message(
+ msg_id="deprecated-method",
+ args=("deprecated_method",),
+ node=node,
+ confidence=UNDEFINED,
+ )
+ ):
+ self.checker.visit_call(node)
+
+ def test_no_message(self):
+ # Tests not raising error when no deprecated functions/methods are present.
+ node = astroid.extract_node(
+ """
+ class MyClass:
+ def mymethod():
+ pass
+
+ MyClass().mymethod()
+
+ def myfunc():
+ pass
+
+ myfunc()
+ """
+ )
+ with self.assertNoMessages():
+ self.checker.visit_call(node)