74 return [n.body + n.orelse] + [[hdl] for hdl in n.handlers] |
77 return [n.body + n.orelse] + [[hdl] for hdl in n.handlers] |
75 |
78 |
76 if PY35_PLUS: |
79 if PY35_PLUS: |
77 FOR_TYPES = (ast.For, ast.AsyncFor) |
80 FOR_TYPES = (ast.For, ast.AsyncFor) |
78 LOOP_TYPES = (ast.While, ast.For, ast.AsyncFor) |
81 LOOP_TYPES = (ast.While, ast.For, ast.AsyncFor) |
|
82 FUNCTION_TYPES = (ast.FunctionDef, ast.AsyncFunctionDef) |
79 else: |
83 else: |
80 FOR_TYPES = (ast.For,) |
84 FOR_TYPES = (ast.For,) |
81 LOOP_TYPES = (ast.While, ast.For) |
85 LOOP_TYPES = (ast.While, ast.For) |
82 |
86 FUNCTION_TYPES = (ast.FunctionDef,) |
83 # https://github.com/python/typed_ast/blob/55420396/ast27/Parser/tokenizer.c#L102-L104 |
87 |
|
88 # https://github.com/python/typed_ast/blob/1.4.0/ast27/Parser/tokenizer.c#L102-L104 |
84 TYPE_COMMENT_RE = re.compile(r'^#\s*type:\s*') |
89 TYPE_COMMENT_RE = re.compile(r'^#\s*type:\s*') |
85 # https://github.com/python/typed_ast/blob/55420396/ast27/Parser/tokenizer.c#L1400 |
90 # https://github.com/python/typed_ast/blob/1.4.0/ast27/Parser/tokenizer.c#L1408-L1413 |
86 TYPE_IGNORE_RE = re.compile(TYPE_COMMENT_RE.pattern + r'ignore\s*(#|$)') |
91 ASCII_NON_ALNUM = ''.join([chr(i) for i in range(128) if not chr(i).isalnum()]) |
87 # https://github.com/python/typed_ast/blob/55420396/ast27/Grammar/Grammar#L147 |
92 TYPE_IGNORE_RE = re.compile( |
|
93 TYPE_COMMENT_RE.pattern + r'ignore([{}]|$)'.format(ASCII_NON_ALNUM)) |
|
94 # https://github.com/python/typed_ast/blob/1.4.0/ast27/Grammar/Grammar#L147 |
88 TYPE_FUNC_RE = re.compile(r'^(\(.*?\))\s*->\s*(.*)$') |
95 TYPE_FUNC_RE = re.compile(r'^(\(.*?\))\s*->\s*(.*)$') |
|
96 |
|
97 |
|
98 MAPPING_KEY_RE = re.compile(r'\(([^()]*)\)') |
|
99 CONVERSION_FLAG_RE = re.compile('[#0+ -]*') |
|
100 WIDTH_RE = re.compile(r'(?:\*|\d*)') |
|
101 PRECISION_RE = re.compile(r'(?:\.(?:\*|\d*))?') |
|
102 LENGTH_RE = re.compile('[hlL]?') |
|
103 # https://docs.python.org/3/library/stdtypes.html#old-string-formatting |
|
104 VALID_CONVERSIONS = frozenset('diouxXeEfFgGcrsa%') |
|
105 |
|
106 |
|
107 def _must_match(regex, string, pos): |
|
108 # type: (Pattern[str], str, int) -> Match[str] |
|
109 match = regex.match(string, pos) |
|
110 assert match is not None |
|
111 return match |
|
112 |
|
113 |
|
114 def parse_percent_format(s): # type: (str) -> Tuple[PercentFormat, ...] |
|
115 """Parses the string component of a `'...' % ...` format call |
|
116 |
|
117 Copied from https://github.com/asottile/pyupgrade at v1.20.1 |
|
118 """ |
|
119 |
|
120 def _parse_inner(): |
|
121 # type: () -> Generator[PercentFormat, None, None] |
|
122 string_start = 0 |
|
123 string_end = 0 |
|
124 in_fmt = False |
|
125 |
|
126 i = 0 |
|
127 while i < len(s): |
|
128 if not in_fmt: |
|
129 try: |
|
130 i = s.index('%', i) |
|
131 except ValueError: # no more % fields! |
|
132 yield s[string_start:], None |
|
133 return |
|
134 else: |
|
135 string_end = i |
|
136 i += 1 |
|
137 in_fmt = True |
|
138 else: |
|
139 key_match = MAPPING_KEY_RE.match(s, i) |
|
140 if key_match: |
|
141 key = key_match.group(1) # type: Optional[str] |
|
142 i = key_match.end() |
|
143 else: |
|
144 key = None |
|
145 |
|
146 conversion_flag_match = _must_match(CONVERSION_FLAG_RE, s, i) |
|
147 conversion_flag = conversion_flag_match.group() or None |
|
148 i = conversion_flag_match.end() |
|
149 |
|
150 width_match = _must_match(WIDTH_RE, s, i) |
|
151 width = width_match.group() or None |
|
152 i = width_match.end() |
|
153 |
|
154 precision_match = _must_match(PRECISION_RE, s, i) |
|
155 precision = precision_match.group() or None |
|
156 i = precision_match.end() |
|
157 |
|
158 # length modifier is ignored |
|
159 i = _must_match(LENGTH_RE, s, i).end() |
|
160 |
|
161 try: |
|
162 conversion = s[i] |
|
163 except IndexError: |
|
164 raise ValueError('end-of-string while parsing format') |
|
165 i += 1 |
|
166 |
|
167 fmt = (key, conversion_flag, width, precision, conversion) |
|
168 yield s[string_start:string_end], fmt |
|
169 |
|
170 in_fmt = False |
|
171 string_start = i |
|
172 |
|
173 if in_fmt: |
|
174 raise ValueError('end-of-string while parsing format') |
|
175 |
|
176 return tuple(_parse_inner()) |
89 |
177 |
90 |
178 |
91 class _FieldsOrder(dict): |
179 class _FieldsOrder(dict): |
92 """Fix order of AST node fields.""" |
180 """Fix order of AST node fields.""" |
93 |
181 |
827 self.messages.append(messageClass(self.filename, *args, **kwargs)) |
917 self.messages.append(messageClass(self.filename, *args, **kwargs)) |
828 |
918 |
829 def getParent(self, node): |
919 def getParent(self, node): |
830 # Lookup the first parent which is not Tuple, List or Starred |
920 # Lookup the first parent which is not Tuple, List or Starred |
831 while True: |
921 while True: |
832 node = node.parent |
922 node = node._pyflakes_parent |
833 if not hasattr(node, 'elts') and not hasattr(node, 'ctx'): |
923 if not hasattr(node, 'elts') and not hasattr(node, 'ctx'): |
834 return node |
924 return node |
835 |
925 |
836 def getCommonAncestor(self, lnode, rnode, stop): |
926 def getCommonAncestor(self, lnode, rnode, stop): |
837 if stop in (lnode, rnode) or not (hasattr(lnode, 'parent') and |
927 if ( |
838 hasattr(rnode, 'parent')): |
928 stop in (lnode, rnode) or |
|
929 not ( |
|
930 hasattr(lnode, '_pyflakes_parent') and |
|
931 hasattr(rnode, '_pyflakes_parent') |
|
932 ) |
|
933 ): |
839 return None |
934 return None |
840 if lnode is rnode: |
935 if lnode is rnode: |
841 return lnode |
936 return lnode |
842 |
937 |
843 if (lnode.depth > rnode.depth): |
938 if (lnode._pyflakes_depth > rnode._pyflakes_depth): |
844 return self.getCommonAncestor(lnode.parent, rnode, stop) |
939 return self.getCommonAncestor(lnode._pyflakes_parent, rnode, stop) |
845 if (lnode.depth < rnode.depth): |
940 if (lnode._pyflakes_depth < rnode._pyflakes_depth): |
846 return self.getCommonAncestor(lnode, rnode.parent, stop) |
941 return self.getCommonAncestor(lnode, rnode._pyflakes_parent, stop) |
847 return self.getCommonAncestor(lnode.parent, rnode.parent, stop) |
942 return self.getCommonAncestor( |
|
943 lnode._pyflakes_parent, |
|
944 rnode._pyflakes_parent, |
|
945 stop, |
|
946 ) |
848 |
947 |
849 def descendantOf(self, node, ancestors, stop): |
948 def descendantOf(self, node, ancestors, stop): |
850 for a in ancestors: |
949 for a in ancestors: |
851 if self.getCommonAncestor(node, a, stop): |
950 if self.getCommonAncestor(node, a, stop): |
852 return True |
951 return True |
1019 scope[name].used[1], name, scope[name].source) |
1118 scope[name].used[1], name, scope[name].source) |
1020 break |
1119 break |
1021 |
1120 |
1022 parent_stmt = self.getParent(node) |
1121 parent_stmt = self.getParent(node) |
1023 if isinstance(parent_stmt, (FOR_TYPES, ast.comprehension)) or ( |
1122 if isinstance(parent_stmt, (FOR_TYPES, ast.comprehension)) or ( |
1024 parent_stmt != node.parent and |
1123 parent_stmt != node._pyflakes_parent and |
1025 not self.isLiteralTupleUnpacking(parent_stmt)): |
1124 not self.isLiteralTupleUnpacking(parent_stmt)): |
1026 binding = Binding(name, node) |
1125 binding = Binding(name, node) |
1027 elif name == '__all__' and isinstance(self.scope, ModuleScope): |
1126 elif name == '__all__' and isinstance(self.scope, ModuleScope): |
1028 binding = ExportBinding(name, node.parent, self.scope) |
1127 binding = ExportBinding(name, node._pyflakes_parent, self.scope) |
1029 elif isinstance(getattr(node, 'ctx', None), ast.Param): |
1128 elif isinstance(getattr(node, 'ctx', None), ast.Param): |
1030 binding = Argument(name, self.getScopeNode(node)) |
1129 binding = Argument(name, self.getScopeNode(node)) |
1031 else: |
1130 else: |
1032 binding = Assignment(name, node) |
1131 binding = Assignment(name, node) |
1033 self.addBinding(node, binding) |
1132 self.addBinding(node, binding) |
1237 EXPR = ASSIGN = handleChildren |
1336 EXPR = ASSIGN = handleChildren |
1238 |
1337 |
1239 PASS = ignore |
1338 PASS = ignore |
1240 |
1339 |
1241 # "expr" type nodes |
1340 # "expr" type nodes |
1242 BOOLOP = BINOP = UNARYOP = IFEXP = SET = \ |
1341 BOOLOP = UNARYOP = IFEXP = SET = \ |
1243 CALL = REPR = ATTRIBUTE = SUBSCRIPT = \ |
1342 REPR = ATTRIBUTE = SUBSCRIPT = \ |
1244 STARRED = NAMECONSTANT = NAMEDEXPR = handleChildren |
1343 STARRED = NAMECONSTANT = NAMEDEXPR = handleChildren |
|
1344 |
|
1345 def _handle_string_dot_format(self, node): |
|
1346 try: |
|
1347 placeholders = tuple(parse_format_string(node.func.value.s)) |
|
1348 except ValueError as e: |
|
1349 self.report(messages.StringDotFormatInvalidFormat, node, e) |
|
1350 return |
|
1351 |
|
1352 class state: # py2-compatible `nonlocal` |
|
1353 auto = None |
|
1354 next_auto = 0 |
|
1355 |
|
1356 placeholder_positional = set() |
|
1357 placeholder_named = set() |
|
1358 |
|
1359 def _add_key(fmtkey): |
|
1360 """Returns True if there is an error which should early-exit""" |
|
1361 if fmtkey is None: # end of string or `{` / `}` escapes |
|
1362 return False |
|
1363 |
|
1364 # attributes / indices are allowed in `.format(...)` |
|
1365 fmtkey, _, _ = fmtkey.partition('.') |
|
1366 fmtkey, _, _ = fmtkey.partition('[') |
|
1367 |
|
1368 try: |
|
1369 fmtkey = int(fmtkey) |
|
1370 except ValueError: |
|
1371 pass |
|
1372 else: # fmtkey was an integer |
|
1373 if state.auto is True: |
|
1374 self.report(messages.StringDotFormatMixingAutomatic, node) |
|
1375 return True |
|
1376 else: |
|
1377 state.auto = False |
|
1378 |
|
1379 if fmtkey == '': |
|
1380 if state.auto is False: |
|
1381 self.report(messages.StringDotFormatMixingAutomatic, node) |
|
1382 return True |
|
1383 else: |
|
1384 state.auto = True |
|
1385 |
|
1386 fmtkey = state.next_auto |
|
1387 state.next_auto += 1 |
|
1388 |
|
1389 if isinstance(fmtkey, int): |
|
1390 placeholder_positional.add(fmtkey) |
|
1391 else: |
|
1392 placeholder_named.add(fmtkey) |
|
1393 |
|
1394 return False |
|
1395 |
|
1396 for _, fmtkey, spec, _ in placeholders: |
|
1397 if _add_key(fmtkey): |
|
1398 return |
|
1399 |
|
1400 # spec can also contain format specifiers |
|
1401 if spec is not None: |
|
1402 try: |
|
1403 spec_placeholders = tuple(parse_format_string(spec)) |
|
1404 except ValueError as e: |
|
1405 self.report(messages.StringDotFormatInvalidFormat, node, e) |
|
1406 return |
|
1407 |
|
1408 for _, spec_fmtkey, spec_spec, _ in spec_placeholders: |
|
1409 # can't recurse again |
|
1410 if spec_spec is not None and '{' in spec_spec: |
|
1411 self.report( |
|
1412 messages.StringDotFormatInvalidFormat, |
|
1413 node, |
|
1414 'Max string recursion exceeded', |
|
1415 ) |
|
1416 return |
|
1417 if _add_key(spec_fmtkey): |
|
1418 return |
|
1419 |
|
1420 # bail early if there is *args or **kwargs |
|
1421 if ( |
|
1422 # python 2.x *args / **kwargs |
|
1423 getattr(node, 'starargs', None) or |
|
1424 getattr(node, 'kwargs', None) or |
|
1425 # python 3.x *args |
|
1426 any( |
|
1427 isinstance(arg, getattr(ast, 'Starred', ())) |
|
1428 for arg in node.args |
|
1429 ) or |
|
1430 # python 3.x **kwargs |
|
1431 any(kwd.arg is None for kwd in node.keywords) |
|
1432 ): |
|
1433 return |
|
1434 |
|
1435 substitution_positional = set(range(len(node.args))) |
|
1436 substitution_named = {kwd.arg for kwd in node.keywords} |
|
1437 |
|
1438 extra_positional = substitution_positional - placeholder_positional |
|
1439 extra_named = substitution_named - placeholder_named |
|
1440 |
|
1441 missing_arguments = ( |
|
1442 (placeholder_positional | placeholder_named) - |
|
1443 (substitution_positional | substitution_named) |
|
1444 ) |
|
1445 |
|
1446 if extra_positional: |
|
1447 self.report( |
|
1448 messages.StringDotFormatExtraPositionalArguments, |
|
1449 node, |
|
1450 ', '.join(sorted(str(x) for x in extra_positional)), |
|
1451 ) |
|
1452 if extra_named: |
|
1453 self.report( |
|
1454 messages.StringDotFormatExtraNamedArguments, |
|
1455 node, |
|
1456 ', '.join(sorted(extra_named)), |
|
1457 ) |
|
1458 if missing_arguments: |
|
1459 self.report( |
|
1460 messages.StringDotFormatMissingArgument, |
|
1461 node, |
|
1462 ', '.join(sorted(str(x) for x in missing_arguments)), |
|
1463 ) |
|
1464 |
|
1465 def CALL(self, node): |
|
1466 if ( |
|
1467 isinstance(node.func, ast.Attribute) and |
|
1468 isinstance(node.func.value, ast.Str) and |
|
1469 node.func.attr == 'format' |
|
1470 ): |
|
1471 self._handle_string_dot_format(node) |
|
1472 self.handleChildren(node) |
|
1473 |
|
1474 def _handle_percent_format(self, node): |
|
1475 try: |
|
1476 placeholders = parse_percent_format(node.left.s) |
|
1477 except ValueError: |
|
1478 self.report( |
|
1479 messages.PercentFormatInvalidFormat, |
|
1480 node, |
|
1481 'incomplete format', |
|
1482 ) |
|
1483 return |
|
1484 |
|
1485 named = set() |
|
1486 positional_count = 0 |
|
1487 positional = None |
|
1488 for _, placeholder in placeholders: |
|
1489 if placeholder is None: |
|
1490 continue |
|
1491 name, _, width, precision, conversion = placeholder |
|
1492 |
|
1493 if conversion == '%': |
|
1494 continue |
|
1495 |
|
1496 if conversion not in VALID_CONVERSIONS: |
|
1497 self.report( |
|
1498 messages.PercentFormatUnsupportedFormatCharacter, |
|
1499 node, |
|
1500 conversion, |
|
1501 ) |
|
1502 |
|
1503 if positional is None and conversion: |
|
1504 positional = name is None |
|
1505 |
|
1506 for part in (width, precision): |
|
1507 if part is not None and '*' in part: |
|
1508 if not positional: |
|
1509 self.report( |
|
1510 messages.PercentFormatStarRequiresSequence, |
|
1511 node, |
|
1512 ) |
|
1513 else: |
|
1514 positional_count += 1 |
|
1515 |
|
1516 if positional and name is not None: |
|
1517 self.report( |
|
1518 messages.PercentFormatMixedPositionalAndNamed, |
|
1519 node, |
|
1520 ) |
|
1521 return |
|
1522 elif not positional and name is None: |
|
1523 self.report( |
|
1524 messages.PercentFormatMixedPositionalAndNamed, |
|
1525 node, |
|
1526 ) |
|
1527 return |
|
1528 |
|
1529 if positional: |
|
1530 positional_count += 1 |
|
1531 else: |
|
1532 named.add(name) |
|
1533 |
|
1534 if ( |
|
1535 isinstance(node.right, (ast.List, ast.Tuple)) and |
|
1536 # does not have any *splats (py35+ feature) |
|
1537 not any( |
|
1538 isinstance(elt, getattr(ast, 'Starred', ())) |
|
1539 for elt in node.right.elts |
|
1540 ) |
|
1541 ): |
|
1542 substitution_count = len(node.right.elts) |
|
1543 if positional and positional_count != substitution_count: |
|
1544 self.report( |
|
1545 messages.PercentFormatPositionalCountMismatch, |
|
1546 node, |
|
1547 positional_count, |
|
1548 substitution_count, |
|
1549 ) |
|
1550 elif not positional: |
|
1551 self.report(messages.PercentFormatExpectedMapping, node) |
|
1552 |
|
1553 if ( |
|
1554 isinstance(node.right, ast.Dict) and |
|
1555 all(isinstance(k, ast.Str) for k in node.right.keys) |
|
1556 ): |
|
1557 if positional and positional_count > 1: |
|
1558 self.report(messages.PercentFormatExpectedSequence, node) |
|
1559 return |
|
1560 |
|
1561 substitution_keys = {k.s for k in node.right.keys} |
|
1562 extra_keys = substitution_keys - named |
|
1563 missing_keys = named - substitution_keys |
|
1564 if not positional and extra_keys: |
|
1565 self.report( |
|
1566 messages.PercentFormatExtraNamedArguments, |
|
1567 node, |
|
1568 ', '.join(sorted(extra_keys)), |
|
1569 ) |
|
1570 if not positional and missing_keys: |
|
1571 self.report( |
|
1572 messages.PercentFormatMissingArgument, |
|
1573 node, |
|
1574 ', '.join(sorted(missing_keys)), |
|
1575 ) |
|
1576 |
|
1577 def BINOP(self, node): |
|
1578 if ( |
|
1579 isinstance(node.op, ast.Mod) and |
|
1580 isinstance(node.left, ast.Str) |
|
1581 ): |
|
1582 self._handle_percent_format(node) |
|
1583 self.handleChildren(node) |
1245 |
1584 |
1246 NUM = STR = BYTES = ELLIPSIS = CONSTANT = ignore |
1585 NUM = STR = BYTES = ELLIPSIS = CONSTANT = ignore |
1247 |
1586 |
1248 # "slice" type nodes |
1587 # "slice" type nodes |
1249 SLICE = EXTSLICE = INDEX = handleChildren |
1588 SLICE = EXTSLICE = INDEX = handleChildren |
1377 def CONTINUE(self, node): |
1733 def CONTINUE(self, node): |
1378 # Walk the tree up until we see a loop (OK), a function or class |
1734 # Walk the tree up until we see a loop (OK), a function or class |
1379 # definition (not OK), for 'continue', a finally block (not OK), or |
1735 # definition (not OK), for 'continue', a finally block (not OK), or |
1380 # the top module scope (not OK) |
1736 # the top module scope (not OK) |
1381 n = node |
1737 n = node |
1382 while hasattr(n, 'parent'): |
1738 while hasattr(n, '_pyflakes_parent'): |
1383 n, n_child = n.parent, n |
1739 n, n_child = n._pyflakes_parent, n |
1384 if isinstance(n, LOOP_TYPES): |
1740 if isinstance(n, LOOP_TYPES): |
1385 # Doesn't apply unless it's in the loop itself |
1741 # Doesn't apply unless it's in the loop itself |
1386 if n_child not in n.orelse: |
1742 if n_child not in n.orelse: |
1387 return |
1743 return |
1388 if isinstance(n, (ast.FunctionDef, ast.ClassDef)): |
1744 if isinstance(n, (ast.FunctionDef, ast.ClassDef)): |
1389 break |
1745 break |
1390 # Handle Try/TryFinally difference in Python < and >= 3.3 |
1746 # Handle Try/TryFinally difference in Python < and >= 3.3 |
1391 if hasattr(n, 'finalbody') and isinstance(node, ast.Continue): |
1747 if hasattr(n, 'finalbody') and isinstance(node, ast.Continue): |
1392 if n_child in n.finalbody: |
1748 if n_child in n.finalbody and not PY38_PLUS: |
1393 self.report(messages.ContinueInFinally, node) |
1749 self.report(messages.ContinueInFinally, node) |
1394 return |
1750 return |
1395 if isinstance(node, ast.Continue): |
1751 if isinstance(node, ast.Continue): |
1396 self.report(messages.ContinueOutsideLoop, node) |
1752 self.report(messages.ContinueOutsideLoop, node) |
1397 else: # ast.Break |
1753 else: # ast.Break |