Coverage for C:\Repos\ekr-pylint\pylint\checkers\deprecated.py: 34%
88 statements
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
1# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
2# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
3# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
5"""Checker mixin for deprecated functionality."""
7from __future__ import annotations
9from collections.abc import Container, Iterable
10from itertools import chain
11from typing import Any
13import astroid
14from astroid import nodes
16from pylint.checkers import utils
17from pylint.checkers.base_checker import BaseChecker
18from pylint.checkers.utils import get_import_name, infer_all, safe_infer
20ACCEPTABLE_NODES = (
21 astroid.BoundMethod,
22 astroid.UnboundMethod,
23 nodes.FunctionDef,
24 nodes.ClassDef,
25)
28class DeprecatedMixin(BaseChecker):
29 """A mixin implementing logic for checking deprecated symbols.
31 A class implementing mixin must define "deprecated-method" Message.
32 """
34 msgs: Any = {
35 "W1505": (
36 "Using deprecated method %s()",
37 "deprecated-method",
38 "The method is marked as deprecated and will be removed in the future.",
39 ),
40 "W1511": (
41 "Using deprecated argument %s of method %s()",
42 "deprecated-argument",
43 "The argument is marked as deprecated and will be removed in the future.",
44 ),
45 "W0402": (
46 "Deprecated module %r",
47 "deprecated-module",
48 "A module marked as deprecated is imported.",
49 ),
50 "W1512": (
51 "Using deprecated class %s of module %s",
52 "deprecated-class",
53 "The class is marked as deprecated and will be removed in the future.",
54 ),
55 "W1513": (
56 "Using deprecated decorator %s()",
57 "deprecated-decorator",
58 "The decorator is marked as deprecated and will be removed in the future.",
59 ),
60 }
62 @utils.only_required_for_messages(
63 "deprecated-method",
64 "deprecated-argument",
65 "deprecated-class",
66 )
67 def visit_call(self, node: nodes.Call) -> None:
68 """Called when a :class:`nodes.Call` node is visited."""
69 self.check_deprecated_class_in_call(node)
70 for inferred in infer_all(node.func):
71 # Calling entry point for deprecation check logic.
72 self.check_deprecated_method(node, inferred)
74 @utils.only_required_for_messages(
75 "deprecated-module",
76 "deprecated-class",
77 )
78 def visit_import(self, node: nodes.Import) -> None:
79 """Triggered when an import statement is seen."""
80 for name in (name for name, _ in node.names):
81 self.check_deprecated_module(node, name)
82 if "." in name:
83 # Checking deprecation for import module with class
84 mod_name, class_name = name.split(".", 1)
85 self.check_deprecated_class(node, mod_name, (class_name,))
87 def deprecated_decorators(self) -> Iterable:
88 """Callback returning the deprecated decorators.
90 Returns:
91 collections.abc.Container of deprecated decorator names.
92 """
93 return ()
95 @utils.only_required_for_messages("deprecated-decorator")
96 def visit_decorators(self, node: nodes.Decorators) -> None:
97 """Triggered when a decorator statement is seen."""
98 children = list(node.get_children())
99 if not children:
100 return
101 if isinstance(children[0], nodes.Call):
102 inf = safe_infer(children[0].func)
103 else:
104 inf = safe_infer(children[0])
105 qname = inf.qname() if inf else None
106 if qname in self.deprecated_decorators():
107 self.add_message("deprecated-decorator", node=node, args=qname)
109 @utils.only_required_for_messages(
110 "deprecated-module",
111 "deprecated-class",
112 )
113 def visit_importfrom(self, node: nodes.ImportFrom) -> None:
114 """Triggered when a from statement is seen."""
115 basename = node.modname
116 basename = get_import_name(node, basename)
117 self.check_deprecated_module(node, basename)
118 class_names = (name for name, _ in node.names)
119 self.check_deprecated_class(node, basename, class_names)
121 def deprecated_methods(self) -> Container[str]:
122 """Callback returning the deprecated methods/functions.
124 Returns:
125 collections.abc.Container of deprecated function/method names.
126 """
127 return ()
129 def deprecated_arguments(self, method: str) -> Iterable[tuple[int | None, str]]:
130 """Callback returning the deprecated arguments of method/function.
132 Args:
133 method (str): name of function/method checked for deprecated arguments
135 Returns:
136 collections.abc.Iterable in form:
137 ((POSITION1, PARAM1), (POSITION2: PARAM2) ...)
138 where
139 * POSITIONX - position of deprecated argument PARAMX in function definition.
140 If argument is keyword-only, POSITIONX should be None.
141 * PARAMX - name of the deprecated argument.
142 E.g. suppose function:
144 .. code-block:: python
145 def bar(arg1, arg2, arg3, arg4, arg5='spam')
147 with deprecated arguments `arg2` and `arg4`. `deprecated_arguments` should return:
149 .. code-block:: python
150 ((1, 'arg2'), (3, 'arg4'))
151 """
152 # pylint: disable=unused-argument
153 return ()
155 def deprecated_modules(self) -> Iterable:
156 """Callback returning the deprecated modules.
158 Returns:
159 collections.abc.Container of deprecated module names.
160 """
161 return ()
163 def deprecated_classes(self, module: str) -> Iterable:
164 """Callback returning the deprecated classes of module.
166 Args:
167 module (str): name of module checked for deprecated classes
169 Returns:
170 collections.abc.Container of deprecated class names.
171 """
172 # pylint: disable=unused-argument
173 return ()
175 def check_deprecated_module(self, node, mod_path):
176 """Checks if the module is deprecated."""
177 for mod_name in self.deprecated_modules():
178 if mod_path == mod_name or mod_path.startswith(mod_name + "."):
179 self.add_message("deprecated-module", node=node, args=mod_path)
181 def check_deprecated_method(self, node, inferred):
182 """Executes the checker for the given node.
184 This method should be called from the checker implementing this mixin.
185 """
187 # Reject nodes which aren't of interest to us.
188 if not isinstance(inferred, ACCEPTABLE_NODES):
189 return
191 if isinstance(node.func, nodes.Attribute):
192 func_name = node.func.attrname
193 elif isinstance(node.func, nodes.Name):
194 func_name = node.func.name
195 else:
196 # Not interested in other nodes.
197 return
199 if hasattr(inferred.parent, "qname") and inferred.parent.qname():
200 # Handling the situation when deprecated function is
201 # alias to existing function.
202 qnames = {
203 inferred.qname(),
204 f"{inferred.parent.qname()}.{func_name}",
205 func_name,
206 }
207 else:
208 qnames = {inferred.qname(), func_name}
209 if any(name in self.deprecated_methods() for name in qnames):
210 self.add_message("deprecated-method", node=node, args=(func_name,))
211 return
212 num_of_args = len(node.args)
213 kwargs = {kw.arg for kw in node.keywords} if node.keywords else {}
214 deprecated_arguments = (self.deprecated_arguments(qn) for qn in qnames)
215 for position, arg_name in chain(*deprecated_arguments):
216 if arg_name in kwargs:
217 # function was called with deprecated argument as keyword argument
218 self.add_message(
219 "deprecated-argument", node=node, args=(arg_name, func_name)
220 )
221 elif position is not None and position < num_of_args:
222 # function was called with deprecated argument as positional argument
223 self.add_message(
224 "deprecated-argument", node=node, args=(arg_name, func_name)
225 )
227 def check_deprecated_class(self, node, mod_name, class_names):
228 """Checks if the class is deprecated."""
230 for class_name in class_names:
231 if class_name in self.deprecated_classes(mod_name):
232 self.add_message(
233 "deprecated-class", node=node, args=(class_name, mod_name)
234 )
236 def check_deprecated_class_in_call(self, node):
237 """Checks if call the deprecated class."""
239 if isinstance(node.func, nodes.Attribute) and isinstance(
240 node.func.expr, nodes.Name
241 ):
242 mod_name = node.func.expr.name
243 class_name = node.func.attrname
244 self.check_deprecated_class(node, mod_name, (class_name,))