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

branch
eric7
changeset 9277
471c5a263d53
parent 9274
86fab0c74430
child 9278
36448ca469c2
equal deleted inserted replaced
9276:e6748a5e24b9 9277:471c5a263d53
9 9
10 import ast 10 import ast
11 import collections 11 import collections
12 import copy 12 import copy
13 import itertools 13 import itertools
14 import json
14 15
15 try: 16 try:
16 from ast import unparse 17 from ast import unparse
17 except ImportError: 18 except ImportError:
18 # Python < 3.9 19 # Python < 3.9
19 from .ast_unparse import unparse 20 from .ast_unparse import unparse
20 21
21 ###################################################################### 22 ###############################################################################
22 ## The following code is derived from the flake8-simplify package. 23 ## The following code is derived from the flake8-simplify package (v0.19.2).
23 ## 24 ##
24 ## Original License: 25 ## Original License:
25 ## 26 ##
26 ## MIT License 27 ## MIT License
27 ## 28 ##
42 ## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 43 ## 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 ## 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 ## 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 ## FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
46 ## IN THE SOFTWARE. 47 ## IN THE SOFTWARE.
47 ###################################################################### 48 ###############################################################################
48 # TODO: update to bandit v0.19.2
49 49
50 BOOL_CONST_TYPES = (ast.Constant, ast.NameConstant) 50 BOOL_CONST_TYPES = (ast.Constant, ast.NameConstant)
51 AST_CONST_TYPES = (ast.Constant, ast.NameConstant, ast.Str, ast.Num) 51 AST_CONST_TYPES = (ast.Constant, ast.NameConstant, ast.Str, ast.Num)
52 STR_TYPES = (ast.Constant, ast.Str) 52 STR_TYPES = (ast.Constant, ast.Str)
53 53
88 88
89 @param node reference to the Assign node 89 @param node reference to the Assign node
90 @type ast.Assign 90 @type ast.Assign
91 """ 91 """
92 self.__check181(node) 92 self.__check181(node)
93 self.__check904(node)
94 self.__check909(node)
93 95
94 self.generic_visit(node) 96 self.generic_visit(node)
95 97
96 def visit_BoolOp(self, node): 98 def visit_BoolOp(self, node):
97 """ 99 """
121 self.__check106(node) 123 self.__check106(node)
122 self.__check108(node) 124 self.__check108(node)
123 self.__check114(node) 125 self.__check114(node)
124 self.__check116(node) 126 self.__check116(node)
125 self.__check122(node) 127 self.__check122(node)
128 self.__check123(node)
126 129
127 self.generic_visit(node) 130 self.generic_visit(node)
128 131
129 def visit_IfExp(self, node): 132 def visit_IfExp(self, node):
130 """ 133 """
174 """ 177 """
175 self.__check115(node) 178 self.__check115(node)
176 self.__check182(node) 179 self.__check182(node)
177 self.__check401(node) 180 self.__check401(node)
178 self.__check402(node) 181 self.__check402(node)
182 self.__check901(node)
183 self.__check905(node)
184 self.__check906(node)
179 185
180 self.generic_visit(node) 186 self.generic_visit(node)
181 187
182 def visit_With(self, node): 188 def visit_With(self, node):
183 """ 189 """
234 self.__check206(node) 240 self.__check206(node)
235 self.__check207(node) 241 self.__check207(node)
236 self.__check208(node) 242 self.__check208(node)
237 243
238 self.generic_visit(node) 244 self.generic_visit(node)
245
246 def visit_Subscript(self, node):
247 """
248 Public method to process a Subscript node.
249
250 @param node reference to the Subscript node
251 @type ast.Subscript
252 """
253 self.__check907(node)
254
255 self.generic_visit(node)
239 256
240 ############################################################# 257 #############################################################
241 ## Helper methods for the various checkers below 258 ## Helper methods for the various checkers below
242 ############################################################# 259 #############################################################
243 260
264 arg0Name = unparse(call.args[0]) 281 arg0Name = unparse(call.args[0])
265 counter[arg0Name] += 1 282 counter[arg0Name] += 1
266 283
267 return [name for name, count in counter.items() if count > 1] 284 return [name for name, count in counter.items() if count > 1]
268 285
269 def __isConstantIncrease(self, expression): 286 def __isConstantIncrease(self, expr):
270 """ 287 """
271 Private method check the given expression for being a constant 288 Private method to check an expression for being a constant increase.
272 increase. 289
273 290 @param expr reference to the node to be checked
274 @param expression reference to the expression node
275 @type ast.AugAssign 291 @type ast.AugAssign
276 @return flag indicating a constant increase 292 @return flag indicating a constant increase
277 @rtype bool 293 @rtype bool
278 """ 294 """
279 return isinstance(expression.op, ast.Add) and ( 295 return isinstance(expr.op, ast.Add) and (
280 ( 296 (
281 isinstance(expression.value, ast.Constant) 297 isinstance(expr.value, ast.Constant)
282 and isinstance(expression.value.value, int) 298 and expr.value.value == 1
283 ) 299 )
284 or isinstance(expression.value, ast.Num) 300 or (isinstance(expr.value, ast.Num) and expr.value.n == 1)
285 ) 301 )
286 302
287 def __getIfBodyPairs(self, node): 303 def __getIfBodyPairs(self, node):
288 """ 304 """
289 Private method to extract a list of pairs of test and body for an 305 Private method to extract a list of pairs of test and body for an
413 elif isinstance(op, ast.NotIn): 429 elif isinstance(op, ast.NotIn):
414 op = ast.In() 430 op = ast.In()
415 newNode.ops = [op] 431 newNode.ops = [op]
416 return newNode 432 return newNode
417 433
434 def __expressionUsesVariable(self, expr, var):
435 """
436 Private method to check, if a variable is used by an expression.
437
438 @param expr expression node to be checked
439 @type ast.expr
440 @param var variable name to be checked for
441 @type str
442 @return flag indicating the expression uses the variable
443 @rtype bool
444 """
445 return var in unparse(expr)
446 # This is WAY too broad, but it's better to have false-negatives than
447 # false-positives.
448
449 def __bodyContainsContinue(self, stmts):
450 """
451 Private method to check, if a list of statements contain a 'continue' statement.
452
453 @param stmts list of statements
454 @type list of ast.stmt
455 @return flag indicating a continue statement
456 @rtype bool
457 """
458 return any(
459 isinstance(stmt, ast.Continue)
460 or (isinstance(stmt, ast.If) and self.__bodyContainsContinue(stmt.body))
461 for stmt in stmts
462 )
463
418 ############################################################# 464 #############################################################
419 ## Methods to check for possible code simplifications below 465 ## Methods to check for possible code simplifications below
420 ############################################################# 466 #############################################################
421 467
422 def __check101(self, node): 468 def __check101(self, node):
512 or not isinstance(node.body[0].value, ast.Yield) 558 or not isinstance(node.body[0].value, ast.Yield)
513 or not isinstance(node.target, ast.Name) 559 or not isinstance(node.target, ast.Name)
514 or not isinstance(node.body[0].value.value, ast.Name) 560 or not isinstance(node.body[0].value.value, ast.Name)
515 or node.target.id != node.body[0].value.value.id 561 or node.target.id != node.body[0].value.value.id
516 or node.orelse != [] 562 or node.orelse != []
563 or isinstance(node.parent, ast.AsyncFunctionDef)
517 ): 564 ):
518 iterable = unparse(node.iter) 565 iterable = unparse(node.iter)
519 self.__error(node.lineno - 1, node.col_offset, "Y104", iterable) 566 self.__error(node.lineno - 1, node.col_offset, "Y104", iterable)
520 567
521 def __check105(self, node): 568 def __check105(self, node):
607 break 654 break
608 655
609 if ( 656 if (
610 (tryHasReturn or exceptHasReturn) 657 (tryHasReturn or exceptHasReturn)
611 and finallyHasReturn 658 and finallyHasReturn
612 and finallyReturn is not None
613 ): 659 ):
614 self.__error(finallyReturn.lineno - 1, finallyReturn.col_offset, "Y107") 660 self.__error(finallyReturn.lineno - 1, finallyReturn.col_offset, "Y107")
615 661
616 def __check108(self, node): 662 def __check108(self, node):
617 """ 663 """
643 and isinstance(node.body[0].targets[0], ast.Name) 689 and isinstance(node.body[0].targets[0], ast.Name)
644 and isinstance(node.orelse[0].targets[0], ast.Name) 690 and isinstance(node.orelse[0].targets[0], ast.Name)
645 and node.body[0].targets[0].id == node.orelse[0].targets[0].id 691 and node.body[0].targets[0].id == node.orelse[0].targets[0].id
646 and not isinstance(node.parent, ast.If) 692 and not isinstance(node.parent, ast.If)
647 ): 693 ):
648 assign = unparse(node.body[0].targets[0]) 694 targetVar = node.body[0].targets[0]
695 assign = unparse(targetVar)
696
697 # It's part of a bigger if-elseif block:
698 if isinstance(node.parent, ast.If):
699 for n in node.parent.body:
700 if (
701 isinstance(n, ast.Assign)
702 and isinstance(n.targets[0], ast.Name)
703 and n.targets[0].id == targetVar.id
704 ):
705 return
706
649 body = unparse(node.body[0].value) 707 body = unparse(node.body[0].value)
650 cond = unparse(node.test) 708 cond = unparse(node.test)
651 orelse = unparse(node.orelse[0].value) 709 orelse = unparse(node.orelse[0].value)
652 710
653 self.__error( 711 self.__error(
718 and isinstance(node.body[0], ast.If) 776 and isinstance(node.body[0], ast.If)
719 and len(node.body[0].body) == 1 777 and len(node.body[0].body) == 1
720 and isinstance(node.body[0].body[0], ast.Return) 778 and isinstance(node.body[0].body[0], ast.Return)
721 and isinstance(node.body[0].body[0].value, BOOL_CONST_TYPES) 779 and isinstance(node.body[0].body[0].value, BOOL_CONST_TYPES)
722 and hasattr(node.body[0].body[0].value, "value") 780 and hasattr(node.body[0].body[0].value, "value")
781 and isinstance(node.next_sibling, ast.Return)
723 ): 782 ):
724 check = unparse(node.body[0].test) 783 check = unparse(node.body[0].test)
725 target = unparse(node.target) 784 target = unparse(node.target)
726 iterable = unparse(node.iter) 785 iterable = unparse(node.iter)
727 if node.body[0].body[0].value.value is True: 786 if node.body[0].body[0].value.value is True:
728 self.__error( 787 self.__error(
729 node.lineno - 1, node.col_offset, "Y110", check, target, iterable 788 node.lineno - 1, node.col_offset, "Y110", check, target, iterable
730 ) 789 )
731 elif node.body[0].body[0].value.value is False: 790 elif node.body[0].body[0].value.value is False:
732 check = "not " + check 791 isCompoundExpression = " and " in check or " or " in check
733 if check.startswith("not not "): 792
734 check = check[len("not not ") :] 793 if isCompoundExpression:
794 check = f"not ({check})"
795 else:
796 if check.startswith("not "):
797 check = check[len("not "):]
798 else:
799 check = f"not {check}"
735 self.__error( 800 self.__error(
736 node.lineno - 1, node.col_offset, "Y111", check, target, iterable 801 node.lineno - 1, node.col_offset, "Y111", check, target, iterable
737 ) 802 )
738 803
739 def __check112(self, node): 804 def __check112(self, node):
824 """ 889 """
825 # idx = 0 890 # idx = 0
826 # for el in iterable: 891 # for el in iterable:
827 # ... 892 # ...
828 # idx += 1 893 # idx += 1
829 variableCandidates = [] 894 if not self.__bodyContainsContinue(node.body):
830 for expression in node.body: 895 # Find variables that might just count the iteration of the current loop
831 if ( 896 variableCandidates = []
832 isinstance(expression, ast.AugAssign) 897 for expression in node.body:
833 and self.__isConstantIncrease(expression) 898 if (
834 and isinstance(expression.target, ast.Name) 899 isinstance(expression, ast.AugAssign)
835 ): 900 and self.__isConstantIncrease(expression)
836 variableCandidates.append(expression.target) 901 and isinstance(expression.target, ast.Name)
837 902 ):
838 for candidate in variableCandidates: 903 variableCandidates.append(expression.target)
839 self.__error( 904 strCandidates = [unparse(x) for x in variableCandidates]
840 candidate.lineno - 1, candidate.col_offset, "Y113", unparse(candidate) 905
841 ) 906 olderSiblings = []
907 for olderSibling in node.parent.body:
908 if olderSibling is node:
909 break
910 olderSiblings.append(olderSibling)
911
912 matches = [
913 n.targets[0]
914 for n in olderSiblings
915 if isinstance(n, ast.Assign)
916 and len(n.targets) == 1
917 and isinstance(n.targets[0], ast.Name)
918 and unparse(n.targets[0]) in strCandidates
919 ]
920 if len(matches) == 0:
921 return
922
923 sibling = node.previous_sibling
924 while sibling is not None:
925 sibling = sibling.previous_sibling
926
927 for match in matches:
928 variable = unparse(match)
929 self.__error(
930 match.lineno - 1, match.col_offset, "Y113", variable
931 )
842 932
843 def __check114(self, node): 933 def __check114(self, node):
844 """ 934 """
845 Private method to check for alternative if clauses with identical 935 Private method to check for alternative if clauses with identical
846 bodies. 936 bodies.
852 # b 942 # b
853 # elif c: 943 # elif c:
854 # b 944 # b
855 ifBodyPairs = self.__getIfBodyPairs(node) 945 ifBodyPairs = self.__getIfBodyPairs(node)
856 errorPairs = [] 946 errorPairs = []
857 for ifbody1, ifbody2 in itertools.combinations(ifBodyPairs, 2): 947 for i in range(len(ifBodyPairs) - 1):
948 # It's not all combinations because of this:
949 ifbody1 = ifBodyPairs[i]
950 ifbody2 = ifBodyPairs[i + 1]
858 if self.__isSameBody(ifbody1[1], ifbody2[1]): 951 if self.__isSameBody(ifbody1[1], ifbody2[1]):
859 errorPairs.append((ifbody1, ifbody2)) 952 errorPairs.append((ifbody1, ifbody2))
860 for ifbody1, ifbody2 in errorPairs: 953 for ifbody1, ifbody2 in errorPairs:
861 self.__error( 954 self.__error(
862 ifbody1[0].lineno - 1, 955 ifbody1[0].lineno - 1,
923 if node.body[0].value is not None: 1016 if node.body[0].value is not None:
924 bodyValueStr = unparse(node.body[0].value).strip("'") 1017 bodyValueStr = unparse(node.body[0].value).strip("'")
925 else: 1018 else:
926 bodyValueStr = "None" 1019 bodyValueStr = "None"
927 if isinstance(node.test.comparators[0], ast.Str): 1020 if isinstance(node.test.comparators[0], ast.Str):
928 keyValuePairs = {node.test.comparators[0].s: bodyValueStr} 1021 value = (
1022 bodyValueStr
1023 if bodyValueStr[0] == '"' and bodyValueStr[-1] == '"' else
1024 bodyValueStr[1:-1]
1025 )
1026 keyValuePairs = {node.test.comparators[0].s: value}
929 elif isinstance(node.test.comparators[0], ast.Num): 1027 elif isinstance(node.test.comparators[0], ast.Num):
930 keyValuePairs = { 1028 keyValuePairs = {node.test.comparators[0].n: bodyValueStr}
931 node.test.comparators[0].n: bodyValueStr,
932 }
933 else: 1029 else:
934 keyValuePairs = {node.test.comparators[0].value: bodyValueStr} 1030 keyValuePairs = {node.test.comparators[0].value: bodyValueStr}
935 while child: 1031 while child:
936 if not ( 1032 if not (
937 isinstance(child.test, ast.Compare) 1033 isinstance(child.test, ast.Compare)
945 and isinstance(child.body[0], ast.Return) 1041 and isinstance(child.body[0], ast.Return)
946 and len(child.orelse) <= 1 1042 and len(child.orelse) <= 1
947 ): 1043 ):
948 return 1044 return
949 1045
1046 returnCall = child.body[0]
1047 if isinstance(returnCall.value, ast.Call):
1048 return
1049
950 if isinstance(child.test.comparators[0], ast.Str): 1050 if isinstance(child.test.comparators[0], ast.Str):
951 key = child.test.comparators[0].s 1051 key = child.test.comparators[0].s
952 elif isinstance(child.test.comparators[0], ast.Num): 1052 elif isinstance(child.test.comparators[0], ast.Num):
953 key = child.test.comparators[0].n 1053 key = child.test.comparators[0].n
954 else: 1054 else:
955 key = child.test.comparators[0].value 1055 key = child.test.comparators[0].value
956 keyValuePairs[key] = unparse(child.body[0].value).strip("'") 1056
1057 value = unparse(child.body[0].value)
1058 if value[0] == '"' and value[-1] == '"':
1059 value = value[1:-1]
1060 keyValuePairs[key] = value
1061
957 if len(child.orelse) == 1: 1062 if len(child.orelse) == 1:
958 if isinstance(child.orelse[0], ast.If): 1063 if isinstance(child.orelse[0], ast.If):
959 child = child.orelse[0] 1064 child = child.orelse[0]
960 elif isinstance(child.orelse[0], ast.Return): 1065 elif isinstance(child.orelse[0], ast.Return):
961 elseValue = unparse(child.orelse[0].value) 1066 elseValue = unparse(child.orelse[0].value)
1123 ): 1228 ):
1124 key = unparse(node.test.left) 1229 key = unparse(node.test.left)
1125 dictname = unparse(node.test.comparators[0]) 1230 dictname = unparse(node.test.comparators[0])
1126 self.__error(node.lineno - 1, node.col_offset, "Y122", dictname, key) 1231 self.__error(node.lineno - 1, node.col_offset, "Y122", dictname, key)
1127 1232
1233 def __check123(self, node):
1234 """
1235 Private method to check for complicated dictionary access with default value.
1236
1237 @param node reference to the AST node to be checked
1238 @type ast.If
1239 """
1240 isPattern1 = (
1241 len(node.body) == 1
1242 and isinstance(node.body[0], ast.Assign)
1243 and len(node.body[0].targets) == 1
1244 and isinstance(node.body[0].value, ast.Subscript)
1245 and len(node.orelse) == 1
1246 and isinstance(node.orelse[0], ast.Assign)
1247 and len(node.orelse[0].targets) == 1
1248 and isinstance(node.test, ast.Compare)
1249 and len(node.test.ops) == 1
1250 and isinstance(node.test.ops[0], ast.In)
1251 )
1252
1253 # just like pattern_1, but using NotIn and reversing if/else
1254 isPattern2 = (
1255 len(node.body) == 1
1256 and isinstance(node.body[0], ast.Assign)
1257 and len(node.orelse) == 1
1258 and isinstance(node.orelse[0], ast.Assign)
1259 and isinstance(node.orelse[0].value, ast.Subscript)
1260 and isinstance(node.test, ast.Compare)
1261 and len(node.test.ops) == 1
1262 and isinstance(node.test.ops[0], ast.NotIn)
1263 )
1264
1265 if isPattern1:
1266 key = node.test.left
1267 if unparse(key) != unparse(node.body[0].value.slice):
1268 return
1269 assignToIfBody = node.body[0].targets[0]
1270 assignToElse = node.orelse[0].targets[0]
1271 if unparse(assignToIfBody) != unparse(assignToElse):
1272 return
1273 dictName = node.test.comparators[0]
1274 defaultValue = node.orelse[0].value
1275 valueNode = node.body[0].targets[0]
1276 keyStr = unparse(key)
1277 dictStr = unparse(dictName)
1278 defaultStr = unparse(defaultValue)
1279 valueStr = unparse(valueNode)
1280 elif isPattern2:
1281 key = node.test.left
1282 if unparse(key) != unparse(node.orelse[0].value.slice):
1283 return
1284 dictName = node.test.comparators[0]
1285 defaultValue = node.body[0].value
1286 valueNode = node.body[0].targets[0]
1287 keyStr = unparse(key)
1288 dictStr = unparse(dictName)
1289 defaultStr = unparse(defaultValue)
1290 valueStr = unparse(valueNode)
1291 else:
1292 return
1293 self.__error(node.lineno - 1, node.col_offset, "Y123", valueStr, dictStr,
1294 keyStr, defaultStr)
1295
1128 def __check181(self, node): 1296 def __check181(self, node):
1129 """ 1297 """
1130 Private method to check for assignments that could be converted into 1298 Private method to check for assignments that could be converted into
1131 an augmented assignment. 1299 an augmented assignment.
1132 1300
1548 ) 1716 )
1549 1717
1550 if hasBareNumeric and not isException: 1718 if hasBareNumeric and not isException:
1551 self.__error(node.lineno - 1, node.col_offset, "Y402") 1719 self.__error(node.lineno - 1, node.col_offset, "Y402")
1552 1720
1721 def __check901(self, node):
1722 """
1723 Private method to check for unnecessary bool conversion.
1724
1725 @param node reference to the AST node to be checked
1726 @type ast.Call
1727 """
1728 if (
1729 isinstance(node.func, ast.Name)
1730 and node.func.id == "bool"
1731 and len(node.args) == 1
1732 and isinstance(node.args[0], ast.Compare)
1733 ):
1734 actual = unparse(node)
1735 expected = unparse(node.args[0])
1736 self.__error(node.lineno - 1, node.col_offset, "Y901", expected, actual)
1737
1738 def __check904(self, node):
1739 """
1740 Private method to check for dictionary initialization.
1741
1742 @param node reference to the AST node to be checked
1743 @type ast.Assign
1744 """
1745 # a = {}; a['b'] = 'c'
1746 n2 = node.next_sibling
1747 if (
1748 isinstance(node.value, ast.Dict)
1749 and isinstance(n2, ast.Assign)
1750 and len(n2.targets) == 1
1751 and len(node.targets) == 1
1752 and isinstance(n2.targets[0], ast.Subscript)
1753 and isinstance(n2.targets[0].value, ast.Name)
1754 and isinstance(node.targets[0], ast.Name)
1755 and n2.targets[0].value.id == node.targets[0].id
1756 ):
1757 dictName = unparse(node.targets[0])
1758 if not self.__expressionUsesVariable(n2.value, dictName):
1759 self.__error(node.lineno - 1, node.col_offset, "Y904", dictName)
1760
1761 def __check905(self, node):
1762 """
1763 Private method to check for list initialization by splitting a string.
1764
1765 @param node reference to the AST node to be checked
1766 @type ast.Call
1767 """
1768 if (
1769 isinstance(node.func, ast.Attribute)
1770 and node.func.attr == "split"
1771 and isinstance(node.func.value, (ast.Str, ast.Constant))
1772 ):
1773 if isinstance(node.func.value, ast.Constant):
1774 value = node.func.value.value
1775 else:
1776 value = node.func.value.s
1777
1778 expected = json.dumps(value.split())
1779 actual = unparse(node.func.value) + ".split()"
1780 self.__error(node.lineno - 1, node.col_offset, "Y905", expected, actual)
1781
1782 def __check906(self, node):
1783 """
1784 Private method to check for unnecessary nesting of os.path.join().
1785
1786 @param node reference to the AST node to be checked
1787 @type ast.Call
1788 """
1789 # __IGNORE_WARNING_D234r__
1790 def getOsPathJoinArgs(node):
1791 names = []
1792 for arg in node.args:
1793 if (
1794 isinstance(arg, ast.Call)
1795 and isinstance(arg.func, ast.Attribute)
1796 and isinstance(arg.func.value, ast.Attribute)
1797 and isinstance(arg.func.value.value, ast.Name)
1798 and arg.func.value.value.id == "os"
1799 and arg.func.value.attr == "path"
1800 and arg.func.attr == "join"
1801 ):
1802 names += getOsPathJoinArgs(arg)
1803 elif isinstance(arg, ast.Name):
1804 names.append(arg.id)
1805 elif isinstance(arg, ast.Str):
1806 names.append(f"'{arg.s}'")
1807 return names
1808
1809 if (
1810 isinstance(node.func, ast.Attribute)
1811 and isinstance(node.func.value, ast.Attribute)
1812 and isinstance(node.func.value.value, ast.Name)
1813 and node.func.value.value.id == "os"
1814 and node.func.value.attr == "path"
1815 and node.func.attr == "join"
1816 and len(node.args) == 2
1817 and any(
1818 (
1819 isinstance(arg, ast.Call)
1820 and isinstance(arg.func, ast.Attribute)
1821 and isinstance(arg.func.value, ast.Attribute)
1822 and isinstance(arg.func.value.value, ast.Name)
1823 and arg.func.value.value.id == "os"
1824 and arg.func.value.attr == "path"
1825 and arg.func.attr == "join"
1826 )
1827 for arg in node.args
1828 )
1829 ):
1830 names = getOsPathJoinArgs(node)
1831
1832 actual = unparse(node)
1833 expected = "os.path.join({0})".format(', '.join(names))
1834 self.__error(node.lineno - 1, node.col_offset, "Y906", expected, actual)
1835
1836 def __check907(self, node):
1837 """
1838 Private method to check for Union type annotation with None.
1839
1840 @param node reference to the AST node to be checked
1841 @type ast.Subscript
1842 """
1843 if (isinstance(node.value, ast.Name) and node.value.id == "Union"):
1844 if (
1845 isinstance(node.slice, ast.Index)
1846 and isinstance(node.slice.value, ast.Tuple)
1847 ):
1848 # Python 3.8
1849 tupleVar = node.slice.value
1850 elif isinstance(node.slice, ast.Tuple):
1851 # Python 3.9+
1852 tupleVar = node.slice
1853 else:
1854 return
1855
1856 hasNone = False
1857 others = []
1858 for elt in tupleVar.elts:
1859 if isinstance(elt, BOOL_CONST_TYPES) and elt.value is None:
1860 hasNone = True
1861 else:
1862 others.append(elt)
1863
1864 if len(others) == 1 and hasNone:
1865 type_ = unparse(others[0])
1866 self.__error(
1867 node.lineno - 1, node.col_offset, "Y907", type_, unparse(node)
1868 )
1869
1870 def __check909(self, node):
1871 """
1872 Private method to check for reflexive assignments.
1873
1874 @param node reference to the AST node to be checked
1875 @type ast.Assign
1876 """
1877 names = []
1878 if isinstance(node.value, (ast.Name, ast.Subscript, ast.Tuple)):
1879 names.append(unparse(node.value))
1880 for target in node.targets:
1881 names.append(unparse(target))
1882
1883 if len(names) != len(set(names)) and not isinstance(node.parent, ast.ClassDef):
1884 srccode = unparse(node)
1885 self.__error(node.lineno - 1, node.col_offset, "Y909", srccode)
1553 1886
1554 # 1887 #
1555 # eflag: noqa = M891 1888 # eflag: noqa = M891

eric ide

mercurial