GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing github.com — vscode-python/pythonFiles/parso/python/errors.py at master · charismafight/vscode-python Expand file tree/ Copy patherrors.pyMore file actions 1053 lines (867 loc) · 38.8 KB / Copy patherrors.pyFile metadata and controls1053 lines (867 loc) · 38.8 KB 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 # -*- coding: utf-8 -*- import codecs import warnings import re from contextlib import contextmanager from parso.normalizer import Normalizer, NormalizerConfig, Issue, Rule from parso.python.tree import search_ancestor from parso.parser import ParserSyntaxError _BLOCK_STMTS = ('if_stmt', 'while_stmt', 'for_stmt', 'try_stmt', 'with_stmt') _STAR_EXPR_PARENTS = ('testlist_star_expr', 'testlist_comp', 'exprlist') # This is the maximal block size given by python. _MAX_BLOCK_SIZE = 20 _MAX_INDENT_COUNT = 100 ALLOWED_FUTURES = ( 'all_feature_names', 'nested_scopes', 'generators', 'division', 'absolute_import', 'with_statement', 'print_function', 'unicode_literals', ) def _iter_stmts(scope): """ Iterates over all statements and splits up simple_stmt. """ for child in scope.children: if child.type == 'simple_stmt': for child2 in child.children: if child2.type == 'newline' or child2 == ';': continue yield child2 else: yield child def _get_comprehension_type(atom): first, second = atom.children[:2] if second.type == 'testlist_comp' and second.children[1].type == 'comp_for': if first == '[': return 'list comprehension' else: return 'generator expression' elif second.type == 'dictorsetmaker' and second.children[-1].type == 'comp_for': if second.children[1] == ':': return 'dict comprehension' else: return 'set comprehension' return None def _is_future_import(import_from): # It looks like a __future__ import that is relative is still a future # import. That feels kind of odd, but whatever. # if import_from.level != 0: # return False from_names = import_from.get_from_names() return [n.value for n in from_names] == ['__future__'] def _remove_parens(atom): """ Returns the inner part of an expression like `(foo)`. Also removes nested parens. """ try: children = atom.children except AttributeError: pass else: if len(children) == 3 and children[0] == '(': return _remove_parens(atom.children[1]) return atom def _iter_params(parent_node): return (n for n in parent_node.children if n.type == 'param') def _is_future_import_first(import_from): """ Checks if the import is the first statement of a file. """ found_docstring = False for stmt in _iter_stmts(import_from.get_root_node()): if stmt.type == 'string' and not found_docstring: continue found_docstring = True if stmt == import_from: return True if stmt.type == 'import_from' and _is_future_import(stmt): continue return False def _iter_definition_exprs_from_lists(exprlist): for child in exprlist.children[::2]: if child.type == 'atom' and child.children[0] in ('(', '['): testlist_comp = child.children[0] if testlist_comp.type == 'testlist_comp': for expr in _iter_definition_exprs_from_lists(testlist_comp): yield expr continue elif child.children[0] == '[': yield testlist_comp continue yield child def _get_expr_stmt_definition_exprs(expr_stmt): exprs = [] for list_ in expr_stmt.children[:-2:2]: if list_.type in ('testlist_star_expr', 'testlist'): exprs += _iter_definition_exprs_from_lists(list_) else: exprs.append(list_) return exprs def _get_for_stmt_definition_exprs(for_stmt): exprlist = for_stmt.children[1] if exprlist.type != 'exprlist': return [exprlist] return list(_iter_definition_exprs_from_lists(exprlist)) class _Context(object): def __init__(self, node, add_syntax_error, parent_context=None): self.node = node self.blocks = [] self.parent_context = parent_context self._used_name_dict = {} self._global_names = [] self._nonlocal_names = [] self._nonlocal_names_in_subscopes = [] self._add_syntax_error = add_syntax_error def is_async_funcdef(self): # Stupidly enough async funcdefs can have two different forms, # depending if a decorator is used or not. return self.is_function() \ and self.node.parent.type in ('async_funcdef', 'async_stmt') def is_function(self): return self.node.type == 'funcdef' def add_name(self, name): parent_type = name.parent.type if parent_type == 'trailer': # We are only interested in first level names. return if parent_type == 'global_stmt': self._global_names.append(name) elif parent_type == 'nonlocal_stmt': self._nonlocal_names.append(name) else: self._used_name_dict.setdefault(name.value, []).append(name) def finalize(self): """ Returns a list of nonlocal names that need to be part of that scope. """ self._analyze_names(self._global_names, 'global') self._analyze_names(self._nonlocal_names, 'nonlocal') # Python2.6 doesn't have dict comprehensions. global_name_strs = dict((n.value, n) for n in self._global_names) for nonlocal_name in self._nonlocal_names: try: global_name = global_name_strs[nonlocal_name.value] except KeyError: continue message = "name '%s' is nonlocal and global" % global_name.value if global_name.start_pos < nonlocal_name.start_pos: error_name = global_name else: error_name = nonlocal_name self._add_syntax_error(error_name, message) nonlocals_not_handled = [] for nonlocal_name in self._nonlocal_names_in_subscopes: search = nonlocal_name.value if search in global_name_strs or self.parent_context is None: message = "no binding for nonlocal '%s' found" % nonlocal_name.value self._add_syntax_error(nonlocal_name, message) elif not self.is_function() or \ nonlocal_name.value not in self._used_name_dict: nonlocals_not_handled.append(nonlocal_name) return self._nonlocal_names + nonlocals_not_handled def _analyze_names(self, globals_or_nonlocals, type_): def raise_(message): self._add_syntax_error(base_name, message % (base_name.value, type_)) params = [] if self.node.type == 'funcdef': params = self.node.get_params() for base_name in globals_or_nonlocals: found_global_or_nonlocal = False # Somehow Python does it the reversed way. for name in reversed(self._used_name_dict.get(base_name.value, [])): if name.start_pos > base_name.start_pos: # All following names don't have to be checked. found_global_or_nonlocal = True parent = name.parent if parent.type == 'param' and parent.name == name: # Skip those here, these definitions belong to the next # scope. continue if name.is_definition(): if parent.type == 'expr_stmt' \ and parent.children[1].type == 'annassign': if found_global_or_nonlocal: # If it's after the global the error seems to be # placed there. base_name = name raise_("annotated name '%s' can't be %s") break else: message = "name '%s' is assigned to before %s declaration" else: message = "name '%s' is used prior to %s declaration" if not found_global_or_nonlocal: raise_(message) # Only add an error for the first occurence. break for param in params: if param.name.value == base_name.value: raise_("name '%s' is parameter and %s"), @contextmanager def add_block(self, node): self.blocks.append(node) yield self.blocks.pop() def add_context(self, node): return _Context(node, self._add_syntax_error, parent_context=self) def close_child_context(self, child_context): self._nonlocal_names_in_subscopes += child_context.finalize() class ErrorFinder(Normalizer): """ Searches for errors in the syntax tree. """ def __init__(self, *args, **kwargs): super(ErrorFinder, self).__init__(*args, **kwargs) self._error_dict = {} self.version = self.grammar.version_info def initialize(self, node): def create_context(node): if node is None: return None parent_context = create_context(node.parent) if node.type in ('classdef', 'funcdef', 'file_input'): return _Context(node, self._add_syntax_error, parent_context) return parent_context self.context = create_context(node) or _Context(node, self._add_syntax_error) self._indentation_count = 0 def visit(self, node): if node.type == 'error_node': with self.visit_node(node): # Don't need to investigate the inners of an error node. We # might find errors in there that should be ignored, because # the error node itself already shows that there's an issue. return '' return super(ErrorFinder, self).visit(node) @contextmanager def visit_node(self, node): self._check_type_rules(node) if node.type in _BLOCK_STMTS: with self.context.add_block(node): if len(self.context.blocks) == _MAX_BLOCK_SIZE: self._add_syntax_error(node, "too many statically nested blocks") yield return elif node.type == 'suite': self._indentation_count += 1 if self._indentation_count == _MAX_INDENT_COUNT: self._add_indentation_error(node.children[1], "too many levels of indentation") yield if node.type == 'suite': self._indentation_count -= 1 elif node.type in ('classdef', 'funcdef'): context = self.context self.context = context.parent_context self.context.close_child_context(context) def visit_leaf(self, leaf): if leaf.type == 'error_leaf': if leaf.original_type in ('indent', 'error_dedent'): # Indents/Dedents itself never have a prefix. They are just # "pseudo" tokens that get removed by the syntax tree later. # Therefore in case of an error we also have to check for this. spacing = list(leaf.get_next_leaf()._split_prefix())[-1] if leaf.original_type == 'indent': message = 'unexpected indent' else: message = 'unindent does not match any outer indentation level' self._add_indentation_error(spacing, message) else: if leaf.value.startswith('\\'): message = 'unexpected character after line continuation character' else: match = re.match('\\w{,2}("{1,3}|\'{1,3})', leaf.value) if match is None: message = 'invalid syntax' else: if len(match.group(1)) == 1: message = 'EOL while scanning string literal' else: message = 'EOF while scanning triple-quoted string literal' self._add_syntax_error(leaf, message) return '' elif leaf.value == ':': parent = leaf.parent if parent.type in ('classdef', 'funcdef'): self.context = self.context.add_context(parent) # The rest is rule based. return super(ErrorFinder, self).visit_leaf(leaf) def _add_indentation_error(self, spacing, message): self.add_issue(spacing, 903, "IndentationError: " + message) def _add_syntax_error(self, node, message): self.add_issue(node, 901, "SyntaxError: " + message) def add_issue(self, node, code, message): # Overwrite the default behavior. # Check if the issues are on the same line. line = node.start_pos[0] args = (code, message, node) self._error_dict.setdefault(line, args) def finalize(self): self.context.finalize() for code, message, node in self._error_dict.values(): self.issues.append(Issue(node, code, message)) class IndentationRule(Rule): code = 903 def _get_message(self, message): message = super(IndentationRule, self)._get_message(message) return "IndentationError: " + message @ErrorFinder.register_rule(type='error_node') class _ExpectIndentedBlock(IndentationRule): message = 'expected an indented block' def get_node(self, node): leaf = node.get_next_leaf() return list(leaf._split_prefix())[-1] def is_issue(self, node): # This is the beginning of a suite that is not indented. return node.children[-1].type == 'newline' class ErrorFinderConfig(NormalizerConfig): normalizer_class = ErrorFinder class SyntaxRule(Rule): code = 901 def _get_message(self, message): message = super(SyntaxRule, self)._get_message(message) return "SyntaxError: " + message @ErrorFinder.register_rule(type='error_node') class _InvalidSyntaxRule(SyntaxRule): message = "invalid syntax" def get_node(self, node): return node.get_next_leaf() def is_issue(self, node): # Error leafs will be added later as an error. return node.get_next_leaf().type != 'error_leaf' @ErrorFinder.register_rule(value='await') class _AwaitOutsideAsync(SyntaxRule): message = "'await' outside async function" def is_issue(self, leaf): return not self._normalizer.context.is_async_funcdef() def get_error_node(self, node): # Return the whole await statement. return node.parent @ErrorFinder.register_rule(value='break') class _BreakOutsideLoop(SyntaxRule): message = "'break' outside loop" def is_issue(self, leaf): in_loop = False for block in self._normalizer.context.blocks: if block.type in ('for_stmt', 'while_stmt'): in_loop = True return not in_loop @ErrorFinder.register_rule(value='continue') class _ContinueChecks(SyntaxRule): message = "'continue' not properly in loop" message_in_finally = "'continue' not supported inside 'finally' clause" def is_issue(self, leaf): in_loop = False for block in self._normalizer.context.blocks: if block.type in ('for_stmt', 'while_stmt'): in_loop = True if block.type == 'try_stmt': last_block = block.children[-3] if last_block == 'finally' and leaf.start_pos > last_block.start_pos: self.add_issue(leaf, message=self.message_in_finally) return False # Error already added if not in_loop: return True @ErrorFinder.register_rule(value='from') class _YieldFromCheck(SyntaxRule): message = "'yield from' inside async function" def get_node(self, leaf): return leaf.parent.parent # This is the actual yield statement. def is_issue(self, leaf): return leaf.parent.type == 'yield_arg' \ and self._normalizer.context.is_async_funcdef() @ErrorFinder.register_rule(type='name') class _NameChecks(SyntaxRule): message = 'cannot assign to __debug__' message_keyword = 'assignment to keyword' message_none = 'cannot assign to None' def is_issue(self, leaf): self._normalizer.context.add_name(leaf) if leaf.value == '__debug__' and leaf.is_definition(): if self._normalizer.version < (3, 0): return True else: self.add_issue(leaf, message=self.message_keyword) if leaf.value == 'None' and self._normalizer.version < (3, 0) \ and leaf.is_definition(): self.add_issue(leaf, message=self.message_none) @ErrorFinder.register_rule(type='string') class _StringChecks(SyntaxRule): message = "bytes can only contain ASCII literal characters." def is_issue(self, leaf): string_prefix = leaf.string_prefix.lower() if 'b' in string_prefix \ and self._normalizer.version >= (3, 0) \ and any(c for c in leaf.value if ord(c) > 127): # b'ä' return True if 'r' not in string_prefix: # Raw strings don't need to be checked if they have proper # escaping. is_bytes = self._normalizer.version < (3, 0) if 'b' in string_prefix: is_bytes = True if 'u' in string_prefix: is_bytes = False payload = leaf._get_payload() if is_bytes: payload = payload.encode('utf-8') func = codecs.escape_decode else: func = codecs.unicode_escape_decode try: with warnings.catch_warnings(): # The warnings from parsing strings are not relevant. warnings.filterwarnings('ignore') func(payload) except UnicodeDecodeError as e: self.add_issue(leaf, message='(unicode error) ' + str(e)) except ValueError as e: self.add_issue(leaf, message='(value error) ' + str(e)) @ErrorFinder.register_rule(value='*') class _StarCheck(SyntaxRule): message = "named arguments must follow bare *" def is_issue(self, leaf): params = leaf.parent if params.type == 'parameters' and params: after = params.children[params.children.index(leaf) + 1:] after = [child for child in after if child not in (',', ')') and not child.star_count] return len(after) == 0 @ErrorFinder.register_rule(value='**') class _StarStarCheck(SyntaxRule): # e.g. {**{} for a in [1]} # TODO this should probably get a better end_pos including # the next sibling of leaf. message = "dict unpacking cannot be used in dict comprehension" def is_issue(self, leaf): if leaf.parent.type == 'dictorsetmaker': comp_for = leaf.get_next_sibling().get_next_sibling() return comp_for is not None and comp_for.type == 'comp_for' @ErrorFinder.register_rule(value='yield') @ErrorFinder.register_rule(value='return') class _ReturnAndYieldChecks(SyntaxRule): message = "'return' with value in async generator" message_async_yield = "'yield' inside async function" def get_node(self, leaf): return leaf.parent def is_issue(self, leaf): if self._normalizer.context.node.type != 'funcdef': self.add_issue(self.get_node(leaf), message="'%s' outside function" % leaf.value) elif self._normalizer.context.is_async_funcdef() \ and any(self._normalizer.context.node.iter_yield_exprs()): if leaf.value == 'return' and leaf.parent.type == 'return_stmt': return True elif leaf.value == 'yield' \ and leaf.get_next_leaf() != 'from' \ and self._normalizer.version == (3, 5): self.add_issue(self.get_node(leaf), message=self.message_async_yield) @ErrorFinder.register_rule(type='atom') class _BytesAndStringMix(SyntaxRule): # e.g. 's' b'' message = "cannot mix bytes and nonbytes literals" def _is_bytes_literal(self, string): return 'b' in string.string_prefix.lower() def is_issue(self, node): first = node.children[0] if first.type == 'string' and self._normalizer.version >= (3, 0): first_is_bytes = self._is_bytes_literal(first) for string in node.children[1:]: if first_is_bytes != self._is_bytes_literal(string): return True @ErrorFinder.register_rule(type='import_as_names') class _TrailingImportComma(SyntaxRule): # e.g. from foo import a, message = "trailing comma not allowed without surrounding parentheses" def is_issue(self, node): if node.children[-1] == ',': return True @ErrorFinder.register_rule(type='import_from') class _ImportStarInFunction(SyntaxRule): message = "import * only allowed at module level" def is_issue(self, node): return node.is_star_import() and self._normalizer.context.parent_context is not None @ErrorFinder.register_rule(type='import_from') class _FutureImportRule(SyntaxRule): message = "from __future__ imports must occur at the beginning of the file" def is_issue(self, node): if _is_future_import(node): if not _is_future_import_first(node): return True for from_name, future_name in node.get_paths(): name = future_name.value allowed_futures = list(ALLOWED_FUTURES) if self._normalizer.version >= (3, 5): allowed_futures.append('generator_stop') if name == 'braces': self.add_issue(node, message = "not a chance") elif name == 'barry_as_FLUFL': m = "Seriously I'm not implementing this :) ~ Dave" self.add_issue(node, message=m) elif name not in ALLOWED_FUTURES: message = "future feature %s is not defined" % name self.add_issue(node, message=message) @ErrorFinder.register_rule(type='star_expr') class _StarExprRule(SyntaxRule): message = "starred assignment target must be in a list or tuple" message_iterable_unpacking = "iterable unpacking cannot be used in comprehension" message_assignment = "can use starred expression only as assignment target" def is_issue(self, node): if node.parent.type not in _STAR_EXPR_PARENTS: return True if node.parent.type == 'testlist_comp': # [*[] for a in [1]] if node.parent.children[1].type == 'comp_for': self.add_issue(node, message=self.message_iterable_unpacking) if self._normalizer.version <= (3, 4): n = search_ancestor(node, 'for_stmt', 'expr_stmt') found_definition = False if n is not None: if n.type == 'expr_stmt': exprs = _get_expr_stmt_definition_exprs(n) else: exprs = _get_for_stmt_definition_exprs(n) if node in exprs: found_definition = True if not found_definition: self.add_issue(node, message=self.message_assignment) @ErrorFinder.register_rule(types=_STAR_EXPR_PARENTS) class _StarExprParentRule(SyntaxRule): def is_issue(self, node): if node.parent.type == 'del_stmt': self.add_issue(node.parent, message="can't use starred expression here") else: def is_definition(node, ancestor): if ancestor is None: return False type_ = ancestor.type if type_ == 'trailer': return False if type_ == 'expr_stmt': return node.start_pos < ancestor.children[-1].start_pos return is_definition(node, ancestor.parent) if is_definition(node, node.parent): args = [c for c in node.children if c != ','] starred = [c for c in args if c.type == 'star_expr'] if len(starred) > 1: message = "two starred expressions in assignment" self.add_issue(starred[1], message=message) elif starred: count = args.index(starred[0]) if count >= 256: message = "too many expressions in star-unpacking assignment" self.add_issue(starred[0], message=message) @ErrorFinder.register_rule(type='annassign') class _AnnotatorRule(SyntaxRule): # True: int # {}: float message = "illegal target for annotation" def get_node(self, node): return node.parent def is_issue(self, node): type_ = None lhs = node.parent.children[0] lhs = _remove_parens(lhs) try: children = lhs.children except AttributeError: pass else: if ',' in children or lhs.type == 'atom' and children[0] == '(': type_ = 'tuple' elif lhs.type == 'atom' and children[0] == '[': type_ = 'list' trailer = children[-1] if type_ is None: if not (lhs.type == 'name' # subscript/attributes are allowed or lhs.type in ('atom_expr', 'power') and trailer.type == 'trailer' and trailer.children[0] != '('): return True else: # x, y: str message = "only single target (not %s) can be annotated" self.add_issue(lhs.parent, message=message % type_) @ErrorFinder.register_rule(type='argument') class _ArgumentRule(SyntaxRule): def is_issue(self, node): first = node.children[0] if node.children[1] == '=' and first.type != 'name': if first.type == 'lambdef': # f(lambda: 1=1) message = "lambda cannot contain assignment" else: # f(+x=1) message = "keyword can't be an expression" self.add_issue(first, message=message) @ErrorFinder.register_rule(type='nonlocal_stmt') class _NonlocalModuleLevelRule(SyntaxRule): message = "nonlocal declaration not allowed at module level" def is_issue(self, node): return self._normalizer.context.parent_context is None @ErrorFinder.register_rule(type='arglist') class _ArglistRule(SyntaxRule): message = "Generator expression must be parenthesized if not sole argument" def is_issue(self, node): first_arg = node.children[0] if first_arg.type == 'argument' \ and first_arg.children[1].type == 'comp_for': # e.g. foo(x for x in [], b) return len(node.children) >= 2 else: arg_set = set() kw_only = False kw_unpacking_only = False is_old_starred = False # In python 3 this would be a bit easier (stars are part of # argument), but we have to understand both. for argument in node.children: if argument == ',': continue if argument in ('*', '**'): # Python < 3.5 has the order engraved in the grammar # file. No need to do anything here. is_old_starred = True continue if is_old_starred: is_old_starred = False continue if argument.type == 'argument': first = argument.children[0] if first in ('*', '**'): if first == '*': if kw_unpacking_only: # foo(**kwargs, *args) message = "iterable argument unpacking follows keyword argument unpacking" self.add_issue(argument, message=message) else: kw_unpacking_only = True else: # Is a keyword argument. kw_only = True if first.type == 'name': if first.value in arg_set: # f(x=1, x=2) self.add_issue(first, message="keyword argument repeated") else: arg_set.add(first.value) else: if kw_unpacking_only: # f(**x, y) message = "positional argument follows keyword argument unpacking" self.add_issue(argument, message=message) elif kw_only: # f(x=2, y) message = "positional argument follows keyword argument" self.add_issue(argument, message=message) @ErrorFinder.register_rule(type='parameters') @ErrorFinder.register_rule(type='lambdef') class _ParameterRule(SyntaxRule): # def f(x=3, y): pass message = "non-default argument follows default argument" def is_issue(self, node): param_names = set() default_only = False for p in _iter_params(node): if p.name.value in param_names: message = "duplicate argument '%s' in function definition" self.add_issue(p.name, message=message % p.name.value) param_names.add(p.name.value) if p.default is None and not p.star_count: if default_only: return True else: default_only = True @ErrorFinder.register_rule(type='try_stmt') class _TryStmtRule(SyntaxRule): message = "default 'except:' must be last" def is_issue(self, try_stmt): default_except = None for except_clause in try_stmt.children[3::3]: if except_clause in ('else', 'finally'): break if except_clause == 'except': default_except = except_clause elif default_except is not None: self.add_issue(default_except, message=self.message) @ErrorFinder.register_rule(type='string') class _FStringRule(SyntaxRule): _fstring_grammar = None message_empty = "f-string: empty expression not allowed" # f'{}' message_single_closing = "f-string: single '}' is not allowed" # f'}' message_nested = "f-string: expressions nested too deeply" message_backslash = "f-string expression part cannot include a backslash" # f'{"\"}' or f'{"\\"}' message_comment = "f-string expression part cannot include '#'" # f'{#}' message_unterminated_string = "f-string: unterminated string" # f'{"}' message_conversion = "f-string: invalid conversion character: expected 's', 'r', or 'a'" message_incomplete = "f-string: expecting '}'" # f'{' message_syntax = "invalid syntax" @classmethod def _load_grammar(cls): import parso if cls._fstring_grammar is None: cls._fstring_grammar = parso.load_grammar(language='python-f-string') return cls._fstring_grammar def is_issue(self, fstring): if 'f' not in fstring.string_prefix.lower(): return parsed = self._load_grammar().parse_leaf(fstring) for child in parsed.children: if child.type == 'expression': self._check_expression(child) elif child.type == 'error_node': next_ = child.get_next_leaf() if next_.type == 'error_leaf' and next_.original_type == 'unterminated_string': self.add_issue(next_, message=self.message_unterminated_string) # At this point nothing more is comming except the error # leaf that we've already checked here. break self.add_issue(child, message=self.message_incomplete) elif child.type == 'error_leaf': self.add_issue(child, message=self.message_single_closing) def _check_python_expr(self, python_expr): value = python_expr.value if '\\' in value: self.add_issue(python_expr, message=self.message_backslash) return if '#' in value: self.add_issue(python_expr, message=self.message_comment) return if re.match('\s*$', value) is not None: self.add_issue(python_expr, message=self.message_empty) return # This is now nested parsing. We parsed the fstring and now # we're parsing Python again. try: # CPython has a bit of a special ways to parse Python code within # f-strings. It wraps the code in brackets to make sure that # whitespace doesn't make problems (indentation/newlines). # Just use that algorithm as well here and adapt start positions. start_pos = python_expr.start_pos start_pos = start_pos[0], start_pos[1] - 1 eval_input = self._normalizer.grammar._parse( '(%s)' % value, start_symbol='eval_input', start_pos=start_pos, error_recovery=False ) except ParserSyntaxError as e: self.add_issue(e.error_leaf, message=self.message_syntax) return issues = self._normalizer.grammar.iter_errors(eval_input) self._normalizer.issues += issues def _check_format_spec(self, format_spec): for expression in format_spec.children[1:]: nested_format_spec = expression.children[-2] if nested_format_spec.type == 'format_spec': if len(nested_format_spec.children) > 1: self.add_issue( nested_format_spec.children[1], message=self.message_nested ) self._check_expression(expression) def _check_expression(self, expression): for c in expression.children: if c.type == 'python_expr': self._check_python_expr(c) elif c.type == 'conversion': if c.value not in ('s', 'r', 'a'): self.add_issue(c, message=self.message_conversion) elif c.type == 'format_spec': self._check_format_spec(c) class _CheckAssignmentRule(SyntaxRule): def _check_assignment(self, node, is_deletion=False): error = None type_ = node.type if type_ == 'lambdef': error = 'lambda' elif type_ == 'atom': first, second = node.children[:2] error = _get_comprehension_type(node) if error is None: if second.type in ('dictorsetmaker', 'string'): error = 'literal' elif first in ('(', '['): if second.type == 'yield_expr': error = 'yield expression' elif second.type == 'testlist_comp': # This is not a comprehension, they were handled # further above. for child in second.children[::2]: self._check_assignment(child, is_deletion) else: # Everything handled, must be useless brackets. self._check_assignment(second, is_deletion) elif type_ == 'keyword': error = 'keyword' elif type_ == 'operator': if node.value == '...': error = 'Ellipsis' elif type_ == 'comparison': error = 'comparison' elif type_ in ('string', 'number'): error = 'literal' elif type_ == 'yield_expr': # This one seems to be a slightly different warning in Python. message = 'assignment to yield expression not possible' self.add_issue(node, message=message) elif type_ == 'test': error = 'conditional expression' elif type_ in ('atom_expr', 'power'): if node.children[0] == 'await': error = 'await expression' elif node.children[-2] == '**': error = 'operator' else: # Has a trailer trailer = node.children[-1] assert trailer.type == 'trailer' if trailer.children[0] == '(': error = 'function call' elif type_ in ('testlist_star_expr', 'exprlist', 'testlist'): for child in node.children[::2]: self._check_assignment(child, is_deletion) elif ('expr' in type_ and type_ != 'star_expr' # is a substring or '_test' in type_ or type_ in ('term', 'factor')): error = 'operator' if error is not None: message = "can't %s %s" % ("delete" if is_deletion else "assign to", error) self.add_issue(node, message=message) @ErrorFinder.register_rule(type='comp_for') class _CompForRule(_CheckAssignmentRule): message = "asynchronous comprehension outside of an asynchronous function" |
| You are visitor 01402520 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |