eric7/Plugins/CheckerPlugins/CodeStyleChecker/Simplify/SimplifyNodeVisitor.py

branch
eric7
changeset 8312
800c432b34c8
parent 8251
ee6af20714c3
child 8881
54e42bc2437a
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
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

eric ide

mercurial