diff options
author | Matus Valo <matusvalo@gmail.com> | 2021-01-29 06:36:51 +0100 |
---|---|---|
committer | Pierre Sassoulas <pierre.sassoulas@gmail.com> | 2021-02-21 11:41:20 +0100 |
commit | 5aa8f18b6681711f683e5d56c0f569a2a4145571 (patch) | |
tree | 20f796677ce31c7e6df816895eed6d1c333ba3ab | |
parent | 45c824549f9a7fa38df04ae8648d1647c82ba9b4 (diff) | |
download | pylint-git-5aa8f18b6681711f683e5d56c0f569a2a4145571.tar.gz |
Introduced DeprecatedMixin
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | pylint/checkers/__init__.py | 2 | ||||
-rw-r--r-- | pylint/checkers/deprecated.py | 49 | ||||
-rw-r--r-- | pylint/checkers/stdlib.py | 46 | ||||
-rw-r--r-- | tests/checkers/unittest_deprecated.py | 99 |
5 files changed, 166 insertions, 34 deletions
@@ -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) |