|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a node visitor checking for code that could be simplified. |
|
8 """ |
|
9 |
|
10 import ast |
|
11 import collections |
|
12 import copy |
|
13 import itertools |
|
14 |
|
15 try: |
|
16 from ast import unparse |
|
17 except ImportError: |
|
18 # Python < 3.9 |
|
19 from .ast_unparse import unparse |
|
20 |
|
21 ###################################################################### |
|
22 ## The following code is derived from the flake8-simplify package. |
|
23 ## |
|
24 ## Original License: |
|
25 ## |
|
26 ## MIT License |
|
27 ## |
|
28 ## Copyright (c) 2020 Martin Thoma |
|
29 ## |
|
30 ## Permission is hereby granted, free of charge, to any person obtaining a copy |
|
31 ## of this software and associated documentation files (the "Software"), to |
|
32 ## deal in the Software without restriction, including without limitation the |
|
33 ## rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
|
34 ## sell copies of the Software, and to permit persons to whom the Software is |
|
35 ## furnished to do so, subject to the following conditions: |
|
36 ## |
|
37 ## The above copyright notice and this permission notice shall be included in |
|
38 ## all copies or substantial portions of the Software. |
|
39 ## |
|
40 ## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
41 ## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
42 ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
43 ## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
44 ## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|
45 ## FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
|
46 ## IN THE SOFTWARE. |
|
47 ###################################################################### |
|
48 |
|
49 BOOL_CONST_TYPES = (ast.Constant, ast.NameConstant) |
|
50 AST_CONST_TYPES = (ast.Constant, ast.NameConstant, ast.Str, ast.Num) |
|
51 STR_TYPES = (ast.Constant, ast.Str) |
|
52 |
|
53 |
|
54 class SimplifyNodeVisitor(ast.NodeVisitor): |
|
55 """ |
|
56 Class to traverse the AST node tree and check for code that can be |
|
57 simplified. |
|
58 """ |
|
59 def __init__(self, errorCallback): |
|
60 """ |
|
61 Constructor |
|
62 |
|
63 @param errorCallback callback function to register an error |
|
64 @type func |
|
65 """ |
|
66 super().__init__() |
|
67 |
|
68 self.__error = errorCallback |
|
69 |
|
70 self.__classDefinitionStack = [] |
|
71 |
|
72 def visit_Expr(self, node): |
|
73 """ |
|
74 Public method to process an Expr node. |
|
75 |
|
76 @param node reference to the Expr node |
|
77 @type ast.Expr |
|
78 """ |
|
79 self.__check112(node) |
|
80 |
|
81 self.generic_visit(node) |
|
82 |
|
83 def visit_Assign(self, node): |
|
84 """ |
|
85 Public method to process an Assign node. |
|
86 |
|
87 @param node reference to the Assign node |
|
88 @type ast.Assign |
|
89 """ |
|
90 self.__check181(node) |
|
91 |
|
92 self.generic_visit(node) |
|
93 |
|
94 def visit_BoolOp(self, node): |
|
95 """ |
|
96 Public method to process a BoolOp node. |
|
97 |
|
98 @param node reference to the BoolOp node |
|
99 @type ast.BoolOp |
|
100 """ |
|
101 self.__check101(node) |
|
102 self.__check109(node) |
|
103 self.__check221(node) |
|
104 self.__check222(node) |
|
105 self.__check223(node) |
|
106 self.__check224(node) |
|
107 |
|
108 self.generic_visit(node) |
|
109 |
|
110 def visit_If(self, node): |
|
111 """ |
|
112 Public method to process an If node. |
|
113 |
|
114 @param node reference to the If node |
|
115 @type ast.If |
|
116 """ |
|
117 self.__check102(node) |
|
118 self.__check103(node) |
|
119 self.__check106(node) |
|
120 self.__check108(node) |
|
121 self.__check114(node) |
|
122 self.__check116(node) |
|
123 self.__check122(node) |
|
124 |
|
125 self.generic_visit(node) |
|
126 |
|
127 def visit_IfExp(self, node): |
|
128 """ |
|
129 Public method to process an IfExp node. |
|
130 |
|
131 @param node reference to the IfExp node |
|
132 @type ast.IfExp |
|
133 """ |
|
134 self.__check211(node) |
|
135 self.__check212(node) |
|
136 self.__check213(node) |
|
137 |
|
138 self.generic_visit(node) |
|
139 |
|
140 def visit_For(self, node): |
|
141 """ |
|
142 Public method to process a For node. |
|
143 |
|
144 @param node reference to the For node |
|
145 @type ast.For |
|
146 """ |
|
147 self.__check104(node) |
|
148 self.__check110_111(node) |
|
149 self.__check113(node) |
|
150 self.__check118(node) |
|
151 |
|
152 self.generic_visit(node) |
|
153 |
|
154 def visit_Try(self, node): |
|
155 """ |
|
156 Public method to process a Try node. |
|
157 |
|
158 @param node reference to the Try node |
|
159 @type ast.Try |
|
160 """ |
|
161 self.__check105(node) |
|
162 self.__check107(node) |
|
163 |
|
164 self.generic_visit(node) |
|
165 |
|
166 def visit_Call(self, node): |
|
167 """ |
|
168 Public method to process a Call node. |
|
169 |
|
170 @param node reference to the Call node |
|
171 @type ast.Call |
|
172 """ |
|
173 self.__check115(node) |
|
174 self.__check182(node) |
|
175 self.__check401(node) |
|
176 self.__check402(node) |
|
177 |
|
178 self.generic_visit(node) |
|
179 |
|
180 def visit_With(self, node): |
|
181 """ |
|
182 Public method to process a With node. |
|
183 |
|
184 @param node reference to the With node |
|
185 @type ast.With |
|
186 """ |
|
187 self.__check117(node) |
|
188 |
|
189 self.generic_visit(node) |
|
190 |
|
191 def visit_Compare(self, node): |
|
192 """ |
|
193 Public method to process a Compare node. |
|
194 |
|
195 @param node reference to the Compare node |
|
196 @type ast.Compare |
|
197 """ |
|
198 self.__check118(node) |
|
199 self.__check301(node) |
|
200 |
|
201 self.generic_visit(node) |
|
202 |
|
203 def visit_ClassDef(self, node): |
|
204 """ |
|
205 Public method to process a ClassDef node. |
|
206 |
|
207 @param node reference to the ClassDef node |
|
208 @type ast.ClassDef |
|
209 """ |
|
210 # register the name of the class being defined |
|
211 self.__classDefinitionStack.append(node.name) |
|
212 |
|
213 self.__check119(node) |
|
214 self.__check120_121(node) |
|
215 |
|
216 self.generic_visit(node) |
|
217 |
|
218 self.__classDefinitionStack.pop() |
|
219 |
|
220 def visit_UnaryOp(self, node): |
|
221 """ |
|
222 Public method to process a UnaryOp node. |
|
223 |
|
224 @param node reference to the UnaryOp node |
|
225 @type ast.UnaryOp |
|
226 """ |
|
227 self.__check201(node) |
|
228 self.__check202(node) |
|
229 self.__check203(node) |
|
230 self.__check204(node) |
|
231 self.__check205(node) |
|
232 self.__check206(node) |
|
233 self.__check207(node) |
|
234 self.__check208(node) |
|
235 |
|
236 self.generic_visit(node) |
|
237 |
|
238 ############################################################# |
|
239 ## Helper methods for the various checkers below |
|
240 ############################################################# |
|
241 |
|
242 def __getDuplicatedIsinstanceCall(self, node): |
|
243 """ |
|
244 Private method to get a list of isinstance arguments which could |
|
245 be combined. |
|
246 |
|
247 @param node reference to the AST node to be inspected |
|
248 @type ast.BoolOp |
|
249 @return list of variable names of duplicated isinstance calls |
|
250 @rtype list of str |
|
251 """ |
|
252 counter = collections.defaultdict(int) |
|
253 |
|
254 for call in node.values: |
|
255 # Ensure this is a call of the built-in isinstance() function. |
|
256 if not isinstance(call, ast.Call) or len(call.args) != 2: |
|
257 continue |
|
258 functionName = unparse(call.func) |
|
259 if functionName != "isinstance": |
|
260 continue |
|
261 |
|
262 arg0Name = unparse(call.args[0]) |
|
263 counter[arg0Name] += 1 |
|
264 |
|
265 return [name for name, count in counter.items() if count > 1] |
|
266 |
|
267 def __isConstantIncrease(self, expression): |
|
268 """ |
|
269 Private method check the given expression for being a constant |
|
270 increase. |
|
271 |
|
272 @param expression reference to the expression node |
|
273 @type ast.AugAssign |
|
274 @return flag indicating a constant increase |
|
275 @rtype bool |
|
276 """ |
|
277 return ( |
|
278 isinstance(expression.op, ast.Add) and ( |
|
279 (isinstance(expression.value, ast.Constant) and |
|
280 isinstance(expression.value.value, int)) or |
|
281 isinstance(expression.value, ast.Num) |
|
282 ) |
|
283 ) |
|
284 |
|
285 def __getIfBodyPairs(self, node): |
|
286 """ |
|
287 Private method to extract a list of pairs of test and body for an |
|
288 If node. |
|
289 |
|
290 @param node reference to the If node to be processed |
|
291 @type ast.If |
|
292 @return list of pairs of test and body |
|
293 @rtype list of tuples of (ast.expr, [ast.stmt]) |
|
294 """ |
|
295 pairs = [(node.test, node.body)] |
|
296 orelse = node.orelse |
|
297 while ( |
|
298 isinstance(orelse, list) and |
|
299 len(orelse) == 1 and |
|
300 isinstance(orelse[0], ast.If) |
|
301 ): |
|
302 pairs.append((orelse[0].test, orelse[0].body)) |
|
303 orelse = orelse[0].orelse |
|
304 return pairs |
|
305 |
|
306 def __isSameBody(self, body1, body2): |
|
307 """ |
|
308 Private method check, if the given bodies are equivalent. |
|
309 |
|
310 @param body1 list of statements of the first body |
|
311 @type list of ast.stmt |
|
312 @param body2 list of statements of the second body |
|
313 @type list of ast.stmt |
|
314 @return flag indicating identical bodies |
|
315 @rtype bool |
|
316 """ |
|
317 if len(body1) != len(body2): |
|
318 return False |
|
319 for a, b in zip(body1, body2): |
|
320 try: |
|
321 statementEqual = self.__isStatementEqual(a, b) |
|
322 except RecursionError: # maximum recursion depth |
|
323 statementEqual = False |
|
324 if not statementEqual: |
|
325 return False |
|
326 |
|
327 return True |
|
328 |
|
329 def __isSameExpression(self, a, b): |
|
330 """ |
|
331 Private method to check, if two expressions are equal. |
|
332 |
|
333 @param a first expression to be checked |
|
334 @type ast.expr |
|
335 @param b second expression to be checked |
|
336 @type ast.expr |
|
337 @return flag indicating equal expressions |
|
338 @rtype bool |
|
339 """ |
|
340 if isinstance(a, ast.Name) and isinstance(b, ast.Name): |
|
341 return a.id == b.id |
|
342 else: |
|
343 return False |
|
344 |
|
345 def __isStatementEqual(self, a, b): |
|
346 """ |
|
347 Private method to check, if two statements are equal. |
|
348 |
|
349 @param a reference to the first statement |
|
350 @type ast.stmt |
|
351 @param b reference to the second statement |
|
352 @type ast.stmt |
|
353 @return flag indicating if the two statements are equal |
|
354 @rtype bool |
|
355 """ |
|
356 if type(a) is not type(b): |
|
357 return False |
|
358 |
|
359 if isinstance(a, ast.AST): |
|
360 for k, v in vars(a).items(): |
|
361 if k in ("lineno", "col_offset", "ctx", "end_lineno", |
|
362 "parent"): |
|
363 continue |
|
364 if not self.__isStatementEqual(v, getattr(b, k)): |
|
365 return False |
|
366 return True |
|
367 elif isinstance(a, list): |
|
368 return all(itertools.starmap(self.__isStatementEqual, zip(a, b))) |
|
369 else: |
|
370 return a == b |
|
371 |
|
372 def __isExceptionCheck(self, node): |
|
373 """ |
|
374 Private method to check, if the node is checking an exception. |
|
375 |
|
376 @param node reference to the node to be checked |
|
377 @type ast.If |
|
378 @return flag indicating an exception check |
|
379 @rtype bool |
|
380 """ |
|
381 return ( |
|
382 len(node.body) == 1 and isinstance(node.body[0], ast.Raise) |
|
383 ) |
|
384 |
|
385 def __negateTest(self, node): |
|
386 """ |
|
387 Private method negate the given Compare node. |
|
388 |
|
389 @param node reference to the node to be negated |
|
390 @type ast.Compare |
|
391 @return node with negated logic |
|
392 @rtype ast.Compare |
|
393 """ |
|
394 newNode = copy.deepcopy(node) |
|
395 op = newNode.ops[0] |
|
396 if isinstance(op, ast.Eq): |
|
397 op = ast.NotEq() |
|
398 elif isinstance(op, ast.NotEq): |
|
399 op = ast.Eq() |
|
400 elif isinstance(op, ast.Lt): |
|
401 op = ast.GtE() |
|
402 elif isinstance(op, ast.LtE): |
|
403 op = ast.Gt() |
|
404 elif isinstance(op, ast.Gt): |
|
405 op = ast.LtE() |
|
406 elif isinstance(op, ast.GtE): |
|
407 op = ast.Lt() |
|
408 elif isinstance(op, ast.Is): |
|
409 op = ast.IsNot() |
|
410 elif isinstance(op, ast.IsNot): |
|
411 op = ast.Is() |
|
412 elif isinstance(op, ast.In): |
|
413 op = ast.NotIn() |
|
414 elif isinstance(op, ast.NotIn): |
|
415 op = ast.In() |
|
416 newNode.ops = [op] |
|
417 return newNode |
|
418 |
|
419 ############################################################# |
|
420 ## Methods to check for possible code simplifications below |
|
421 ############################################################# |
|
422 |
|
423 def __check101(self, node): |
|
424 """ |
|
425 Private method to check for duplicate isinstance() calls. |
|
426 |
|
427 @param node reference to the AST node to be checked |
|
428 @type ast.BoolOp |
|
429 """ |
|
430 if isinstance(node.op, ast.Or): |
|
431 for variable in self.__getDuplicatedIsinstanceCall(node): |
|
432 self.__error(node.lineno - 1, node.col_offset, "Y101", |
|
433 variable) |
|
434 |
|
435 def __check102(self, node): |
|
436 """ |
|
437 Private method to check for nested if statements without else blocks. |
|
438 |
|
439 @param node reference to the AST node to be checked |
|
440 @type ast.If |
|
441 """ |
|
442 # Don't treat 'if __name__ == "__main__":' as an issue. |
|
443 if ( |
|
444 isinstance(node.test, ast.Compare) and |
|
445 isinstance(node.test.left, ast.Name) and |
|
446 node.test.left.id == "__name__" and |
|
447 isinstance(node.test.ops[0], ast.Eq) and |
|
448 isinstance(node.test.comparators[0], ast.Constant) and |
|
449 node.test.comparators[0].value == "__main__" |
|
450 ): |
|
451 return |
|
452 |
|
453 # ## Pattern 1 |
|
454 # if a: <--- |
|
455 # if b: <--- |
|
456 # c |
|
457 isPattern1 = ( |
|
458 node.orelse == [] and |
|
459 len(node.body) == 1 and |
|
460 isinstance(node.body[0], ast.If) and |
|
461 node.body[0].orelse == [] |
|
462 ) |
|
463 # ## Pattern 2 |
|
464 # if a: < irrelevant for here |
|
465 # pass |
|
466 # elif b: <--- this is treated like a nested block |
|
467 # if c: <--- |
|
468 # d |
|
469 if isPattern1: |
|
470 self.__error(node.lineno - 1, node.col_offset, "Y102") |
|
471 |
|
472 def __check103(self, node): |
|
473 """ |
|
474 Private method to check for calls that wrap a condition to return |
|
475 a bool. |
|
476 |
|
477 @param node reference to the AST node to be checked |
|
478 @type ast.If |
|
479 """ |
|
480 # if cond: |
|
481 # return True |
|
482 # else: |
|
483 # return False |
|
484 if not ( |
|
485 len(node.body) != 1 or |
|
486 not isinstance(node.body[0], ast.Return) or |
|
487 not isinstance(node.body[0].value, BOOL_CONST_TYPES) or |
|
488 not ( |
|
489 node.body[0].value.value is True or |
|
490 node.body[0].value.value is False |
|
491 ) or |
|
492 len(node.orelse) != 1 or |
|
493 not isinstance(node.orelse[0], ast.Return) or |
|
494 not isinstance(node.orelse[0].value, BOOL_CONST_TYPES) or |
|
495 not ( |
|
496 node.orelse[0].value.value is True or |
|
497 node.orelse[0].value.value is False |
|
498 ) |
|
499 ): |
|
500 condition = unparse(node.test) |
|
501 self.__error(node.lineno - 1, node.col_offset, "Y103", condition) |
|
502 |
|
503 def __check104(self, node): |
|
504 """ |
|
505 Private method to check for "iterate and yield" patterns. |
|
506 |
|
507 @param node reference to the AST node to be checked |
|
508 @type ast.For |
|
509 """ |
|
510 # for item in iterable: |
|
511 # yield item |
|
512 if not ( |
|
513 len(node.body) != 1 or |
|
514 not isinstance(node.body[0], ast.Expr) or |
|
515 not isinstance(node.body[0].value, ast.Yield) or |
|
516 not isinstance(node.target, ast.Name) or |
|
517 not isinstance(node.body[0].value.value, ast.Name) or |
|
518 node.target.id != node.body[0].value.value.id or |
|
519 node.orelse != [] |
|
520 ): |
|
521 iterable = unparse(node.iter) |
|
522 self.__error(node.lineno - 1, node.col_offset, "Y104", iterable) |
|
523 |
|
524 def __check105(self, node): |
|
525 """ |
|
526 Private method to check for "try-except-pass" patterns. |
|
527 |
|
528 @param node reference to the AST node to be checked |
|
529 @type ast.Try |
|
530 """ |
|
531 # try: |
|
532 # foo() |
|
533 # except ValueError: |
|
534 # pass |
|
535 if not ( |
|
536 len(node.handlers) != 1 or |
|
537 not isinstance(node.handlers[0], ast.ExceptHandler) or |
|
538 len(node.handlers[0].body) != 1 or |
|
539 not isinstance(node.handlers[0].body[0], ast.Pass) or |
|
540 node.orelse != [] |
|
541 ): |
|
542 if node.handlers[0].type is None: |
|
543 exception = "Exception" |
|
544 elif isinstance(node.handlers[0].type, ast.Tuple): |
|
545 exception = ", ".join( |
|
546 [unparse(n) for n in node.handlers[0].type.elts]) |
|
547 else: |
|
548 exception = unparse(node.handlers[0].type) |
|
549 self.__error(node.lineno - 1, node.col_offset, "Y105", exception) |
|
550 |
|
551 def __check106(self, node): |
|
552 """ |
|
553 Private method to check for calls where an exception is raised in else. |
|
554 |
|
555 @param node reference to the AST node to be checked |
|
556 @type ast.If |
|
557 """ |
|
558 # if cond: |
|
559 # return True |
|
560 # else: |
|
561 # raise Exception |
|
562 just_one = ( |
|
563 len(node.body) == 1 and |
|
564 len(node.orelse) >= 1 and |
|
565 isinstance(node.orelse[-1], ast.Raise) and |
|
566 not isinstance(node.body[-1], ast.Raise) |
|
567 ) |
|
568 many = ( |
|
569 len(node.body) > 2 * len(node.orelse) and |
|
570 len(node.orelse) >= 1 and |
|
571 isinstance(node.orelse[-1], ast.Raise) and |
|
572 not isinstance(node.body[-1], ast.Raise) |
|
573 ) |
|
574 if just_one or many: |
|
575 self.__error(node.lineno - 1, node.col_offset, "Y106") |
|
576 |
|
577 def __check107(self, node): |
|
578 """ |
|
579 Private method to check for calls where try/except and finally have |
|
580 'return'. |
|
581 |
|
582 @param node reference to the AST node to be checked |
|
583 @type ast.Try |
|
584 """ |
|
585 # def foo(): |
|
586 # try: |
|
587 # 1 / 0 |
|
588 # return "1" |
|
589 # except: |
|
590 # return "2" |
|
591 # finally: |
|
592 # return "3" |
|
593 tryHasReturn = False |
|
594 for stmt in node.body: |
|
595 if isinstance(stmt, ast.Return): |
|
596 tryHasReturn = True |
|
597 break |
|
598 |
|
599 exceptHasReturn = False |
|
600 for stmt2 in node.handlers: |
|
601 if isinstance(stmt2, ast.Return): |
|
602 exceptHasReturn = True |
|
603 break |
|
604 |
|
605 finallyHasReturn = False |
|
606 finallyReturn = None |
|
607 for stmt in node.finalbody: |
|
608 if isinstance(stmt, ast.Return): |
|
609 finallyHasReturn = True |
|
610 finallyReturn = stmt |
|
611 break |
|
612 |
|
613 if ( |
|
614 (tryHasReturn or exceptHasReturn) and |
|
615 finallyHasReturn and |
|
616 finallyReturn is not None |
|
617 ): |
|
618 self.__error(finallyReturn.lineno - 1, |
|
619 finallyReturn.col_offset, "Y107") |
|
620 |
|
621 def __check108(self, node): |
|
622 """ |
|
623 Private method to check for if-elses which could be a ternary |
|
624 operator assignment. |
|
625 |
|
626 @param node reference to the AST node to be checked |
|
627 @type ast.If |
|
628 """ |
|
629 # if a: |
|
630 # b = c |
|
631 # else: |
|
632 # b = d |
|
633 # |
|
634 # but not: |
|
635 # if a: |
|
636 # b = c |
|
637 # elif c: |
|
638 # b = e |
|
639 # else: |
|
640 # b = d |
|
641 if ( |
|
642 len(node.body) == 1 and |
|
643 len(node.orelse) == 1 and |
|
644 isinstance(node.body[0], ast.Assign) and |
|
645 isinstance(node.orelse[0], ast.Assign) and |
|
646 len(node.body[0].targets) == 1 and |
|
647 len(node.orelse[0].targets) == 1 and |
|
648 isinstance(node.body[0].targets[0], ast.Name) and |
|
649 isinstance(node.orelse[0].targets[0], ast.Name) and |
|
650 node.body[0].targets[0].id == node.orelse[0].targets[0].id and |
|
651 not isinstance(node.parent, ast.If) |
|
652 ): |
|
653 assign = unparse(node.body[0].targets[0]) |
|
654 body = unparse(node.body[0].value) |
|
655 cond = unparse(node.test) |
|
656 orelse = unparse(node.orelse[0].value) |
|
657 |
|
658 self.__error(node.lineno - 1, node.col_offset, "Y108", |
|
659 assign, body, cond, orelse) |
|
660 |
|
661 def __check109(self, node): |
|
662 """ |
|
663 Private method to check for multiple equalities with the same value |
|
664 are combined via "or". |
|
665 |
|
666 @param node reference to the AST node to be checked |
|
667 @type ast.BoolOp |
|
668 """ |
|
669 # if a == b or a == c: |
|
670 # d |
|
671 if isinstance(node.op, ast.Or): |
|
672 equalities = [ |
|
673 value |
|
674 for value in node.values |
|
675 if isinstance(value, ast.Compare) and |
|
676 len(value.ops) == 1 and |
|
677 isinstance(value.ops[0], ast.Eq) |
|
678 ] |
|
679 ids = [] # (name, compared_to) |
|
680 for eq in equalities: |
|
681 if isinstance(eq.left, ast.Name): |
|
682 ids.append((eq.left, eq.comparators[0])) |
|
683 if ( |
|
684 len(eq.comparators) == 1 and |
|
685 isinstance(eq.comparators[0], ast.Name) |
|
686 ): |
|
687 ids.append((eq.comparators[0], eq.left)) |
|
688 |
|
689 id2count = {} |
|
690 for identifier, comparedTo in ids: |
|
691 if identifier.id not in id2count: |
|
692 id2count[identifier.id] = [] |
|
693 id2count[identifier.id].append(comparedTo) |
|
694 for value, values in id2count.items(): |
|
695 if len(values) == 1: |
|
696 continue |
|
697 |
|
698 self.__error(node.lineno - 1, node.col_offset, "Y109", |
|
699 value, unparse(ast.Tuple(elts=values)), |
|
700 unparse(node)) |
|
701 |
|
702 def __check110_111(self, node): |
|
703 """ |
|
704 Private method to check if any / all could be used. |
|
705 |
|
706 @param node reference to the AST node to be checked |
|
707 @type ast.For |
|
708 """ |
|
709 # for x in iterable: |
|
710 # if check(x): |
|
711 # return True |
|
712 # return False |
|
713 # |
|
714 # for x in iterable: |
|
715 # if check(x): |
|
716 # return False |
|
717 # return True |
|
718 if ( |
|
719 len(node.body) == 1 and |
|
720 isinstance(node.body[0], ast.If) and |
|
721 len(node.body[0].body) == 1 and |
|
722 isinstance(node.body[0].body[0], ast.Return) and |
|
723 isinstance(node.body[0].body[0].value, BOOL_CONST_TYPES) and |
|
724 hasattr(node.body[0].body[0].value, "value") |
|
725 ): |
|
726 check = unparse(node.body[0].test) |
|
727 target = unparse(node.target) |
|
728 iterable = unparse(node.iter) |
|
729 if node.body[0].body[0].value.value is True: |
|
730 self.__error(node.lineno - 1, node.col_offset, "Y110", |
|
731 check, target, iterable) |
|
732 elif node.body[0].body[0].value.value is False: |
|
733 check = "not " + check |
|
734 if check.startswith("not not "): |
|
735 check = check[len("not not "):] |
|
736 self.__error(node.lineno - 1, node.col_offset, "Y111", |
|
737 check, target, iterable) |
|
738 |
|
739 def __check112(self, node): |
|
740 """ |
|
741 Private method to check for non-capitalized calls to environment |
|
742 variables. |
|
743 |
|
744 @param node reference to the AST node to be checked |
|
745 @type ast.Expr |
|
746 """ |
|
747 # os.environ["foo"] |
|
748 # os.environ.get("bar") |
|
749 isIndexCall = ( |
|
750 isinstance(node.value, ast.Subscript) and |
|
751 isinstance(node.value.value, ast.Attribute) and |
|
752 isinstance(node.value.value.value, ast.Name) and |
|
753 node.value.value.value.id == "os" and |
|
754 node.value.value.attr == "environ" and |
|
755 ( |
|
756 ( |
|
757 isinstance(node.value.slice, ast.Index) and |
|
758 isinstance(node.value.slice.value, STR_TYPES) |
|
759 ) or |
|
760 isinstance(node.value.slice, ast.Constant) |
|
761 ) |
|
762 ) |
|
763 if isIndexCall: |
|
764 subscript = node.value |
|
765 slice_ = subscript.slice |
|
766 if isinstance(slice_, ast.Index): |
|
767 # Python < 3.9 |
|
768 stringPart = slice_.value # type: ignore |
|
769 if isinstance(stringPart, ast.Str): |
|
770 envName = stringPart.s # Python 3.6 / 3.7 fallback |
|
771 else: |
|
772 envName = stringPart.value |
|
773 elif isinstance(slice_, ast.Constant): |
|
774 # Python 3.9 |
|
775 envName = slice_.value |
|
776 |
|
777 # Check if this has a change |
|
778 hasChange = envName != envName.upper() |
|
779 |
|
780 isGetCall = ( |
|
781 isinstance(node.value, ast.Call) and |
|
782 isinstance(node.value.func, ast.Attribute) and |
|
783 isinstance(node.value.func.value, ast.Attribute) and |
|
784 isinstance(node.value.func.value.value, ast.Name) and |
|
785 node.value.func.value.value.id == "os" and |
|
786 node.value.func.value.attr == "environ" and |
|
787 node.value.func.attr == "get" and |
|
788 len(node.value.args) in [1, 2] and |
|
789 isinstance(node.value.args[0], STR_TYPES) |
|
790 ) |
|
791 if isGetCall: |
|
792 call = node.value |
|
793 stringPart = call.args[0] |
|
794 if isinstance(stringPart, ast.Str): |
|
795 envName = stringPart.s # Python 3.6 / 3.7 fallback |
|
796 else: |
|
797 envName = stringPart.value |
|
798 # Check if this has a change |
|
799 hasChange = envName != envName.upper() |
|
800 if not (isIndexCall or isGetCall) or not hasChange: |
|
801 return |
|
802 if isIndexCall: |
|
803 original = unparse(node) |
|
804 expected = f"os.environ['{envName.upper()}']" |
|
805 elif isGetCall: |
|
806 original = unparse(node) |
|
807 if len(node.value.args) == 1: |
|
808 expected = f"os.environ.get('{envName.upper()}')" |
|
809 else: |
|
810 defaultValue = unparse(node.value.args[1]) |
|
811 expected = ( |
|
812 f"os.environ.get('{envName.upper()}', '{defaultValue}')" |
|
813 ) |
|
814 else: |
|
815 return |
|
816 |
|
817 self.__error(node.lineno - 1, node.col_offset, "Y112", expected, |
|
818 original) |
|
819 |
|
820 def __check113(self, node): |
|
821 """ |
|
822 Private method to check for loops in which "enumerate" should be |
|
823 used. |
|
824 |
|
825 @param node reference to the AST node to be checked |
|
826 @type ast.For |
|
827 """ |
|
828 # idx = 0 |
|
829 # for el in iterable: |
|
830 # ... |
|
831 # idx += 1 |
|
832 variableCandidates = [] |
|
833 for expression in node.body: |
|
834 if ( |
|
835 isinstance(expression, ast.AugAssign) and |
|
836 self.__isConstantIncrease(expression) and |
|
837 isinstance(expression.target, ast.Name) |
|
838 ): |
|
839 variableCandidates.append(expression.target) |
|
840 |
|
841 for candidate in variableCandidates: |
|
842 self.__error(candidate.lineno - 1, candidate.col_offset, "Y113", |
|
843 unparse(candidate)) |
|
844 |
|
845 def __check114(self, node): |
|
846 """ |
|
847 Private method to check for alternative if clauses with identical |
|
848 bodies. |
|
849 |
|
850 @param node reference to the AST node to be checked |
|
851 @type ast.If |
|
852 """ |
|
853 # if a: |
|
854 # b |
|
855 # elif c: |
|
856 # b |
|
857 ifBodyPairs = self.__getIfBodyPairs(node) |
|
858 errorPairs = [] |
|
859 for ifbody1, ifbody2 in itertools.combinations(ifBodyPairs, 2): |
|
860 if self.__isSameBody(ifbody1[1], ifbody2[1]): |
|
861 errorPairs.append((ifbody1, ifbody2)) |
|
862 for ifbody1, ifbody2 in errorPairs: |
|
863 self.__error(ifbody1[0].lineno - 1, ifbody1[0].col_offset, "Y114", |
|
864 unparse(ifbody1[0]), unparse(ifbody2[0])) |
|
865 |
|
866 def __check115(self, node): |
|
867 """ |
|
868 Private method to to check for places where open() is called without |
|
869 a context handler. |
|
870 |
|
871 @param node reference to the AST node to be checked |
|
872 @type ast.Call |
|
873 """ |
|
874 # f = open(...) |
|
875 #. .. # (do something with f) |
|
876 # f.close() |
|
877 if ( |
|
878 isinstance(node.func, ast.Name) and |
|
879 node.func.id == "open" and |
|
880 not isinstance(node.parent, ast.withitem) |
|
881 ): |
|
882 self.__error(node.lineno - 1, node.col_offset, "Y115") |
|
883 |
|
884 def __check116(self, node): |
|
885 """ |
|
886 Private method to check for places with 3 or more consecutive |
|
887 if-statements with direct returns. |
|
888 |
|
889 * Each if-statement must be a check for equality with the |
|
890 same variable |
|
891 * Each if-statement must just have a "return" |
|
892 * Else must also just have a return |
|
893 |
|
894 @param node reference to the AST node to be checked |
|
895 @type ast.If |
|
896 """ |
|
897 # if a == "foo": |
|
898 # return "bar" |
|
899 # elif a == "bar": |
|
900 # return "baz" |
|
901 # elif a == "boo": |
|
902 # return "ooh" |
|
903 # else: |
|
904 # return 42 |
|
905 if ( |
|
906 isinstance(node.test, ast.Compare) and |
|
907 isinstance(node.test.left, ast.Name) and |
|
908 len(node.test.ops) == 1 and |
|
909 isinstance(node.test.ops[0], ast.Eq) and |
|
910 len(node.test.comparators) == 1 and |
|
911 isinstance(node.test.comparators[0], AST_CONST_TYPES) and |
|
912 len(node.body) == 1 and |
|
913 isinstance(node.body[0], ast.Return) and |
|
914 len(node.orelse) == 1 and |
|
915 isinstance(node.orelse[0], ast.If) |
|
916 ): |
|
917 variable = node.test.left |
|
918 child = node.orelse[0] |
|
919 elseValue = None |
|
920 if node.body[0].value is not None: |
|
921 bodyValueStr = unparse(node.body[0].value).strip("'") |
|
922 else: |
|
923 bodyValueStr = "None" |
|
924 if isinstance(node.test.comparators[0], ast.Str): |
|
925 keyValuePairs = { |
|
926 node.test.comparators[0].s: bodyValueStr |
|
927 } |
|
928 elif isinstance(node.test.comparators[0], ast.Num): |
|
929 keyValuePairs = { |
|
930 node.test.comparators[0].n: bodyValueStr, |
|
931 } |
|
932 else: |
|
933 keyValuePairs = { |
|
934 node.test.comparators[0].value: bodyValueStr |
|
935 } |
|
936 while child: |
|
937 if not ( |
|
938 isinstance(child.test, ast.Compare) and |
|
939 isinstance(child.test.left, ast.Name) and |
|
940 child.test.left.id == variable.id and |
|
941 len(child.test.ops) == 1 and |
|
942 isinstance(child.test.ops[0], ast.Eq) and |
|
943 len(child.test.comparators) == 1 and |
|
944 isinstance(child.test.comparators[0], AST_CONST_TYPES) and |
|
945 len(child.body) == 1 and |
|
946 isinstance(child.body[0], ast.Return) and |
|
947 len(child.orelse) <= 1 |
|
948 ): |
|
949 return |
|
950 |
|
951 if isinstance(child.test.comparators[0], ast.Str): |
|
952 key = child.test.comparators[0].s |
|
953 elif isinstance(child.test.comparators[0], ast.Num): |
|
954 key = child.test.comparators[0].n |
|
955 else: |
|
956 key = child.test.comparators[0].value |
|
957 keyValuePairs[key] = unparse(child.body[0].value).strip("'") |
|
958 if len(child.orelse) == 1: |
|
959 if isinstance(child.orelse[0], ast.If): |
|
960 child = child.orelse[0] |
|
961 elif isinstance(child.orelse[0], ast.Return): |
|
962 elseValue = unparse(child.orelse[0].value) |
|
963 child = None |
|
964 else: |
|
965 return |
|
966 else: |
|
967 child = None |
|
968 |
|
969 if len(keyValuePairs) < 3: |
|
970 return |
|
971 |
|
972 if elseValue: |
|
973 ret = f"{keyValuePairs}.get({variable.id}, {elseValue})" |
|
974 else: |
|
975 ret = f"{keyValuePairs}.get({variable.id})" |
|
976 |
|
977 self.__error(node.lineno - 1, node.col_offset, "Y116", ret) |
|
978 |
|
979 def __check117(self, node): |
|
980 """ |
|
981 Private method to check for multiple with-statements with same scope. |
|
982 |
|
983 @param node reference to the AST node to be checked |
|
984 @type ast.With |
|
985 """ |
|
986 # with A() as a: |
|
987 # with B() as b: |
|
988 # print("hello") |
|
989 if ( |
|
990 len(node.body) == 1 and |
|
991 isinstance(node.body[0], ast.With) |
|
992 ): |
|
993 withItems = [] |
|
994 for withitem in node.items + node.body[0].items: |
|
995 withItems.append(f"{unparse(withitem)}") |
|
996 mergedWith = f"with {', '.join(withItems)}:" |
|
997 self.__error(node.lineno - 1, node.col_offset, "Y117", mergedWith) |
|
998 |
|
999 def __check118(self, node): |
|
1000 """ |
|
1001 Private method to check for usages of "key in dict.keys()". |
|
1002 |
|
1003 @param node reference to the AST node to be checked |
|
1004 @type ast.Compare or ast.For |
|
1005 """ |
|
1006 # Pattern 1: |
|
1007 # |
|
1008 # if key in dict.keys(): |
|
1009 # # do something |
|
1010 # |
|
1011 # Pattern 2: |
|
1012 # |
|
1013 # for key in dict.keys(): |
|
1014 # # do something |
|
1015 if ( |
|
1016 isinstance(node, ast.Compare) and |
|
1017 len(node.ops) == 1 and |
|
1018 isinstance(node.ops[0], ast.In) and |
|
1019 len(node.comparators) == 1 |
|
1020 ): |
|
1021 callNode = node.comparators[0] |
|
1022 elif ( |
|
1023 isinstance(node, ast.For) |
|
1024 ): |
|
1025 callNode = node.iter |
|
1026 else: |
|
1027 callNode = None |
|
1028 |
|
1029 if not isinstance(callNode, ast.Call): |
|
1030 return |
|
1031 |
|
1032 attrNode = callNode.func |
|
1033 if ( |
|
1034 isinstance(callNode.func, ast.Attribute) and |
|
1035 callNode.func.attr == "keys" and |
|
1036 isinstance(callNode.func.ctx, ast.Load) |
|
1037 ): |
|
1038 if isinstance(node, ast.Compare): |
|
1039 keyStr = unparse(node.left) |
|
1040 else: |
|
1041 keyStr = unparse(node.target) |
|
1042 dictStr = unparse(attrNode.value) |
|
1043 self.__error(node.lineno - 1, node.col_offset, "Y118", |
|
1044 keyStr, dictStr) |
|
1045 |
|
1046 def __check119(self, node): |
|
1047 """ |
|
1048 Private method to check for classes that should be "dataclasses". |
|
1049 |
|
1050 @param node reference to the AST node to be checked |
|
1051 @type ast.ClassDef |
|
1052 """ |
|
1053 if ( |
|
1054 len(node.decorator_list) == 0 and |
|
1055 len(node.bases) == 0 |
|
1056 ): |
|
1057 dataclassFunctions = [ |
|
1058 "__init__", |
|
1059 "__eq__", |
|
1060 "__hash__", |
|
1061 "__repr__", |
|
1062 "__str__", |
|
1063 ] |
|
1064 hasOnlyConstructorMethod = True |
|
1065 for bodyElement in node.body: |
|
1066 if ( |
|
1067 isinstance(bodyElement, ast.FunctionDef) and |
|
1068 bodyElement.name not in dataclassFunctions |
|
1069 ): |
|
1070 hasOnlyConstructorMethod = False |
|
1071 break |
|
1072 |
|
1073 if ( |
|
1074 hasOnlyConstructorMethod and |
|
1075 sum(1 for el in node.body |
|
1076 if isinstance(el, ast.FunctionDef)) > 0 |
|
1077 ): |
|
1078 self.__error(node.lineno - 1, node.col_offset, "Y119", |
|
1079 node.name) |
|
1080 |
|
1081 def __check120_121(self, node): |
|
1082 """ |
|
1083 Private method to check for classes that inherit from object. |
|
1084 |
|
1085 @param node reference to the AST node to be checked |
|
1086 @type ast.ClassDef |
|
1087 """ |
|
1088 # class FooBar(object): |
|
1089 # ... |
|
1090 if ( |
|
1091 len(node.bases) == 1 and |
|
1092 isinstance(node.bases[0], ast.Name) and |
|
1093 node.bases[0].id == "object" |
|
1094 ): |
|
1095 self.__error(node.lineno - 1, node.col_offset, "Y120", |
|
1096 node.name) |
|
1097 |
|
1098 elif ( |
|
1099 len(node.bases) > 1 and |
|
1100 isinstance(node.bases[-1], ast.Name) and |
|
1101 node.bases[-1].id == "object" |
|
1102 ): |
|
1103 self.__error(node.lineno - 1, node.col_offset, "Y121", |
|
1104 node.name, ", ".join(b.id for b in node.bases[:-1])) |
|
1105 |
|
1106 def __check122(self, node): |
|
1107 """ |
|
1108 Private method to check for all if-blocks which only check if a key |
|
1109 is in a dictionary. |
|
1110 |
|
1111 @param node reference to the AST node to be checked |
|
1112 @type ast.If |
|
1113 """ |
|
1114 if ( |
|
1115 isinstance(node.test, ast.Compare) and |
|
1116 len(node.test.ops) == 1 and |
|
1117 isinstance(node.test.ops[0], ast.In) and |
|
1118 len(node.body) == 1 and |
|
1119 len(node.orelse) == 0 |
|
1120 ) and ( |
|
1121 # We might still be left with a check if a value is in a list or |
|
1122 # in the body the developer might remove the element from the list. |
|
1123 # We need to have a look at the body. |
|
1124 isinstance(node.body[0], ast.Assign) and |
|
1125 isinstance(node.body[0].value, ast.Subscript) and |
|
1126 len(node.body[0].targets) == 1 and |
|
1127 isinstance(node.body[0].targets[0], ast.Name) and |
|
1128 isinstance(node.body[0].value.value, ast.Name) and |
|
1129 isinstance(node.test.comparators[0], ast.Name) and |
|
1130 node.body[0].value.value.id == node.test.comparators[0].id |
|
1131 ): |
|
1132 key = unparse(node.test.left) |
|
1133 dictname = unparse(node.test.comparators[0]) |
|
1134 self.__error(node.lineno - 1, node.col_offset, "Y122", |
|
1135 dictname, key) |
|
1136 |
|
1137 def __check181(self, node): |
|
1138 """ |
|
1139 Private method to check for assignments that could be converted into |
|
1140 an augmented assignment. |
|
1141 |
|
1142 @param node reference to the AST node to be checked |
|
1143 @type ast.Assign |
|
1144 """ |
|
1145 # a = a - b |
|
1146 if ( |
|
1147 len(node.targets) == 1 and |
|
1148 isinstance(node.targets[0], ast.Name) and |
|
1149 isinstance(node.value, ast.BinOp) and |
|
1150 isinstance(node.value.left, ast.Name) and |
|
1151 node.value.left.id == node.targets[0].id and |
|
1152 not isinstance(node.value.right, ast.Tuple) |
|
1153 ): |
|
1154 newNode = ast.AugAssign(node.targets[0], node.value.op, |
|
1155 node.value.right) |
|
1156 self.__error(node.lineno - 1, node.col_offset, "Y181", |
|
1157 unparse(newNode), unparse(node)) |
|
1158 |
|
1159 def __check182(self, node): |
|
1160 """ |
|
1161 Private method to check for calls of type 'super()' that could |
|
1162 be shortened to 'super()'. |
|
1163 |
|
1164 @param node reference to the AST node to be checked |
|
1165 @type ast.Call |
|
1166 """ |
|
1167 # super() |
|
1168 if ( |
|
1169 self.__classDefinitionStack and |
|
1170 isinstance(node.func, ast.Name) and |
|
1171 node.func.id == "super" and |
|
1172 len(node.args) == 2 and |
|
1173 all(isinstance(arg, ast.Name) for arg in node.args) and |
|
1174 node.args[0].id == self.__classDefinitionStack[-1] and |
|
1175 node.args[1].id == "self" |
|
1176 ): |
|
1177 self.__error(node.lineno - 1, node.col_offset, "Y182", |
|
1178 unparse(node)) |
|
1179 |
|
1180 def __check201(self, node): |
|
1181 """ |
|
1182 Private method to check for calls where an unary 'not' is used for |
|
1183 an unequality. |
|
1184 |
|
1185 @param node reference to the UnaryOp node |
|
1186 @type ast.UnaryOp |
|
1187 """ |
|
1188 # not a == b |
|
1189 if not ( |
|
1190 ( |
|
1191 not isinstance(node.op, ast.Not) or |
|
1192 not isinstance(node.operand, ast.Compare) or |
|
1193 len(node.operand.ops) != 1 or |
|
1194 not isinstance(node.operand.ops[0], ast.Eq) |
|
1195 ) or |
|
1196 isinstance(node.parent, ast.If) and |
|
1197 self.__isExceptionCheck(node.parent) |
|
1198 ): |
|
1199 comparison = node.operand |
|
1200 left = unparse(comparison.left) |
|
1201 right = unparse(comparison.comparators[0]) |
|
1202 self.__error(node.lineno - 1, node.col_offset, "Y201", |
|
1203 left, right) |
|
1204 |
|
1205 def __check202(self, node): |
|
1206 """ |
|
1207 Private method to check for calls where an unary 'not' is used for |
|
1208 an equality. |
|
1209 |
|
1210 @param node reference to the UnaryOp node |
|
1211 @type ast.UnaryOp |
|
1212 """ |
|
1213 # not a != b |
|
1214 if not ( |
|
1215 ( |
|
1216 not isinstance(node.op, ast.Not) or |
|
1217 not isinstance(node.operand, ast.Compare) or |
|
1218 len(node.operand.ops) != 1 or |
|
1219 not isinstance(node.operand.ops[0], ast.NotEq) |
|
1220 ) or |
|
1221 isinstance(node.parent, ast.If) and |
|
1222 self.__isExceptionCheck(node.parent) |
|
1223 ): |
|
1224 comparison = node.operand |
|
1225 left = unparse(comparison.left) |
|
1226 right = unparse(comparison.comparators[0]) |
|
1227 self.__error(node.lineno - 1, node.col_offset, "Y202", |
|
1228 left, right) |
|
1229 |
|
1230 def __check203(self, node): |
|
1231 """ |
|
1232 Private method to check for calls where an unary 'not' is used for |
|
1233 an in-check. |
|
1234 |
|
1235 @param node reference to the UnaryOp node |
|
1236 @type ast.UnaryOp |
|
1237 """ |
|
1238 # not a in b |
|
1239 if not ( |
|
1240 ( |
|
1241 not isinstance(node.op, ast.Not) or |
|
1242 not isinstance(node.operand, ast.Compare) or |
|
1243 len(node.operand.ops) != 1 or |
|
1244 not isinstance(node.operand.ops[0], ast.In) |
|
1245 ) or |
|
1246 isinstance(node.parent, ast.If) and |
|
1247 self.__isExceptionCheck(node.parent) |
|
1248 ): |
|
1249 comparison = node.operand |
|
1250 left = unparse(comparison.left) |
|
1251 right = unparse(comparison.comparators[0]) |
|
1252 self.__error(node.lineno - 1, node.col_offset, "Y203", |
|
1253 left, right) |
|
1254 |
|
1255 def __check204(self, node): |
|
1256 """ |
|
1257 Private method to check for calls of the type "not (a < b)". |
|
1258 |
|
1259 @param node reference to the UnaryOp node |
|
1260 @type ast.UnaryOp |
|
1261 """ |
|
1262 # not a < b |
|
1263 if not ( |
|
1264 ( |
|
1265 not isinstance(node.op, ast.Not) or |
|
1266 not isinstance(node.operand, ast.Compare) or |
|
1267 len(node.operand.ops) != 1 or |
|
1268 not isinstance(node.operand.ops[0], ast.Lt) |
|
1269 ) or |
|
1270 isinstance(node.parent, ast.If) and |
|
1271 self.__isExceptionCheck(node.parent) |
|
1272 ): |
|
1273 comparison = node.operand |
|
1274 left = unparse(comparison.left) |
|
1275 right = unparse(comparison.comparators[0]) |
|
1276 self.__error(node.lineno - 1, node.col_offset, "Y204", |
|
1277 left, right) |
|
1278 |
|
1279 def __check205(self, node): |
|
1280 """ |
|
1281 Private method to check for calls of the type "not (a <= b)". |
|
1282 |
|
1283 @param node reference to the UnaryOp node |
|
1284 @type ast.UnaryOp |
|
1285 """ |
|
1286 # not a <= b |
|
1287 if not ( |
|
1288 ( |
|
1289 not isinstance(node.op, ast.Not) or |
|
1290 not isinstance(node.operand, ast.Compare) or |
|
1291 len(node.operand.ops) != 1 or |
|
1292 not isinstance(node.operand.ops[0], ast.LtE) |
|
1293 ) or |
|
1294 isinstance(node.parent, ast.If) and |
|
1295 self.__isExceptionCheck(node.parent) |
|
1296 ): |
|
1297 comparison = node.operand |
|
1298 left = unparse(comparison.left) |
|
1299 right = unparse(comparison.comparators[0]) |
|
1300 self.__error(node.lineno - 1, node.col_offset, "Y205", |
|
1301 left, right) |
|
1302 |
|
1303 def __check206(self, node): |
|
1304 """ |
|
1305 Private method to check for calls of the type "not (a > b)". |
|
1306 |
|
1307 @param node reference to the UnaryOp node |
|
1308 @type ast.UnaryOp |
|
1309 """ |
|
1310 # not a > b |
|
1311 if not ( |
|
1312 ( |
|
1313 not isinstance(node.op, ast.Not) or |
|
1314 not isinstance(node.operand, ast.Compare) or |
|
1315 len(node.operand.ops) != 1 or |
|
1316 not isinstance(node.operand.ops[0], ast.Gt) |
|
1317 ) or |
|
1318 isinstance(node.parent, ast.If) and |
|
1319 self.__isExceptionCheck(node.parent) |
|
1320 ): |
|
1321 comparison = node.operand |
|
1322 left = unparse(comparison.left) |
|
1323 right = unparse(comparison.comparators[0]) |
|
1324 self.__error(node.lineno - 1, node.col_offset, "Y206", |
|
1325 left, right) |
|
1326 |
|
1327 def __check207(self, node): |
|
1328 """ |
|
1329 Private method to check for calls of the type "not (a >= b)". |
|
1330 |
|
1331 @param node reference to the UnaryOp node |
|
1332 @type ast.UnaryOp |
|
1333 """ |
|
1334 # not a >= b |
|
1335 if not ( |
|
1336 ( |
|
1337 not isinstance(node.op, ast.Not) or |
|
1338 not isinstance(node.operand, ast.Compare) or |
|
1339 len(node.operand.ops) != 1 or |
|
1340 not isinstance(node.operand.ops[0], ast.GtE) |
|
1341 ) or |
|
1342 isinstance(node.parent, ast.If) and |
|
1343 self.__isExceptionCheck(node.parent) |
|
1344 ): |
|
1345 comparison = node.operand |
|
1346 left = unparse(comparison.left) |
|
1347 right = unparse(comparison.comparators[0]) |
|
1348 self.__error(node.lineno - 1, node.col_offset, "Y207", |
|
1349 left, right) |
|
1350 |
|
1351 def __check208(self, node): |
|
1352 """ |
|
1353 Private method to check for calls of the type "not (not a)". |
|
1354 |
|
1355 @param node reference to the UnaryOp node |
|
1356 @type ast.UnaryOp |
|
1357 """ |
|
1358 # not (not a) |
|
1359 if ( |
|
1360 isinstance(node.op, ast.Not) and |
|
1361 isinstance(node.operand, ast.UnaryOp) and |
|
1362 isinstance(node.operand.op, ast.Not) |
|
1363 ): |
|
1364 var = unparse(node.operand.operand) |
|
1365 self.__error(node.lineno - 1, node.col_offset, "Y208", var) |
|
1366 |
|
1367 def __check211(self, node): |
|
1368 """ |
|
1369 Private method to check for calls of the type "True if a else False". |
|
1370 |
|
1371 @param node reference to the AST node to be checked |
|
1372 @type ast.IfExp |
|
1373 """ |
|
1374 # True if a else False |
|
1375 if ( |
|
1376 isinstance(node.body, BOOL_CONST_TYPES) and |
|
1377 node.body.value is True and |
|
1378 isinstance(node.orelse, BOOL_CONST_TYPES) and |
|
1379 node.orelse.value is False |
|
1380 ): |
|
1381 cond = unparse(node.test) |
|
1382 if isinstance(node.test, ast.Name): |
|
1383 newCond = "bool({0})".format(cond) |
|
1384 else: |
|
1385 newCond = cond |
|
1386 self.__error(node.lineno - 1, node.col_offset, "Y211", |
|
1387 cond, newCond) |
|
1388 |
|
1389 def __check212(self, node): |
|
1390 """ |
|
1391 Private method to check for calls of the type "False if a else True". |
|
1392 |
|
1393 @param node reference to the AST node to be checked |
|
1394 @type ast.IfExp |
|
1395 """ |
|
1396 # False if a else True |
|
1397 if ( |
|
1398 isinstance(node.body, BOOL_CONST_TYPES) and |
|
1399 node.body.value is False and |
|
1400 isinstance(node.orelse, BOOL_CONST_TYPES) and |
|
1401 node.orelse.value is True |
|
1402 ): |
|
1403 cond = unparse(node.test) |
|
1404 if isinstance(node.test, ast.Name): |
|
1405 newCond = "not {0}".format(cond) |
|
1406 else: |
|
1407 if len(node.test.ops) == 1: |
|
1408 newCond = unparse(self.__negateTest(node.test)) |
|
1409 else: |
|
1410 newCond = "not ({0})".format(cond) |
|
1411 self.__error(node.lineno - 1, node.col_offset, "Y212", |
|
1412 cond, newCond) |
|
1413 |
|
1414 def __check213(self, node): |
|
1415 """ |
|
1416 Private method to check for calls of the type "b if not a else a". |
|
1417 |
|
1418 @param node reference to the AST node to be checked |
|
1419 @type ast.IfExp |
|
1420 """ |
|
1421 # b if not a else a |
|
1422 if ( |
|
1423 isinstance(node.test, ast.UnaryOp) and |
|
1424 isinstance(node.test.op, ast.Not) and |
|
1425 self.__isSameExpression(node.test.operand, node.orelse) |
|
1426 ): |
|
1427 a = unparse(node.test.operand) |
|
1428 b = unparse(node.body) |
|
1429 self.__error(node.lineno - 1, node.col_offset, "Y213", a, b) |
|
1430 |
|
1431 def __check221(self, node): |
|
1432 """ |
|
1433 Private method to check for calls of the type "a and not a". |
|
1434 |
|
1435 @param node reference to the AST node to be checked |
|
1436 @type ast.BoolOp |
|
1437 """ |
|
1438 # a and not a |
|
1439 if ( |
|
1440 isinstance(node.op, ast.And) and |
|
1441 len(node.values) >= 2 |
|
1442 ): |
|
1443 # We have a boolean And. Let's make sure there is two times the |
|
1444 # same expression, but once with a "not" |
|
1445 negatedExpressions = [] |
|
1446 nonNegatedExpressions = [] |
|
1447 for exp in node.values: |
|
1448 if ( |
|
1449 isinstance(exp, ast.UnaryOp) and |
|
1450 isinstance(exp.op, ast.Not) |
|
1451 ): |
|
1452 negatedExpressions.append(exp.operand) |
|
1453 else: |
|
1454 nonNegatedExpressions.append(exp) |
|
1455 for negatedExpression in negatedExpressions: |
|
1456 for nonNegatedExpression in nonNegatedExpressions: |
|
1457 if self.__isSameExpression( |
|
1458 negatedExpression, nonNegatedExpression |
|
1459 ): |
|
1460 negExp = unparse(negatedExpression) |
|
1461 self.__error(node.lineno - 1, node.col_offset, "Y221", |
|
1462 negExp) |
|
1463 |
|
1464 def __check222(self, node): |
|
1465 """ |
|
1466 Private method to check for calls of the type "a or not a". |
|
1467 |
|
1468 @param node reference to the AST node to be checked |
|
1469 @type ast.BoolOp |
|
1470 """ |
|
1471 # a or not a |
|
1472 if ( |
|
1473 isinstance(node.op, ast.Or) and |
|
1474 len(node.values) >= 2 |
|
1475 ): |
|
1476 # We have a boolean And. Let's make sure there is two times the |
|
1477 # same expression, but once with a "not" |
|
1478 negatedExpressions = [] |
|
1479 nonNegatedExpressions = [] |
|
1480 for exp in node.values: |
|
1481 if ( |
|
1482 isinstance(exp, ast.UnaryOp) and |
|
1483 isinstance(exp.op, ast.Not) |
|
1484 ): |
|
1485 negatedExpressions.append(exp.operand) |
|
1486 else: |
|
1487 nonNegatedExpressions.append(exp) |
|
1488 for negatedExpression in negatedExpressions: |
|
1489 for nonNegatedExpression in nonNegatedExpressions: |
|
1490 if self.__isSameExpression( |
|
1491 negatedExpression, nonNegatedExpression |
|
1492 ): |
|
1493 negExp = unparse(negatedExpression) |
|
1494 self.__error(node.lineno - 1, node.col_offset, "Y222", |
|
1495 negExp) |
|
1496 |
|
1497 def __check223(self, node): |
|
1498 """ |
|
1499 Private method to check for calls of the type "... or True". |
|
1500 |
|
1501 @param node reference to the AST node to be checked |
|
1502 @type ast.BoolOp |
|
1503 """ |
|
1504 # a or True |
|
1505 if isinstance(node.op, ast.Or): |
|
1506 for exp in node.values: |
|
1507 if isinstance(exp, BOOL_CONST_TYPES) and exp.value is True: |
|
1508 self.__error(node.lineno - 1, node.col_offset, "Y223") |
|
1509 |
|
1510 def __check224(self, node): |
|
1511 """ |
|
1512 Private method to check for calls of the type "... and False". |
|
1513 |
|
1514 @param node reference to the AST node to be checked |
|
1515 @type ast.BoolOp |
|
1516 """ |
|
1517 # a and False |
|
1518 if isinstance(node.op, ast.And): |
|
1519 for exp in node.values: |
|
1520 if isinstance(exp, BOOL_CONST_TYPES) and exp.value is False: |
|
1521 self.__error(node.lineno - 1, node.col_offset, "Y224") |
|
1522 |
|
1523 def __check301(self, node): |
|
1524 """ |
|
1525 Private method to check for Yoda conditions. |
|
1526 |
|
1527 @param node reference to the AST node to be checked |
|
1528 @type ast.Compare |
|
1529 """ |
|
1530 # 42 == age |
|
1531 if ( |
|
1532 isinstance(node.left, AST_CONST_TYPES) and |
|
1533 len(node.ops) == 1 and |
|
1534 isinstance(node.ops[0], ast.Eq) |
|
1535 ): |
|
1536 left = unparse(node.left) |
|
1537 isPy37Str = isinstance(node.left, ast.Str) |
|
1538 isPy38Str = ( |
|
1539 isinstance(node.left, ast.Constant) and |
|
1540 isinstance(node.left.value, str) |
|
1541 ) |
|
1542 if isPy37Str or isPy38Str: |
|
1543 left = f"'{left}'" |
|
1544 right = unparse(node.comparators[0]) |
|
1545 self.__error(node.lineno - 1, node.col_offset, "Y301", |
|
1546 left, right) |
|
1547 |
|
1548 def __check401(self, node): |
|
1549 """ |
|
1550 Private method to check for bare boolean function arguments. |
|
1551 |
|
1552 @param node reference to the AST node to be checked |
|
1553 @type ast.Call |
|
1554 """ |
|
1555 # foo(a, b, True) |
|
1556 hasBareBool = any( |
|
1557 isinstance(callArg, ast.Constant) and |
|
1558 (callArg.value is True or callArg.value is False) |
|
1559 for callArg in node.args |
|
1560 ) |
|
1561 |
|
1562 isException = ( |
|
1563 isinstance(node.func, ast.Attribute) and |
|
1564 node.func.attr in ["get"] |
|
1565 ) |
|
1566 |
|
1567 if hasBareBool and not isException: |
|
1568 self.__error(node.lineno - 1, node.col_offset, "Y401") |
|
1569 |
|
1570 def __check402(self, node): |
|
1571 """ |
|
1572 Private method to check for bare numeric function arguments. |
|
1573 |
|
1574 @param node reference to the AST node to be checked |
|
1575 @type ast.Call |
|
1576 """ |
|
1577 # foo(a, b, 123123) |
|
1578 hasBareNumeric = any( |
|
1579 isinstance(callArg, ast.Constant) and |
|
1580 type(callArg.value) in (float, int) |
|
1581 for callArg in node.args |
|
1582 ) |
|
1583 |
|
1584 isException = ( |
|
1585 isinstance(node.func, ast.Name) and |
|
1586 node.func.id == "range" |
|
1587 ) |
|
1588 isException = isException or ( |
|
1589 isinstance(node.func, ast.Attribute) and |
|
1590 node.func.attr in ("get", "insert") |
|
1591 ) |
|
1592 |
|
1593 if hasBareNumeric and not isException: |
|
1594 self.__error(node.lineno - 1, node.col_offset, "Y402") |
|
1595 |
|
1596 # |
|
1597 # eflag: noqa = M891 |