|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a node visitor for checking the use of deprecated 'typing' symbols. |
|
8 """ |
|
9 |
|
10 # |
|
11 # The visitors are adapted and extended variants of the ones found in |
|
12 # flake8-pep585 v0.1.7 |
|
13 # |
|
14 |
|
15 import ast |
|
16 |
|
17 |
|
18 class AnnotationsDeprecationsVisitor(ast.NodeVisitor): |
|
19 """ |
|
20 Class implementing a node visitor for checking the use of deprecated 'typing' |
|
21 symbols. |
|
22 """ |
|
23 |
|
24 NameReplacements = { |
|
25 "Tuple": "tuple", |
|
26 "List": "list", |
|
27 "Dict": "dict", |
|
28 "Set": "set", |
|
29 "FrozenSet": "frozenset", |
|
30 "Type": "type", |
|
31 "Deque": "collections.deque", |
|
32 "DefaultDict": "collections.defaultdict", |
|
33 "OrderedDict": "collections.OrderedDict", |
|
34 "Counter": "collections.Counter", |
|
35 "ChainMap": "collections.ChainMap", |
|
36 "Awaitable": "collections.abc.Awaitable", |
|
37 "Coroutine": "collections.abc.Coroutine", |
|
38 "AsyncIterable": "collections.abc.AsyncIterable", |
|
39 "AsyncIterator": "collections.abc.AsyncIterator", |
|
40 "AsyncGenerator": "collections.abc.AsyncGenerator", |
|
41 "Iterable": "collections.abc.Iterable", |
|
42 "Iterator": "collections.abc.Iterator", |
|
43 "Generator": "collections.abc.Generator", |
|
44 "Reversible": "collections.abc.Reversible", |
|
45 "Container": "collections.abc.Container", |
|
46 "Collection": "collections.abc.Collection", |
|
47 "Callable": "collections.abc.Callable", |
|
48 "AbstractSet": "collections.abc.Set", |
|
49 "MutableSet": "collections.abc.MutableSet", |
|
50 "Mapping": "collections.abc.Mapping", |
|
51 "MutableMapping": "collections.abc.MutableMapping", |
|
52 "Sequence": "collections.abc.Sequence", |
|
53 "MutableSequence": "collections.abc.MutableSequence", |
|
54 "ByteString": "collections.abc.ByteString", |
|
55 "MappingView": "collections.abc.MappingView", |
|
56 "KeysView": "collections.abc.KeysView", |
|
57 "ItemsView": "collections.abc.ItemsView", |
|
58 "ValuesView": "collections.abc.ValuesView", |
|
59 "ContextManager": "contextlib.AbstractContextManager", |
|
60 "AsyncContextManager": "contextlib.AbstractAsyncContextManager", |
|
61 "Pattern": "re.Pattern", |
|
62 "Match": "re.Match", |
|
63 } |
|
64 |
|
65 def __init__(self, exemptedList): |
|
66 """ |
|
67 Constructor |
|
68 |
|
69 @param exemptedList list of typing symbols exempted from checking |
|
70 @type list of str |
|
71 """ |
|
72 self.__exemptedList = exemptedList[:] |
|
73 self.__typingAliases = set() |
|
74 |
|
75 self.__issues = [] |
|
76 |
|
77 def getIssues(self): |
|
78 """ |
|
79 Public method to get the list of detected issues. |
|
80 |
|
81 @return list of detected issues consisting of a tuple of a reference to the node |
|
82 and a tuple containing the used name and the suggested replacement |
|
83 @rtype list of tuples of (ast.AST, (str, str)) |
|
84 """ |
|
85 return self.__issues |
|
86 |
|
87 def __checkDeprecation(self, node, name): |
|
88 """ |
|
89 Private method to check, if the given name is deprecated. |
|
90 |
|
91 @param node reference to the node |
|
92 @type ast.ImportFrom, ast.Attribute |
|
93 @param name name to be checked |
|
94 @type str |
|
95 """ |
|
96 if name not in self.__exemptedList: |
|
97 replacement = self.NameReplacements.get(name) |
|
98 if replacement is not None: |
|
99 self.__issues.append((node, (name, replacement))) |
|
100 |
|
101 def visit_ImportFrom(self, node): |
|
102 """ |
|
103 Public method to handle an ast.ImportFrom node. |
|
104 |
|
105 @param node reference to the node to be handled |
|
106 @type ast.ImportFrom |
|
107 """ |
|
108 if node.module == "typing": |
|
109 for alias in node.names: |
|
110 self.__checkDeprecation(node, alias.name) |
|
111 |
|
112 def visit_Import(self, node): |
|
113 """ |
|
114 Public method to handle an ast.Import node. |
|
115 |
|
116 @param node reference to the node to be handled |
|
117 @type ast.Import |
|
118 """ |
|
119 for alias in node.names: |
|
120 if alias.name == "typing": |
|
121 self.__typingAliases.add(alias.asname or "typing") |
|
122 |
|
123 def visit_Attribute(self, node): |
|
124 """ |
|
125 Public method to handle an ast.Attribute node. |
|
126 |
|
127 @param node reference to the node to be handled |
|
128 @type ast.Attribute |
|
129 """ |
|
130 if ( |
|
131 isinstance(node.value, ast.Name) |
|
132 and node.value.id in self.__typingAliases |
|
133 and node.attr not in self.__exemptedList |
|
134 ): |
|
135 self.__checkDeprecation(node, node.attr) |
|
136 |
|
137 def visit_AnnAssign(self, node): |
|
138 """ |
|
139 Public method to handle an ast.AnnAssign node. |
|
140 |
|
141 @param node reference to the node to be handled |
|
142 @type ast.AnnAssign |
|
143 """ |
|
144 if isinstance(node.annotation, ast.Name): |
|
145 self.__checkDeprecation(node, node.annotation.id) |
|
146 elif isinstance(node.annotation, ast.Subscript) and isinstance( |
|
147 node.annotation.value, ast.Name |
|
148 ): |
|
149 self.__checkDeprecation(node, node.annotation.value.id) |
|
150 |
|
151 def visit_FunctionDef(self, node): |
|
152 """ |
|
153 Public method to handle an ast.FunctionDef or ast.AsyncFunctionDef node. |
|
154 |
|
155 @param node reference to the node to be handled |
|
156 @type ast.FunctionDef or ast.AsyncFunctionDef |
|
157 """ |
|
158 for arg in node.args.args + node.args.posonlyargs + node.args.kwonlyargs: |
|
159 if isinstance(arg.annotation, ast.Name): |
|
160 self.__checkDeprecation(arg, arg.annotation.id) |
|
161 elif isinstance(arg.annotation, ast.Subscript) and isinstance( |
|
162 arg.annotation.value, ast.Name |
|
163 ): |
|
164 self.__checkDeprecation(arg, arg.annotation.value.id) |
|
165 |
|
166 visit_AsyncFunctionDef = visit_FunctionDef |
|
167 |
|
168 |
|
169 class AnnotationsFutureImportVisitor(ast.NodeVisitor): |
|
170 """ |
|
171 Class implementing a node visitor to dtermine, if the annotations __future__ |
|
172 import is present. |
|
173 |
|
174 This class is used to determine usage of annotations for Python 3.8. |
|
175 """ |
|
176 |
|
177 def __init__(self): |
|
178 """ |
|
179 Constructor |
|
180 """ |
|
181 self.__futureImport = False |
|
182 |
|
183 def futureImportPresent(self): |
|
184 """ |
|
185 Public method to check, if a 'from __future__ import annotations' statement |
|
186 exists. |
|
187 |
|
188 @return flag indicating the existence of the import statement |
|
189 @rtype bool |
|
190 """ |
|
191 return self.__futureImport |
|
192 |
|
193 def visit_ImportFrom(self, node): |
|
194 """ |
|
195 Public method to handle an ast.ImportFrom node. |
|
196 |
|
197 @param node reference to the node to be handled |
|
198 @type ast.ImportFrom |
|
199 """ |
|
200 if node.module == "__future__" and "annotations" in { |
|
201 alias.name for alias in node.names |
|
202 }: |
|
203 self.__futureImport = True |