summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatus Valo <matusvalo@gmail.com>2021-02-22 00:26:53 +0100
committerPierre Sassoulas <pierre.sassoulas@gmail.com>2021-03-06 17:43:08 +0100
commit6721cd1cf2da0294124b75d382a042e23ec27d47 (patch)
tree1820e79e37898629f8fce21abc3e6565a7adf298
parentd7cc8bedd1be8f69abc9e981775498569cae18c2 (diff)
downloadpylint-git-6721cd1cf2da0294124b75d382a042e23ec27d47.tar.gz
Introduced deprecated attributes
-rw-r--r--ChangeLog2
-rw-r--r--doc/whatsnew/2.8.rst1
-rw-r--r--examples/deprecation_checker.py62
-rw-r--r--pylint/checkers/deprecated.py75
-rw-r--r--tests/checkers/unittest_deprecated.py302
5 files changed, 393 insertions, 49 deletions
diff --git a/ChangeLog b/ChangeLog
index 4211c8814..5558d0157 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -9,6 +9,8 @@ Release date: TBA
..
Put new features here
+* Introduce logic for checking deprecated attributes in DeprecationMixin.
+
What's New in Pylint 2.7.3?
===========================
diff --git a/doc/whatsnew/2.8.rst b/doc/whatsnew/2.8.rst
index f7806b1a9..7768edd86 100644
--- a/doc/whatsnew/2.8.rst
+++ b/doc/whatsnew/2.8.rst
@@ -12,6 +12,7 @@ Summary -- Release highlights
New checkers
============
+* Add ``deprecated-argument`` check for deprecated arguments.
Other Changes
=============
diff --git a/examples/deprecation_checker.py b/examples/deprecation_checker.py
index 382f76bb3..03b8e3e36 100644
--- a/examples/deprecation_checker.py
+++ b/examples/deprecation_checker.py
@@ -8,28 +8,39 @@ from module mymodule:
def deprecated_function():
pass
+ def myfunction(arg0, arg1, deprecated_arg1=None, arg2='foo', arg3='bar', deprecated_arg2='spam'):
+ pass
+
class MyClass:
def deprecated_method(self):
pass
+ def mymethod(self, arg0, arg1, deprecated1=None, arg2='foo', deprecated2='bar', arg3='spam'):
+ pass
+
$ cat mymain.py
- from mymodule import deprecated_function, MyClass
+ from mymodule import deprecated_function, myfunction, MyClass
deprecated_function()
+ myfunction(0, 1, 'deprecated_arg1', deprecated_arg2=None)
MyClass().deprecated_method()
+ MyClass().mymethod(0, 1, deprecated1=None, deprecated2=None)
$ pylint --load-plugins=deprecation_checker mymain.py
************* Module mymain
mymain.py:3:0: W1505: Using deprecated method deprecated_function() (deprecated-method)
- mymain.py:4:0: W1505: Using deprecated method deprecated_method() (deprecated-method)
+ mymain.py:4:0: W1511: Using deprecated argument deprecated_arg1 of method myfunction() (deprecated-argument)
+ mymain.py:4:0: W1511: Using deprecated argument deprecated_arg2 of method myfunction() (deprecated-argument)
+ mymain.py:5:0: W1505: Using deprecated method deprecated_method() (deprecated-method)
+ mymain.py:6:0: W1511: Using deprecated argument deprecated1 of method mymethod() (deprecated-argument)
+ mymain.py:6:0: W1511: Using deprecated argument deprecated2 of method mymethod() (deprecated-argument)
------------------------------------------------------------------
- Your code has been rated at 3.33/10 (previous run: 3.33/10, +0.00)
+ Your code has been rated at 2.00/10 (previous run: 2.00/10, +0.00)
"""
-import astroid
-from pylint.checkers import BaseChecker, DeprecatedMixin, utils
+from pylint.checkers import BaseChecker, DeprecatedMixin
from pylint.interfaces import IAstroidChecker
@@ -45,24 +56,6 @@ class DeprecationChecker(DeprecatedMixin, BaseChecker):
# The name defines a custom section of the config for this checker.
name = "deprecated"
- @utils.check_messages(
- "deprecated-method",
- )
- def visit_call(self, node):
- """Called when a :class:`.astroid.node_classes.Call` node is visited.
-
- See :mod:`astroid` for the description of available nodes.
-
- :param node: The node to check.
- :type node: astroid.node_classes.Call
- """
- try:
- for inferred in node.func.infer():
- # Calling entry point for deprecation check logic.
- self.check_deprecated_method(node, inferred)
- except astroid.InferenceError:
- return
-
def deprecated_methods(self):
"""Callback method called by DeprecatedMixin for every method/function found in the code.
@@ -71,6 +64,29 @@ class DeprecationChecker(DeprecatedMixin, BaseChecker):
"""
return {"mymodule.deprecated_function", "mymodule.MyClass.deprecated_method"}
+ def deprecated_arguments(self, method: str):
+ """Callback returning the deprecated arguments of method/function.
+
+ Returns:
+ collections.abc.Iterable in form:
+ ((POSITION1, PARAM1), (POSITION2: PARAM2) ...)
+ where
+ * POSITIONX - position of deprecated argument PARAMX in function definition.
+ If argument is keyword-only, POSITIONX should be None.
+ * PARAMX - name of the deprecated argument.
+ """
+ if method == "mymodule.myfunction":
+ # myfunction() has two deprecated arguments:
+ # * deprecated_arg1 defined at 2nd position and
+ # * deprecated_arg2 defined at 5th position.
+ return ((2, "deprecated_arg1"), (5, "deprecated_arg2"))
+ if method == "mymodule.MyClass.mymethod":
+ # mymethod() has two deprecated arguments:
+ # * deprecated1 defined at 2nd position and
+ # * deprecated2 defined at 4th position.
+ return ((2, "deprecated1"), (4, "deprecated2"))
+ return ()
+
def register(linter):
"""This required method auto registers the checker.
diff --git a/pylint/checkers/deprecated.py b/pylint/checkers/deprecated.py
index 54254340d..358c88ee4 100644
--- a/pylint/checkers/deprecated.py
+++ b/pylint/checkers/deprecated.py
@@ -3,11 +3,13 @@
"""Checker mixin for deprecated functionality."""
-import abc
+from itertools import chain
from typing import Any
import astroid
+from pylint.checkers import utils
+
ACCEPTABLE_NODES = (
astroid.BoundMethod,
astroid.UnboundMethod,
@@ -15,9 +17,9 @@ ACCEPTABLE_NODES = (
)
-class DeprecatedMixin(metaclass=abc.ABCMeta):
+class DeprecatedMixin:
"""A mixin implementing logic for checking deprecated symbols.
- A class imlementing mixin must define "deprecated-method" Message.
+ A class implementing mixin must define "deprecated-method" Message.
"""
msgs: Any = {
@@ -26,15 +28,65 @@ class DeprecatedMixin(metaclass=abc.ABCMeta):
"deprecated-method",
"The method is marked as deprecated and will be removed in the future.",
),
+ "W1511": (
+ "Using deprecated argument %s of method %s()",
+ "deprecated-argument",
+ "The argument is marked as deprecated and will be removed in the future.",
+ ),
}
- @abc.abstractmethod
+ @utils.check_messages(
+ "deprecated-method",
+ "deprecated-argument",
+ )
+ def visit_call(self, node):
+ """Called when a :class:`.astroid.node_classes.Call` node is visited.
+
+ Args:
+ node (astroid.node_classes.Call): The node to check.
+ """
+ try:
+ for inferred in node.func.infer():
+ # Calling entry point for deprecation check logic.
+ self.check_deprecated_method(node, inferred)
+ except astroid.InferenceError:
+ return
+
def deprecated_methods(self):
"""Callback returning the deprecated methods/functions.
Returns:
collections.abc.Container of deprecated function/method names.
"""
+ # pylint: disable=no-self-use
+ return ()
+
+ def deprecated_arguments(self, method: str):
+ """Callback returning the deprecated arguments of method/function.
+
+ Args:
+ method (str): name of function/method checked for deprecated arguments
+
+ Returns:
+ collections.abc.Iterable in form:
+ ((POSITION1, PARAM1), (POSITION2: PARAM2) ...)
+ where
+ * POSITIONX - position of deprecated argument PARAMX in function definition.
+ If argument is keyword-only, POSITIONX should be None.
+ * PARAMX - name of the deprecated argument.
+ E.g. suppose function:
+
+ .. code-block:: python
+ def bar(arg1, arg2, arg3, arg4, arg5='spam')
+
+ with deprecated arguments `arg2` and `arg4`. `deprecated_arguments` should return:
+
+ .. code-block:: python
+ ((1, 'arg2'), (3, 'arg4'))
+ """
+ # pylint: disable=no-self-use
+ # pylint: disable=unused-argument
+ return ()
def check_deprecated_method(self, node, inferred):
"""Executes the checker for the given node. This method should
@@ -56,3 +108,18 @@ class DeprecatedMixin(metaclass=abc.ABCMeta):
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,))
+ num_of_args = len(node.args)
+ kwargs = {kw.arg for kw in node.keywords} if node.keywords else {}
+ for position, arg_name in chain(
+ self.deprecated_arguments(func_name), self.deprecated_arguments(qname)
+ ):
+ if arg_name in kwargs:
+ # function was called with deprecated argument as keyword argument
+ self.add_message(
+ "deprecated-argument", node=node, args=(arg_name, func_name)
+ )
+ elif position is not None and position < num_of_args:
+ # function was called with deprecated argument as positional argument
+ self.add_message(
+ "deprecated-argument", node=node, args=(arg_name, func_name)
+ )
diff --git a/tests/checkers/unittest_deprecated.py b/tests/checkers/unittest_deprecated.py
index 42e78a605..f7e8c1309 100644
--- a/tests/checkers/unittest_deprecated.py
+++ b/tests/checkers/unittest_deprecated.py
@@ -1,6 +1,6 @@
import astroid
-from pylint.checkers import BaseChecker, DeprecatedMixin, utils
+from pylint.checkers import BaseChecker, DeprecatedMixin
from pylint.interfaces import UNDEFINED, IAstroidChecker
from pylint.testutils import CheckerTestCase, Message
@@ -9,30 +9,30 @@ class _DeprecatedChecker(DeprecatedMixin, BaseChecker):
__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"}
+ def deprecated_arguments(self, method):
+ if method == "myfunction1":
+ # def myfunction1(arg1, deprecated_arg1='spam')
+ return ((1, "deprecated_arg1"),)
+ if method == "myfunction2":
+ # def myfunction2(arg1, deprecated_arg1, arg2='foo', deprecated_arg2='spam'))
+ return ((1, "deprecated_arg1"), (3, "deprecated_arg2"))
+ if method == "myfunction3":
+ # def myfunction1(arg1, *, deprecated_arg1='spam')
+ return ((None, "deprecated_arg1"),)
+ if method == ".MyClass.mymethod1":
+ # def mymethod1(self, arg1, deprecated_arg1=None)
+ return ((1, "deprecated_arg1"),)
+ if method == ".MyClass.mymethod2":
+ # def mymethod2(self, arg1, deprecated_arg1='bar', arg2='foo', deprecated_arg2='spam'))
+ return ((1, "deprecated_arg1"), (3, "deprecated_arg2"))
+ if method == ".MyClass.mymethod3":
+ # def mymethod1(self, arg1, *, deprecated_arg1=None)
+ return ((None, "deprecated_arg1"),)
+ return ()
+
class TestDeprecatedChecker(CheckerTestCase):
CHECKER_CLASS = _DeprecatedChecker
@@ -97,3 +97,261 @@ class TestDeprecatedChecker(CheckerTestCase):
)
with self.assertNoMessages():
self.checker.visit_call(node)
+
+ def test_function_deprecated_arg(self):
+ # Tests raising error when calling function with deprecated argument
+ node = astroid.extract_node(
+ """
+ def myfunction1(arg1, deprecated_arg1='spam'):
+ pass
+
+ myfunction1(None, 'deprecated')
+ """
+ )
+ with self.assertAddsMessages(
+ Message(
+ msg_id="deprecated-argument",
+ args=("deprecated_arg1", "myfunction1"),
+ node=node,
+ confidence=UNDEFINED,
+ )
+ ):
+ self.checker.visit_call(node)
+
+ def test_function_deprecated_kwarg(self):
+ # Tests raising error when calling function with deprecated keyword argument
+ node = astroid.extract_node(
+ """
+ def myfunction1(arg1, deprecated_arg1='spam'):
+ pass
+
+ myfunction1(None, deprecated_arg1='deprecated')
+ """
+ )
+ with self.assertAddsMessages(
+ Message(
+ msg_id="deprecated-argument",
+ args=("deprecated_arg1", "myfunction1"),
+ node=node,
+ confidence=UNDEFINED,
+ )
+ ):
+ self.checker.visit_call(node)
+
+ def test_function_deprecated_not_used(self):
+ # Tests raising error when calling function without deprecated argument
+ node = astroid.extract_node(
+ """
+ def myfunction1(arg1, deprecated_arg1='spam'):
+ pass
+
+ myfunction1(None)
+ """
+ )
+ with self.assertNoMessages():
+ self.checker.visit_call(node)
+
+ def test_function_deprecated_kwarg_only(self):
+ # Tests raising error when calling function with deprecated keyword only argument
+ node = astroid.extract_node(
+ """
+ def myfunction3(arg1, *, deprecated_arg1='spam'):
+ pass
+
+ myfunction3(None, deprecated_arg1='deprecated')
+ """
+ )
+ with self.assertAddsMessages(
+ Message(
+ msg_id="deprecated-argument",
+ args=("deprecated_arg1", "myfunction3"),
+ node=node,
+ confidence=UNDEFINED,
+ )
+ ):
+ self.checker.visit_call(node)
+
+ def test_method_deprecated_arg(self):
+ # Tests raising error when calling method with deprecated argument
+ node = astroid.extract_node(
+ """
+ class MyClass:
+ def mymethod1(self, arg1, deprecated_arg1):
+ pass
+
+ MyClass().mymethod1(None, 'deprecated')
+ """
+ )
+ with self.assertAddsMessages(
+ Message(
+ msg_id="deprecated-argument",
+ args=("deprecated_arg1", "mymethod1"),
+ node=node,
+ confidence=UNDEFINED,
+ )
+ ):
+ self.checker.visit_call(node)
+
+ def test_method_deprecated_kwarg(self):
+ # Tests raising error when calling method with deprecated keyword argument
+ node = astroid.extract_node(
+ """
+ class MyClass:
+ def mymethod1(self, arg1, deprecated_arg1):
+ pass
+
+ MyClass().mymethod1(None, deprecated_arg1='deprecated')
+ """
+ )
+ with self.assertAddsMessages(
+ Message(
+ msg_id="deprecated-argument",
+ args=("deprecated_arg1", "mymethod1"),
+ node=node,
+ confidence=UNDEFINED,
+ )
+ ):
+ self.checker.visit_call(node)
+
+ def test_method_deprecated_not_used(self):
+ # Tests raising error when calling method without deprecated argument
+ node = astroid.extract_node(
+ """
+ class MyClass:
+ def mymethod1(self, arg1, deprecated_arg1):
+ pass
+
+ MyClass().mymethod1(None)
+ """
+ )
+ with self.assertNoMessages():
+ self.checker.visit_call(node)
+
+ def test_method_deprecated_kwarg_only(self):
+ # Tests raising error when calling method with deprecated keyword only argument
+ node = astroid.extract_node(
+ """
+ class MyClass:
+ def mymethod3(self, arg1, *, deprecated_arg1):
+ pass
+
+ MyClass().mymethod3(None, deprecated_arg1='deprecated')
+ """
+ )
+ with self.assertAddsMessages(
+ Message(
+ msg_id="deprecated-argument",
+ args=("deprecated_arg1", "mymethod3"),
+ node=node,
+ confidence=UNDEFINED,
+ )
+ ):
+ self.checker.visit_call(node)
+
+ def test_function_deprecated_arg_kwargs(self):
+ # Tests raising error when calling function with deprecated argument
+ # and keyword argument
+ node = astroid.extract_node(
+ """
+ def myfunction2(arg1, deprecated_arg1, arg2='foo', deprecated_arg2='spam'):
+ pass
+
+ myfunction2(None, 'deprecated', deprecated_arg2='deprecated')
+ """
+ )
+ with self.assertAddsMessages(
+ Message(
+ msg_id="deprecated-argument",
+ args=("deprecated_arg1", "myfunction2"),
+ node=node,
+ confidence=UNDEFINED,
+ ),
+ Message(
+ msg_id="deprecated-argument",
+ args=("deprecated_arg2", "myfunction2"),
+ node=node,
+ confidence=UNDEFINED,
+ ),
+ ):
+ self.checker.visit_call(node)
+
+ def test_function_deprecated_kwarg_kwarg(self):
+ # Tests raising error when calling function with deprecated keyword arguments
+ node = astroid.extract_node(
+ """
+ def myfunction2(arg1, deprecated_arg1, arg2='foo', deprecated_arg2='spam'):
+ pass
+
+ myfunction2(None, deprecated_arg1='deprecated', deprecated_arg2='deprecated')
+ """
+ )
+ with self.assertAddsMessages(
+ Message(
+ msg_id="deprecated-argument",
+ args=("deprecated_arg1", "myfunction2"),
+ node=node,
+ confidence=UNDEFINED,
+ ),
+ Message(
+ msg_id="deprecated-argument",
+ args=("deprecated_arg2", "myfunction2"),
+ node=node,
+ confidence=UNDEFINED,
+ ),
+ ):
+ self.checker.visit_call(node)
+
+ def test_method_deprecated_arg_kwargs(self):
+ # Tests raising error when calling method with deprecated argument
+ # and keyword argument
+ node = astroid.extract_node(
+ """
+ class MyClass:
+ def mymethod2(self, arg1, deprecated_arg1, arg2='foo', deprecated_arg2='spam'):
+ pass
+
+ MyClass().mymethod2(None, 'deprecated', deprecated_arg2='deprecated')
+ """
+ )
+ with self.assertAddsMessages(
+ Message(
+ msg_id="deprecated-argument",
+ args=("deprecated_arg1", "mymethod2"),
+ node=node,
+ confidence=UNDEFINED,
+ ),
+ Message(
+ msg_id="deprecated-argument",
+ args=("deprecated_arg2", "mymethod2"),
+ node=node,
+ confidence=UNDEFINED,
+ ),
+ ):
+ self.checker.visit_call(node)
+
+ def test_method_deprecated_kwarg_kwarg(self):
+ # Tests raising error when calling method with deprecated keyword arguments
+ node = astroid.extract_node(
+ """
+ class MyClass:
+ def mymethod2(self, arg1, deprecated_arg1, arg2='foo', deprecated_arg2='spam'):
+ pass
+
+ MyClass().mymethod2(None, deprecated_arg1='deprecated', deprecated_arg2='deprecated')
+ """
+ )
+ with self.assertAddsMessages(
+ Message(
+ msg_id="deprecated-argument",
+ args=("deprecated_arg1", "mymethod2"),
+ node=node,
+ confidence=UNDEFINED,
+ ),
+ Message(
+ msg_id="deprecated-argument",
+ args=("deprecated_arg2", "mymethod2"),
+ node=node,
+ confidence=UNDEFINED,
+ ),
+ ):
+ self.checker.visit_call(node)