63 "open": "P301", |
64 "open": "P301", |
64 |
65 |
65 "py.path.local": "P401", |
66 "py.path.local": "P401", |
66 } |
67 } |
67 |
68 |
68 def __init__(self, source, filename, selected, ignored, expected, repeat): |
69 def __init__(self, source, filename, tree, selected, ignored, expected, |
|
70 repeat): |
69 """ |
71 """ |
70 Constructor |
72 Constructor |
71 |
73 |
72 @param source source code to be checked |
74 @param source source code to be checked |
73 @type list of str |
75 @type list of str |
74 @param filename name of the source file |
76 @param filename name of the source file |
75 @type str |
77 @type str |
|
78 @param tree AST tree of the source code |
|
79 @type ast.Module |
76 @param selected list of selected codes |
80 @param selected list of selected codes |
77 @type list of str |
81 @type list of str |
78 @param ignored list of codes to be ignored |
82 @param ignored list of codes to be ignored |
79 @type list of str |
83 @type list of str |
80 @param expected list of expected codes |
84 @param expected list of expected codes |
86 self.__ignore = ('',) if selected else tuple(ignored) |
90 self.__ignore = ('',) if selected else tuple(ignored) |
87 self.__expected = expected[:] |
91 self.__expected = expected[:] |
88 self.__repeat = repeat |
92 self.__repeat = repeat |
89 self.__filename = filename |
93 self.__filename = filename |
90 self.__source = source[:] |
94 self.__source = source[:] |
|
95 self.__tree = copy.deepcopy(tree) |
91 |
96 |
92 # statistics counters |
97 # statistics counters |
93 self.counters = {} |
98 self.counters = {} |
94 |
99 |
95 # collection of detected errors |
100 # collection of detected errors |
145 "code": code, |
150 "code": code, |
146 "args": args, |
151 "args": args, |
147 } |
152 } |
148 ) |
153 ) |
149 |
154 |
150 def __reportInvalidSyntax(self): |
|
151 """ |
|
152 Private method to report a syntax error. |
|
153 """ |
|
154 exc_type, exc = sys.exc_info()[:2] |
|
155 if len(exc.args) > 1: |
|
156 offset = exc.args[1] |
|
157 if len(offset) > 2: |
|
158 offset = offset[1:3] |
|
159 else: |
|
160 offset = (1, 0) |
|
161 self.__error(offset[0] - 1, offset[1] or 0, |
|
162 'M901', exc_type.__name__, exc.args[0]) |
|
163 |
|
164 def __generateTree(self): |
|
165 """ |
|
166 Private method to generate an AST for our source. |
|
167 |
|
168 @return generated AST |
|
169 @rtype ast.AST |
|
170 """ |
|
171 return ast.parse("".join(self.__source), self.__filename) |
|
172 |
|
173 def run(self): |
155 def run(self): |
174 """ |
156 """ |
175 Public method to check the given source against functions |
157 Public method to check the given source against functions |
176 to be replaced by 'pathlib' equivalents. |
158 to be replaced by 'pathlib' equivalents. |
177 """ |
159 """ |
179 # don't do anything, if essential data is missing |
161 # don't do anything, if essential data is missing |
180 return |
162 return |
181 |
163 |
182 if not self.__checkCodes: |
164 if not self.__checkCodes: |
183 # don't do anything, if no codes were selected |
165 # don't do anything, if no codes were selected |
184 return |
|
185 |
|
186 try: |
|
187 self.__tree = self.__generateTree() |
|
188 except (SyntaxError, TypeError): |
|
189 self.__reportInvalidSyntax() |
|
190 return |
166 return |
191 |
167 |
192 visitor = PathlibVisitor(self.__checkForReplacement) |
168 visitor = PathlibVisitor(self.__checkForReplacement) |
193 visitor.visit(self.__tree) |
169 visitor.visit(self.__tree) |
194 |
170 |
200 @param node reference to the AST node to check |
176 @param node reference to the AST node to check |
201 @type ast.AST |
177 @type ast.AST |
202 @param name resolved name of the node |
178 @param name resolved name of the node |
203 @type str |
179 @type str |
204 """ |
180 """ |
205 try: |
181 with contextlib.suppress(KeyError): |
206 errorCode = self.Function2Code[name] |
182 errorCode = self.Function2Code[name] |
207 self.__error(node.lineno - 1, node.col_offset, errorCode) |
183 self.__error(node.lineno - 1, node.col_offset, errorCode) |
208 except KeyError: |
|
209 # name is not in our list of replacements |
|
210 pass |
|
211 |
184 |
212 |
185 |
213 class PathlibVisitor(ast.NodeVisitor): |
186 class PathlibVisitor(ast.NodeVisitor): |
214 """ |
187 """ |
215 Class to traverse the AST node tree and check for potential issues. |
188 Class to traverse the AST node tree and check for potential issues. |
220 |
193 |
221 @param checkCallback callback function taking a reference to the |
194 @param checkCallback callback function taking a reference to the |
222 AST node and the resolved name |
195 AST node and the resolved name |
223 @type func |
196 @type func |
224 """ |
197 """ |
225 super(PathlibVisitor, self).__init__() |
198 super().__init__() |
226 |
199 |
227 self.__checkCallback = checkCallback |
200 self.__checkCallback = checkCallback |
228 self.__importAlias = {} |
201 self.__importAlias = {} |
229 |
202 |
230 def visit_ImportFrom(self, node): |
203 def visit_ImportFrom(self, node): |
283 Public method to resolve the name. |
256 Public method to resolve the name. |
284 |
257 |
285 @return resolved name |
258 @return resolved name |
286 @rtype str |
259 @rtype str |
287 """ |
260 """ |
288 try: |
261 with contextlib.suppress(KeyError, IndexError): |
289 attr = self.__importAlias[self.__names[-1]] |
262 attr = self.__importAlias[self.__names[-1]] |
290 self.__names[-1] = attr |
263 self.__names[-1] = attr |
291 except (KeyError, IndexError): |
|
292 # do nothing if there is no such name or the names list is empty |
264 # do nothing if there is no such name or the names list is empty |
293 pass |
|
294 |
265 |
295 return ".".join(reversed(self.__names)) |
266 return ".".join(reversed(self.__names)) |
296 |
267 |
297 def visit_Name(self, node): |
268 def visit_Name(self, node): |
298 """ |
269 """ |