152 |
167 |
153 arg0Name = unparse(call.args[0]) |
168 arg0Name = unparse(call.args[0]) |
154 counter[arg0Name] += 1 |
169 counter[arg0Name] += 1 |
155 |
170 |
156 return [name for name, count in counter.items() if count > 1] |
171 return [name for name, count in counter.items() if count > 1] |
|
172 |
|
173 def __isConstantIncrease(self, expression): |
|
174 """ |
|
175 Private method check the given expression for being a constant |
|
176 increase. |
|
177 |
|
178 @param expression reference to the expression node |
|
179 @type ast.AugAssign |
|
180 @return flag indicating a constant increase |
|
181 @rtype bool |
|
182 """ |
|
183 return ( |
|
184 isinstance(expression.op, ast.Add) and |
|
185 isinstance(expression.value, (ast.Constant, ast.Num)) |
|
186 ) |
|
187 |
|
188 def __getIfBodyPairs(self, node): |
|
189 """ |
|
190 Private method to extract a list of pairs of test and body for an |
|
191 If node. |
|
192 |
|
193 @param node reference to the If node to be processed |
|
194 @type ast.If |
|
195 @return list of pairs of test and body |
|
196 @rtype list of tuples of (ast.expr, [ast.stmt]) |
|
197 """ |
|
198 pairs = [(node.test, node.body)] |
|
199 orelse = node.orelse |
|
200 while ( |
|
201 isinstance(orelse, list) and |
|
202 len(orelse) == 1 and |
|
203 isinstance(orelse[0], ast.If) |
|
204 ): |
|
205 pairs.append((orelse[0].test, orelse[0].body)) |
|
206 orelse = orelse[0].orelse |
|
207 return pairs |
|
208 |
|
209 def __isSameBody(self, body1, body2): |
|
210 """ |
|
211 Private method check, if the given bodies are equivalent. |
|
212 |
|
213 @param body1 list of statements of the first body |
|
214 @type list of ast.stmt |
|
215 @param body2 list of statements of the second body |
|
216 @type list of ast.stmt |
|
217 @return flag indicating identical bodies |
|
218 @rtype bool |
|
219 """ |
|
220 if len(body1) != len(body2): |
|
221 return False |
|
222 for a, b in zip(body1, body2): |
|
223 try: |
|
224 statementEqual = self.__isStatementEqual(a, b) |
|
225 except RecursionError: # maximum recursion depth |
|
226 statementEqual = False |
|
227 if not statementEqual: |
|
228 return False |
|
229 return True |
|
230 |
|
231 def __isStatementEqual(self, a: ast.stmt, b: ast.stmt) -> bool: |
|
232 """ |
|
233 Private method to check, if two statements are equal. |
|
234 |
|
235 @param a reference to the first statement |
|
236 @type ast.stmt |
|
237 @param b reference to the second statement |
|
238 @type ast.stmt |
|
239 @return flag indicating if the two statements are equal |
|
240 @rtype bool |
|
241 """ |
|
242 if type(a) is not type(b): |
|
243 return False |
|
244 |
|
245 if isinstance(a, ast.AST): |
|
246 for k, v in vars(a).items(): |
|
247 if k in ("lineno", "col_offset", "ctx", "end_lineno", |
|
248 "parent"): |
|
249 continue |
|
250 if not self.__isStatementEqual(v, getattr(b, k)): |
|
251 return False |
|
252 return True |
|
253 elif isinstance(a, list): |
|
254 return all(itertools.starmap(self.__isStatementEqual, zip(a, b))) |
|
255 else: |
|
256 return a == b |
157 |
257 |
158 ############################################################# |
258 ############################################################# |
159 ## Methods to check for possible code simplifications below |
259 ## Methods to check for possible code simplifications below |
160 ############################################################# |
260 ############################################################# |
161 |
261 |
427 # for x in iterable: |
527 # for x in iterable: |
428 # if check(x): |
528 # if check(x): |
429 # return False |
529 # return False |
430 # return True |
530 # return True |
431 if ( |
531 if ( |
432 len(node.body) == 1 |
532 len(node.body) == 1 and |
433 and isinstance(node.body[0], ast.If) |
533 isinstance(node.body[0], ast.If) and |
434 and len(node.body[0].body) == 1 |
534 len(node.body[0].body) == 1 and |
435 and isinstance(node.body[0].body[0], ast.Return) |
535 isinstance(node.body[0].body[0], ast.Return) and |
436 and isinstance(node.body[0].body[0].value, BOOL_CONST_TYPES) |
536 isinstance(node.body[0].body[0].value, BOOL_CONST_TYPES) and |
437 and hasattr(node.body[0].body[0].value, "value") |
537 hasattr(node.body[0].body[0].value, "value") |
438 ): |
538 ): |
439 check = unparse(node.body[0].test) |
539 check = unparse(node.body[0].test) |
440 target = unparse(node.target) |
540 target = unparse(node.target) |
441 iterable = unparse(node.iter) |
541 iterable = unparse(node.iter) |
442 if node.body[0].body[0].value.value is True: |
542 if node.body[0].body[0].value.value is True: |
449 self.__error(node.lineno - 1, node.col_offset, "Y111", |
549 self.__error(node.lineno - 1, node.col_offset, "Y111", |
450 check, target, iterable) |
550 check, target, iterable) |
451 |
551 |
452 def __check112(self, node): |
552 def __check112(self, node): |
453 """ |
553 """ |
454 Public method to check for non-capitalized calls to environment |
554 Private method to check for non-capitalized calls to environment |
455 variables. |
555 variables. |
456 |
556 |
457 @param node reference to the AST node to be checked |
557 @param node reference to the AST node to be checked |
458 @type ast.Expr |
558 @type ast.Expr |
459 """ |
559 """ |
460 # os.environ["foo"] |
560 # os.environ["foo"] |
461 # os.environ.get("bar") |
561 # os.environ.get("bar") |
462 isIndexCall = ( |
562 isIndexCall = ( |
463 isinstance(node.value, ast.Subscript) |
563 isinstance(node.value, ast.Subscript) and |
464 and isinstance(node.value.value, ast.Attribute) |
564 isinstance(node.value.value, ast.Attribute) and |
465 and isinstance(node.value.value.value, ast.Name) |
565 isinstance(node.value.value.value, ast.Name) and |
466 and node.value.value.value.id == "os" |
566 node.value.value.value.id == "os" and |
467 and node.value.value.attr == "environ" |
567 node.value.value.attr == "environ" and |
468 and ( |
568 ( |
469 ( |
569 ( |
470 isinstance(node.value.slice, ast.Index) |
570 isinstance(node.value.slice, ast.Index) and |
471 and isinstance(node.value.slice.value, STR_TYPES) |
571 isinstance(node.value.slice.value, STR_TYPES) |
472 ) |
572 ) or |
473 or isinstance(node.value.slice, ast.Constant) |
573 isinstance(node.value.slice, ast.Constant) |
474 ) |
574 ) |
475 ) |
575 ) |
476 if isIndexCall: |
576 if isIndexCall: |
477 subscript = node.value |
577 subscript = node.value |
478 slice_ = subscript.slice |
578 slice_ = subscript.slice |
489 |
589 |
490 # Check if this has a change |
590 # Check if this has a change |
491 hasChange = envName != envName.upper() |
591 hasChange = envName != envName.upper() |
492 |
592 |
493 isGetCall = ( |
593 isGetCall = ( |
494 isinstance(node.value, ast.Call) |
594 isinstance(node.value, ast.Call) and |
495 and isinstance(node.value.func, ast.Attribute) |
595 isinstance(node.value.func, ast.Attribute) and |
496 and isinstance(node.value.func.value, ast.Attribute) |
596 isinstance(node.value.func.value, ast.Attribute) and |
497 and isinstance(node.value.func.value.value, ast.Name) |
597 isinstance(node.value.func.value.value, ast.Name) and |
498 and node.value.func.value.value.id == "os" |
598 node.value.func.value.value.id == "os" and |
499 and node.value.func.value.attr == "environ" |
599 node.value.func.value.attr == "environ" and |
500 and node.value.func.attr == "get" |
600 node.value.func.attr == "get" and |
501 and len(node.value.args) in [1, 2] |
601 len(node.value.args) in [1, 2] and |
502 and isinstance(node.value.args[0], STR_TYPES) |
602 isinstance(node.value.args[0], STR_TYPES) |
503 ) |
603 ) |
504 if isGetCall: |
604 if isGetCall: |
505 call = node.value |
605 call = node.value |
506 stringPart = call.args[0] |
606 stringPart = call.args[0] |
507 if isinstance(stringPart, ast.Str): |
607 if isinstance(stringPart, ast.Str): |
527 else: |
627 else: |
528 return |
628 return |
529 |
629 |
530 self.__error(node.lineno - 1, node.col_offset, "Y112", expected, |
630 self.__error(node.lineno - 1, node.col_offset, "Y112", expected, |
531 original) |
631 original) |
|
632 |
|
633 def __check113(self, node): |
|
634 """ |
|
635 Private method to check for loops in which "enumerate" should be |
|
636 used. |
|
637 |
|
638 @param node reference to the AST node to be checked |
|
639 @type ast.For |
|
640 """ |
|
641 # idx = 0 |
|
642 # for el in iterable: |
|
643 # ... |
|
644 # idx += 1 |
|
645 variableCandidates = [] |
|
646 for expression in node.body: |
|
647 if ( |
|
648 isinstance(expression, ast.AugAssign) and |
|
649 self.__isConstantIncrease(expression) and |
|
650 isinstance(expression.target, ast.Name) |
|
651 ): |
|
652 variableCandidates.append(expression.target) |
|
653 |
|
654 for candidate in variableCandidates: |
|
655 self.__error(candidate.lineno - 1, candidate.col_offset, "Y113", |
|
656 unparse(candidate)) |
|
657 |
|
658 def __check114(self, node): |
|
659 """ |
|
660 Private method to check for alternative if clauses with identical |
|
661 bodies. |
|
662 |
|
663 @param node reference to the AST node to be checked |
|
664 @type ast.If |
|
665 """ |
|
666 # if a: |
|
667 # b |
|
668 # elif c: |
|
669 # b |
|
670 ifBodyPairs = self.__getIfBodyPairs(node) |
|
671 errorPairs = [] |
|
672 for ifbody1, ifbody2 in itertools.combinations(ifBodyPairs, 2): |
|
673 if self.__isSameBody(ifbody1[1], ifbody2[1]): |
|
674 errorPairs.append((ifbody1, ifbody2)) |
|
675 for ifbody1, ifbody2 in errorPairs: |
|
676 self.__error(ifbody1[0].lineno - 1, ifbody1[0].col_offset, "Y114", |
|
677 unparse(ifbody1[0]), unparse(ifbody2[0])) |
|
678 |
|
679 def __check115(self, node): |
|
680 """ |
|
681 Private method to to check for places where open() is called without |
|
682 a context handler. |
|
683 |
|
684 @param node reference to the AST node to be checked |
|
685 @type ast.Call |
|
686 """ |
|
687 # f = open(...) |
|
688 #. .. # (do something with f) |
|
689 # f.close() |
|
690 if ( |
|
691 isinstance(node.func, ast.Name) and |
|
692 node.func.id == "open" and |
|
693 not isinstance(node.parent, ast.withitem) |
|
694 ): |
|
695 self.__error(node.lineno - 1, node.col_offset, "Y115") |
|
696 |
|
697 def __check116(self, node): |
|
698 """ |
|
699 Private method to check for places with 3 or more consecutive |
|
700 if-statements with direct returns. |
|
701 |
|
702 * Each if-statement must be a check for equality with the |
|
703 same variable |
|
704 * Each if-statement must just have a "return" |
|
705 * Else must also just have a return |
|
706 |
|
707 @param node reference to the AST node to be checked |
|
708 @type ast.If |
|
709 """ |
|
710 # if a == "foo": |
|
711 # return "bar" |
|
712 # elif a == "bar": |
|
713 # return "baz" |
|
714 # elif a == "boo": |
|
715 # return "ooh" |
|
716 # else: |
|
717 # return 42 |
|
718 if ( |
|
719 isinstance(node.test, ast.Compare) and |
|
720 isinstance(node.test.left, ast.Name) and |
|
721 len(node.test.ops) == 1 and |
|
722 isinstance(node.test.ops[0], ast.Eq) and |
|
723 len(node.test.comparators) == 1 and |
|
724 isinstance(node.test.comparators[0], AST_CONST_TYPES) and |
|
725 len(node.body) == 1 and |
|
726 isinstance(node.body[0], ast.Return) and |
|
727 len(node.orelse) == 1 and |
|
728 isinstance(node.orelse[0], ast.If) |
|
729 ): |
|
730 variable = node.test.left |
|
731 child = node.orelse[0] |
|
732 elseValue = None |
|
733 if isinstance(node.test.comparators[0], ast.Str): |
|
734 keyValuePairs = { |
|
735 node.test.comparators[0].s: |
|
736 unparse(node.body[0].value).strip("'") |
|
737 } |
|
738 elif isinstance(node.test.comparators[0], ast.Num): |
|
739 keyValuePairs = { |
|
740 node.test.comparators[0].n: |
|
741 unparse(node.body[0].value).strip("'") |
|
742 } |
|
743 else: |
|
744 keyValuePairs = { |
|
745 node.test.comparators[0].value: |
|
746 unparse(node.body[0].value).strip("'") |
|
747 } |
|
748 while child: |
|
749 if not ( |
|
750 isinstance(child.test, ast.Compare) and |
|
751 isinstance(child.test.left, ast.Name) and |
|
752 child.test.left.id == variable.id and |
|
753 len(child.test.ops) == 1 and |
|
754 isinstance(child.test.ops[0], ast.Eq) and |
|
755 len(child.test.comparators) == 1 and |
|
756 isinstance(child.test.comparators[0], AST_CONST_TYPES) and |
|
757 len(child.body) == 1 and |
|
758 isinstance(child.body[0], ast.Return) and |
|
759 len(child.orelse) <= 1 |
|
760 ): |
|
761 return |
|
762 |
|
763 if isinstance(child.test.comparators[0], ast.Str): |
|
764 key = child.test.comparators[0].s |
|
765 elif isinstance(child.test.comparators[0], ast.Num): |
|
766 key = child.test.comparators[0].n |
|
767 else: |
|
768 key = child.test.comparators[0].value |
|
769 keyValuePairs[key] = unparse(child.body[0].value).strip("'") |
|
770 if len(child.orelse) == 1: |
|
771 if isinstance(child.orelse[0], ast.If): |
|
772 child = child.orelse[0] |
|
773 elif isinstance(child.orelse[0], ast.Return): |
|
774 elseValue = unparse(child.orelse[0].value) |
|
775 child = None |
|
776 else: |
|
777 return |
|
778 else: |
|
779 child = None |
|
780 |
|
781 if len(keyValuePairs) < 3: |
|
782 return |
|
783 |
|
784 if elseValue: |
|
785 ret = f"{keyValuePairs}.get({variable.id}, {elseValue})" |
|
786 else: |
|
787 ret = f"{keyValuePairs}.get({variable.id})" |
|
788 |
|
789 self.__error(node.lineno - 1, node.col_offset, "Y116", ret) |
532 |
790 |
533 # |
791 # |
534 # eflag: noqa = M891 |
792 # eflag: noqa = M891 |