mirror of
https://github.com/davidhalter/parso.git
synced 2025-12-06 12:54:29 +08:00
Compare commits
25 Commits
v0.8.3
...
9ddffca4da
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ddffca4da | ||
|
|
06db036e23 | ||
|
|
c792ae546c | ||
|
|
1c01dafc2b | ||
|
|
1ca6b1f3e8 | ||
|
|
5a9349ae58 | ||
|
|
279fd6903e | ||
|
|
e255b69cb8 | ||
|
|
744f2ac39e | ||
|
|
3c04eef132 | ||
|
|
f7bea28bcc | ||
|
|
27af7ef106 | ||
|
|
171fd33cb6 | ||
|
|
4eba7d697f | ||
|
|
cf240c7d2b | ||
|
|
ffadfca81b | ||
|
|
378e645bbc | ||
|
|
df34112b5b | ||
|
|
e0a1caecc4 | ||
|
|
7d43001f9d | ||
|
|
cf5969d7a1 | ||
|
|
6b6b59f6d7 | ||
|
|
7af5259159 | ||
|
|
8ee84d005e | ||
|
|
0740450899 |
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@@ -22,18 +22,15 @@ jobs:
|
||||
# Ignore F401, which are unused imports. flake8 is a primitive tool and is sometimes wrong.
|
||||
run: flake8 --extend-ignore F401 parso test/*.py setup.py scripts/
|
||||
- name: Run Mypy
|
||||
run: mypy parso
|
||||
run: mypy parso setup.py
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ['3.6', '3.7', '3.8', '3.9']
|
||||
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
|
||||
experimental: [false]
|
||||
# include:
|
||||
# - python-version: '3.10-dev'
|
||||
# experimental: true
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
|
||||
16
.readthedocs.yml
Normal file
16
.readthedocs.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
version: 2
|
||||
|
||||
python:
|
||||
install:
|
||||
- method: pip
|
||||
path: .
|
||||
extra_requirements:
|
||||
- docs
|
||||
|
||||
submodules:
|
||||
include: all
|
||||
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.11"
|
||||
@@ -6,6 +6,11 @@ Changelog
|
||||
Unreleased
|
||||
++++++++++
|
||||
|
||||
0.8.4 (2024-04-05)
|
||||
++++++++++++++++++
|
||||
|
||||
- Add basic support for Python 3.13
|
||||
|
||||
0.8.3 (2021-11-30)
|
||||
++++++++++++++++++
|
||||
|
||||
|
||||
@@ -88,8 +88,7 @@ Acknowledgements
|
||||
|
||||
- Guido van Rossum (@gvanrossum) for creating the parser generator pgen2
|
||||
(originally used in lib2to3).
|
||||
- `Salome Schneider <https://www.crepes-schnaegg.ch/cr%C3%AApes-schn%C3%A4gg/kunst-f%C3%BCrs-cr%C3%AApes-mobil/>`_
|
||||
for the extremely awesome parso logo.
|
||||
- Salome Schneider for the extremely awesome parso logo.
|
||||
|
||||
|
||||
.. _jedi: https://github.com/davidhalter/jedi
|
||||
|
||||
9
SECURITY.md
Normal file
9
SECURITY.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Security Policy
|
||||
|
||||
If security issues arise, we will try to fix those as soon as possible.
|
||||
|
||||
Due to Parso's nature, Security Issues will probably be extremely rare, but we will of course treat them seriously.
|
||||
|
||||
## Reporting Security Problems
|
||||
|
||||
If you need to report a security vulnerability, please send an email to davidhalter88@gmail.com. Typically, I will respond in the next few business days.
|
||||
@@ -43,7 +43,7 @@ from parso.grammar import Grammar, load_grammar
|
||||
from parso.utils import split_lines, python_bytes_to_unicode
|
||||
|
||||
|
||||
__version__ = '0.8.3'
|
||||
__version__ = '0.8.4'
|
||||
|
||||
|
||||
def parse(code=None, **kwargs):
|
||||
|
||||
@@ -106,14 +106,14 @@ class Grammar(Generic[_NodeT]):
|
||||
|
||||
if file_io is None:
|
||||
if code is None:
|
||||
file_io = FileIO(path) # type: ignore
|
||||
file_io = FileIO(path) # type: ignore[arg-type]
|
||||
else:
|
||||
file_io = KnownContentFileIO(path, code)
|
||||
|
||||
if cache and file_io.path is not None:
|
||||
module_node = load_module(self._hashed, file_io, cache_path=cache_path)
|
||||
if module_node is not None:
|
||||
return module_node # type: ignore
|
||||
return module_node # type: ignore[no-any-return]
|
||||
|
||||
if code is None:
|
||||
code = file_io.read()
|
||||
@@ -132,7 +132,7 @@ class Grammar(Generic[_NodeT]):
|
||||
module_node = module_cache_item.node
|
||||
old_lines = module_cache_item.lines
|
||||
if old_lines == lines:
|
||||
return module_node # type: ignore
|
||||
return module_node # type: ignore[no-any-return]
|
||||
|
||||
new_node = self._diff_parser(
|
||||
self._pgen_grammar, self._tokenizer, module_node
|
||||
@@ -144,7 +144,7 @@ class Grammar(Generic[_NodeT]):
|
||||
# Never pickle in pypy, it's slow as hell.
|
||||
pickling=cache and not is_pypy,
|
||||
cache_path=cache_path)
|
||||
return new_node # type: ignore
|
||||
return new_node # type: ignore[no-any-return]
|
||||
|
||||
tokens = self._tokenizer(lines)
|
||||
|
||||
@@ -160,7 +160,7 @@ class Grammar(Generic[_NodeT]):
|
||||
# Never pickle in pypy, it's slow as hell.
|
||||
pickling=cache and not is_pypy,
|
||||
cache_path=cache_path)
|
||||
return root_node # type: ignore
|
||||
return root_node # type: ignore[no-any-return]
|
||||
|
||||
def _get_token_namespace(self):
|
||||
ns = self._token_namespace
|
||||
|
||||
@@ -276,7 +276,7 @@ def generate_grammar(bnf_grammar: str, token_namespace) -> Grammar:
|
||||
dfa_state.transitions[transition] = DFAPlan(next_dfa)
|
||||
|
||||
_calculate_tree_traversal(rule_to_dfas)
|
||||
return Grammar(start_nonterminal, rule_to_dfas, reserved_strings) # type: ignore
|
||||
return Grammar(start_nonterminal, rule_to_dfas, reserved_strings) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def _make_transition(token_namespace, reserved_syntax_strings, label):
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import codecs
|
||||
import sys
|
||||
import warnings
|
||||
import re
|
||||
from contextlib import contextmanager
|
||||
@@ -33,7 +34,10 @@ def _get_rhs_name(node, version):
|
||||
return "literal"
|
||||
else:
|
||||
if second.children[1] == ":" or second.children[0] == "**":
|
||||
if version < (3, 10):
|
||||
return "dict display"
|
||||
else:
|
||||
return "dict literal"
|
||||
else:
|
||||
return "set display"
|
||||
elif (
|
||||
@@ -47,7 +51,10 @@ def _get_rhs_name(node, version):
|
||||
elif first == "[":
|
||||
return "list"
|
||||
elif first == "{" and second == "}":
|
||||
if version < (3, 10):
|
||||
return "dict display"
|
||||
else:
|
||||
return "dict literal"
|
||||
elif first == "{" and len(node.children) > 2:
|
||||
return "set display"
|
||||
elif type_ == "keyword":
|
||||
@@ -58,7 +65,10 @@ def _get_rhs_name(node, version):
|
||||
else:
|
||||
return str(node.value)
|
||||
elif type_ == "operator" and node.value == "...":
|
||||
if version < (3, 10):
|
||||
return "Ellipsis"
|
||||
else:
|
||||
return "ellipsis"
|
||||
elif type_ == "comparison":
|
||||
return "comparison"
|
||||
elif type_ in ("string", "number", "strings"):
|
||||
@@ -83,7 +93,10 @@ def _get_rhs_name(node, version):
|
||||
or "_test" in type_
|
||||
or type_ in ("term", "factor")
|
||||
):
|
||||
if version < (3, 10):
|
||||
return "operator"
|
||||
else:
|
||||
return "expression"
|
||||
elif type_ == "star_expr":
|
||||
return "starred"
|
||||
elif type_ == "testlist_star_expr":
|
||||
@@ -610,7 +623,10 @@ class _NameChecks(SyntaxRule):
|
||||
|
||||
@ErrorFinder.register_rule(type='string')
|
||||
class _StringChecks(SyntaxRule):
|
||||
if sys.version_info < (3, 10):
|
||||
message = "bytes can only contain ASCII literal characters."
|
||||
else:
|
||||
message = "bytes can only contain ASCII literal characters"
|
||||
|
||||
def is_issue(self, leaf):
|
||||
string_prefix = leaf.string_prefix.lower()
|
||||
@@ -1043,14 +1059,20 @@ class _CheckAssignmentRule(SyntaxRule):
|
||||
error = 'literal'
|
||||
else:
|
||||
if second.children[1] == ':':
|
||||
if self._normalizer.version < (3, 10):
|
||||
error = 'dict display'
|
||||
else:
|
||||
error = 'dict literal'
|
||||
else:
|
||||
error = 'set display'
|
||||
elif first == "{" and second == "}":
|
||||
if self._normalizer.version < (3, 8):
|
||||
error = 'literal'
|
||||
else:
|
||||
if self._normalizer.version < (3, 10):
|
||||
error = "dict display"
|
||||
else:
|
||||
error = "dict literal"
|
||||
elif first == "{" and len(node.children) > 2:
|
||||
if self._normalizer.version < (3, 8):
|
||||
error = 'literal'
|
||||
@@ -1083,7 +1105,10 @@ class _CheckAssignmentRule(SyntaxRule):
|
||||
error = str(node.value)
|
||||
elif type_ == 'operator':
|
||||
if node.value == '...':
|
||||
if self._normalizer.version < (3, 10):
|
||||
error = 'Ellipsis'
|
||||
else:
|
||||
error = 'ellipsis'
|
||||
elif type_ == 'comparison':
|
||||
error = 'comparison'
|
||||
elif type_ in ('string', 'number', 'strings'):
|
||||
@@ -1098,7 +1123,10 @@ class _CheckAssignmentRule(SyntaxRule):
|
||||
if node.children[0] == 'await':
|
||||
error = 'await expression'
|
||||
elif node.children[-2] == '**':
|
||||
if self._normalizer.version < (3, 10):
|
||||
error = 'operator'
|
||||
else:
|
||||
error = 'expression'
|
||||
else:
|
||||
# Has a trailer
|
||||
trailer = node.children[-1]
|
||||
@@ -1120,7 +1148,10 @@ class _CheckAssignmentRule(SyntaxRule):
|
||||
elif ('expr' in type_ and type_ != 'star_expr' # is a substring
|
||||
or '_test' in type_
|
||||
or type_ in ('term', 'factor')):
|
||||
if self._normalizer.version < (3, 10):
|
||||
error = 'operator'
|
||||
else:
|
||||
error = 'expression'
|
||||
elif type_ == "star_expr":
|
||||
if is_deletion:
|
||||
if self._normalizer.version >= (3, 9):
|
||||
|
||||
169
parso/python/grammar313.txt
Normal file
169
parso/python/grammar313.txt
Normal file
@@ -0,0 +1,169 @@
|
||||
# Grammar for Python
|
||||
|
||||
# NOTE WELL: You should also follow all the steps listed at
|
||||
# https://devguide.python.org/grammar/
|
||||
|
||||
# Start symbols for the grammar:
|
||||
# single_input is a single interactive statement;
|
||||
# file_input is a module or sequence of commands read from an input file;
|
||||
# eval_input is the input for the eval() functions.
|
||||
# NB: compound_stmt in single_input is followed by extra NEWLINE!
|
||||
single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
|
||||
file_input: stmt* ENDMARKER
|
||||
eval_input: testlist NEWLINE* ENDMARKER
|
||||
|
||||
decorator: '@' namedexpr_test NEWLINE
|
||||
decorators: decorator+
|
||||
decorated: decorators (classdef | funcdef | async_funcdef)
|
||||
|
||||
async_funcdef: 'async' funcdef
|
||||
funcdef: 'def' NAME parameters ['->' test] ':' suite
|
||||
|
||||
parameters: '(' [typedargslist] ')'
|
||||
typedargslist: (
|
||||
(tfpdef ['=' test] (',' tfpdef ['=' test])* ',' '/' [',' [ tfpdef ['=' test] (
|
||||
',' tfpdef ['=' test])* ([',' [
|
||||
'*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
|
||||
| '**' tfpdef [',']]])
|
||||
| '*' [tfpdef] (',' tfpdef ['=' test])* ([',' ['**' tfpdef [',']]])
|
||||
| '**' tfpdef [',']]] )
|
||||
| (tfpdef ['=' test] (',' tfpdef ['=' test])* [',' [
|
||||
'*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
|
||||
| '**' tfpdef [',']]]
|
||||
| '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
|
||||
| '**' tfpdef [','])
|
||||
)
|
||||
tfpdef: NAME [':' test]
|
||||
varargslist: vfpdef ['=' test ](',' vfpdef ['=' test])* ',' '/' [',' [ (vfpdef ['=' test] (',' vfpdef ['=' test])* [',' [
|
||||
'*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
|
||||
| '**' vfpdef [',']]]
|
||||
| '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
|
||||
| '**' vfpdef [',']) ]] | (vfpdef ['=' test] (',' vfpdef ['=' test])* [',' [
|
||||
'*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
|
||||
| '**' vfpdef [',']]]
|
||||
| '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
|
||||
| '**' vfpdef [',']
|
||||
)
|
||||
vfpdef: NAME
|
||||
|
||||
stmt: simple_stmt | compound_stmt | NEWLINE
|
||||
simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
|
||||
small_stmt: (expr_stmt | del_stmt | pass_stmt | flow_stmt |
|
||||
import_stmt | global_stmt | nonlocal_stmt | assert_stmt)
|
||||
expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
|
||||
('=' (yield_expr|testlist_star_expr))*)
|
||||
annassign: ':' test ['=' (yield_expr|testlist_star_expr)]
|
||||
testlist_star_expr: (test|star_expr) (',' (test|star_expr))* [',']
|
||||
augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' |
|
||||
'<<=' | '>>=' | '**=' | '//=')
|
||||
# For normal and annotated assignments, additional restrictions enforced by the interpreter
|
||||
del_stmt: 'del' exprlist
|
||||
pass_stmt: 'pass'
|
||||
flow_stmt: break_stmt | continue_stmt | return_stmt | raise_stmt | yield_stmt
|
||||
break_stmt: 'break'
|
||||
continue_stmt: 'continue'
|
||||
return_stmt: 'return' [testlist_star_expr]
|
||||
yield_stmt: yield_expr
|
||||
raise_stmt: 'raise' [test ['from' test]]
|
||||
import_stmt: import_name | import_from
|
||||
import_name: 'import' dotted_as_names
|
||||
# note below: the ('.' | '...') is necessary because '...' is tokenized as ELLIPSIS
|
||||
import_from: ('from' (('.' | '...')* dotted_name | ('.' | '...')+)
|
||||
'import' ('*' | '(' import_as_names ')' | import_as_names))
|
||||
import_as_name: NAME ['as' NAME]
|
||||
dotted_as_name: dotted_name ['as' NAME]
|
||||
import_as_names: import_as_name (',' import_as_name)* [',']
|
||||
dotted_as_names: dotted_as_name (',' dotted_as_name)*
|
||||
dotted_name: NAME ('.' NAME)*
|
||||
global_stmt: 'global' NAME (',' NAME)*
|
||||
nonlocal_stmt: 'nonlocal' NAME (',' NAME)*
|
||||
assert_stmt: 'assert' test [',' test]
|
||||
|
||||
compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated | async_stmt
|
||||
async_stmt: 'async' (funcdef | with_stmt | for_stmt)
|
||||
if_stmt: 'if' namedexpr_test ':' suite ('elif' namedexpr_test ':' suite)* ['else' ':' suite]
|
||||
while_stmt: 'while' namedexpr_test ':' suite ['else' ':' suite]
|
||||
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
|
||||
try_stmt: ('try' ':' suite
|
||||
((except_clause ':' suite)+
|
||||
['else' ':' suite]
|
||||
['finally' ':' suite] |
|
||||
'finally' ':' suite))
|
||||
with_stmt: 'with' with_item (',' with_item)* ':' suite
|
||||
with_item: test ['as' expr]
|
||||
# NB compile.c makes sure that the default except clause is last
|
||||
except_clause: 'except' [test ['as' NAME]]
|
||||
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
|
||||
|
||||
namedexpr_test: test [':=' test]
|
||||
test: or_test ['if' or_test 'else' test] | lambdef
|
||||
lambdef: 'lambda' [varargslist] ':' test
|
||||
or_test: and_test ('or' and_test)*
|
||||
and_test: not_test ('and' not_test)*
|
||||
not_test: 'not' not_test | comparison
|
||||
comparison: expr (comp_op expr)*
|
||||
# <> isn't actually a valid comparison operator in Python. It's here for the
|
||||
# sake of a __future__ import described in PEP 401 (which really works :-)
|
||||
comp_op: '<'|'>'|'=='|'>='|'<='|'<>'|'!='|'in'|'not' 'in'|'is'|'is' 'not'
|
||||
star_expr: '*' expr
|
||||
expr: xor_expr ('|' xor_expr)*
|
||||
xor_expr: and_expr ('^' and_expr)*
|
||||
and_expr: shift_expr ('&' shift_expr)*
|
||||
shift_expr: arith_expr (('<<'|'>>') arith_expr)*
|
||||
arith_expr: term (('+'|'-') term)*
|
||||
term: factor (('*'|'@'|'/'|'%'|'//') factor)*
|
||||
factor: ('+'|'-'|'~') factor | power
|
||||
power: atom_expr ['**' factor]
|
||||
atom_expr: ['await'] atom trailer*
|
||||
atom: ('(' [yield_expr|testlist_comp] ')' |
|
||||
'[' [testlist_comp] ']' |
|
||||
'{' [dictorsetmaker] '}' |
|
||||
NAME | NUMBER | strings | '...' | 'None' | 'True' | 'False')
|
||||
testlist_comp: (namedexpr_test|star_expr) ( comp_for | (',' (namedexpr_test|star_expr))* [','] )
|
||||
trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
|
||||
subscriptlist: subscript (',' subscript)* [',']
|
||||
subscript: test [':=' test] | [test] ':' [test] [sliceop]
|
||||
sliceop: ':' [test]
|
||||
exprlist: (expr|star_expr) (',' (expr|star_expr))* [',']
|
||||
testlist: test (',' test)* [',']
|
||||
dictorsetmaker: ( ((test ':' test | '**' expr)
|
||||
(comp_for | (',' (test ':' test | '**' expr))* [','])) |
|
||||
((test [':=' test] | star_expr)
|
||||
(comp_for | (',' (test [':=' test] | star_expr))* [','])) )
|
||||
|
||||
classdef: 'class' NAME ['(' [arglist] ')'] ':' suite
|
||||
|
||||
arglist: argument (',' argument)* [',']
|
||||
|
||||
# The reason that keywords are test nodes instead of NAME is that using NAME
|
||||
# results in an ambiguity. ast.c makes sure it's a NAME.
|
||||
# "test '=' test" is really "keyword '=' test", but we have no such token.
|
||||
# These need to be in a single rule to avoid grammar that is ambiguous
|
||||
# to our LL(1) parser. Even though 'test' includes '*expr' in star_expr,
|
||||
# we explicitly match '*' here, too, to give it proper precedence.
|
||||
# Illegal combinations and orderings are blocked in ast.c:
|
||||
# multiple (test comp_for) arguments are blocked; keyword unpackings
|
||||
# that precede iterable unpackings are blocked; etc.
|
||||
argument: ( test [comp_for] |
|
||||
test ':=' test |
|
||||
test '=' test |
|
||||
'**' test |
|
||||
'*' test )
|
||||
|
||||
comp_iter: comp_for | comp_if
|
||||
sync_comp_for: 'for' exprlist 'in' or_test [comp_iter]
|
||||
comp_for: ['async'] sync_comp_for
|
||||
comp_if: 'if' or_test [comp_iter]
|
||||
|
||||
# not used in grammar, but may appear in "node" passed from Parser to Compiler
|
||||
encoding_decl: NAME
|
||||
|
||||
yield_expr: 'yield' [yield_arg]
|
||||
yield_arg: 'from' test | testlist_star_expr
|
||||
|
||||
strings: (STRING | fstring)+
|
||||
fstring: FSTRING_START fstring_content* FSTRING_END
|
||||
fstring_content: FSTRING_STRING | fstring_expr
|
||||
fstring_conversion: '!' NAME
|
||||
fstring_expr: '{' (testlist_comp | yield_expr) ['='] [ fstring_conversion ] [ fstring_format_spec ] '}'
|
||||
fstring_format_spec: ':' fstring_content*
|
||||
@@ -295,6 +295,8 @@ class FStringEnd(PythonLeaf):
|
||||
|
||||
|
||||
class _StringComparisonMixin:
|
||||
__slots__ = ()
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Make comparisons with strings easy.
|
||||
@@ -544,6 +546,7 @@ class Function(ClassOrFunc):
|
||||
4. annotation (if present)
|
||||
"""
|
||||
type = 'funcdef'
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self, children):
|
||||
super().__init__(children)
|
||||
|
||||
@@ -13,6 +13,9 @@ ignore =
|
||||
|
||||
|
||||
[mypy]
|
||||
show_error_codes = true
|
||||
enable_error_code = ignore-without-code
|
||||
|
||||
disallow_subclassing_any = True
|
||||
|
||||
# Avoid creating future gotchas emerging from bad typing
|
||||
@@ -23,3 +26,4 @@ warn_unused_configs = True
|
||||
warn_unreachable = True
|
||||
|
||||
strict_equality = True
|
||||
no_implicit_optional = False
|
||||
|
||||
10
setup.py
10
setup.py
@@ -47,12 +47,16 @@ setup(
|
||||
],
|
||||
extras_require={
|
||||
'testing': [
|
||||
'pytest<6.0.0',
|
||||
'pytest',
|
||||
'docopt',
|
||||
],
|
||||
'qa': [
|
||||
'flake8==3.8.3',
|
||||
'mypy==0.782',
|
||||
# Latest version which supports Python 3.6
|
||||
'flake8==5.0.4',
|
||||
# Latest version which supports Python 3.6
|
||||
'mypy==0.971',
|
||||
# Arbitrary pins, latest at the time of pinning
|
||||
'types-setuptools==67.2.0.1',
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
@@ -29,7 +29,6 @@ FAILING_EXAMPLES = [
|
||||
'from foo import a,',
|
||||
'from __future__ import whatever',
|
||||
'from __future__ import braces',
|
||||
'from .__future__ import whatever',
|
||||
'def f(x=3, y): pass',
|
||||
'lambda x=3, y: x',
|
||||
'__debug__ = 1',
|
||||
@@ -216,7 +215,6 @@ FAILING_EXAMPLES = [
|
||||
'f"{\'\\\'}"',
|
||||
'f"{#}"',
|
||||
"f'{1!b}'",
|
||||
"f'{1:{5:{3}}}'",
|
||||
"f'{'",
|
||||
"f'{'",
|
||||
"f'}'",
|
||||
@@ -227,8 +225,6 @@ FAILING_EXAMPLES = [
|
||||
"f'{1;1}'",
|
||||
"f'{a;}'",
|
||||
"f'{b\"\" \"\"}'",
|
||||
# f-string expression part cannot include a backslash
|
||||
r'''f"{'\n'}"''',
|
||||
|
||||
'async def foo():\n yield x\n return 1',
|
||||
'async def foo():\n yield x\n return 1',
|
||||
@@ -413,3 +409,17 @@ if sys.version_info[:2] >= (3, 8):
|
||||
FAILING_EXAMPLES += [
|
||||
"f'{1=!b}'",
|
||||
]
|
||||
|
||||
if sys.version_info[:2] < (3, 12):
|
||||
FAILING_EXAMPLES += [
|
||||
# f-string expression part cannot include a backslash before 3.12
|
||||
r'''f"{'\n'}"''',
|
||||
# this compiles successfully but fails when evaluated in 3.12
|
||||
"f'{1:{5:{3}}}'",
|
||||
]
|
||||
|
||||
if sys.version_info[:2] < (3, 13):
|
||||
# this compiles successfully but fails when evaluated in 3.13
|
||||
FAILING_EXAMPLES += [
|
||||
'from .__future__ import whatever',
|
||||
]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Testing if parso finds syntax errors and indentation errors.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
@@ -117,25 +118,89 @@ def _get_actual_exception(code):
|
||||
assert False, "The piece of code should raise an exception."
|
||||
|
||||
# SyntaxError
|
||||
if wanted == 'SyntaxError: assignment to keyword':
|
||||
# Some errors have changed error message in later versions of Python,
|
||||
# and we give a translation table here. We deal with special cases
|
||||
# below.
|
||||
translations = {
|
||||
'SyntaxError: f-string: unterminated string':
|
||||
'SyntaxError: EOL while scanning string literal',
|
||||
"SyntaxError: f-string: expecting '}'":
|
||||
'SyntaxError: EOL while scanning string literal',
|
||||
'SyntaxError: f-string: empty expression not allowed':
|
||||
'SyntaxError: invalid syntax',
|
||||
"SyntaxError: f-string expression part cannot include '#'":
|
||||
'SyntaxError: invalid syntax',
|
||||
"SyntaxError: f-string: single '}' is not allowed":
|
||||
'SyntaxError: invalid syntax',
|
||||
'SyntaxError: cannot use starred expression here':
|
||||
"SyntaxError: can't use starred expression here",
|
||||
'SyntaxError: f-string: cannot use starred expression here':
|
||||
"SyntaxError: f-string: can't use starred expression here",
|
||||
'SyntaxError: unterminated string literal':
|
||||
'SyntaxError: EOL while scanning string literal',
|
||||
'SyntaxError: parameter without a default follows parameter with a default':
|
||||
'SyntaxError: non-default argument follows default argument',
|
||||
"SyntaxError: 'yield from' outside function":
|
||||
"SyntaxError: 'yield' outside function",
|
||||
"SyntaxError: f-string: valid expression required before '}'":
|
||||
'SyntaxError: invalid syntax',
|
||||
"SyntaxError: '{' was never closed":
|
||||
'SyntaxError: invalid syntax',
|
||||
"SyntaxError: f-string: invalid conversion character 'b': expected 's', 'r', or 'a'":
|
||||
"SyntaxError: f-string: invalid conversion character: expected 's', 'r', or 'a'",
|
||||
"SyntaxError: (value error) Invalid format specifier ' 5' for object of type 'int'":
|
||||
'SyntaxError: f-string: expressions nested too deeply',
|
||||
"SyntaxError: f-string: expecting a valid expression after '{'":
|
||||
'SyntaxError: f-string: invalid syntax',
|
||||
"SyntaxError: f-string: expecting '=', or '!', or ':', or '}'":
|
||||
'SyntaxError: f-string: invalid syntax',
|
||||
"SyntaxError: f-string: expecting '=', or '!', or ':', or '}'":
|
||||
'SyntaxError: f-string: invalid syntax',
|
||||
}
|
||||
|
||||
if wanted in translations:
|
||||
wanted = translations[wanted]
|
||||
elif wanted == 'SyntaxError: assignment to keyword':
|
||||
return [wanted, "SyntaxError: can't assign to keyword",
|
||||
'SyntaxError: cannot assign to __debug__'], line_nr
|
||||
elif wanted == 'SyntaxError: f-string: unterminated string':
|
||||
wanted = 'SyntaxError: EOL while scanning string literal'
|
||||
elif wanted == 'SyntaxError: f-string expression part cannot include a backslash':
|
||||
return [
|
||||
wanted,
|
||||
"SyntaxError: EOL while scanning string literal",
|
||||
"SyntaxError: unexpected character after line continuation character",
|
||||
], line_nr
|
||||
elif wanted == "SyntaxError: f-string: expecting '}'":
|
||||
wanted = 'SyntaxError: EOL while scanning string literal'
|
||||
elif wanted == 'SyntaxError: f-string: empty expression not allowed':
|
||||
wanted = 'SyntaxError: invalid syntax'
|
||||
elif wanted == "SyntaxError: f-string expression part cannot include '#'":
|
||||
wanted = 'SyntaxError: invalid syntax'
|
||||
elif wanted == "SyntaxError: f-string: single '}' is not allowed":
|
||||
wanted = 'SyntaxError: invalid syntax'
|
||||
elif "Maybe you meant '==' instead of '='?" in wanted:
|
||||
wanted = wanted.removesuffix(" here. Maybe you meant '==' instead of '='?")
|
||||
elif re.match(
|
||||
r"SyntaxError: unterminated string literal \(detected at line \d+\)", wanted
|
||||
):
|
||||
wanted = "SyntaxError: EOL while scanning string literal"
|
||||
elif re.match(
|
||||
r"SyntaxError: unterminated triple-quoted string literal \(detected at line \d+\)",
|
||||
wanted,
|
||||
):
|
||||
wanted = 'SyntaxError: EOF while scanning triple-quoted string literal'
|
||||
elif re.match(
|
||||
r"IndentationError: expected an indented block after '[^']*' statement on line \d",
|
||||
wanted,
|
||||
):
|
||||
wanted = 'IndentationError: expected an indented block'
|
||||
# The following two errors are produced for both some f-strings and
|
||||
# some non-f-strings in Python 3.13:
|
||||
elif wanted == "SyntaxError: can't use starred expression here":
|
||||
wanted = [
|
||||
"SyntaxError: can't use starred expression here",
|
||||
"SyntaxError: f-string: can't use starred expression here"
|
||||
]
|
||||
elif wanted == 'SyntaxError: cannot mix bytes and nonbytes literals':
|
||||
wanted = [
|
||||
'SyntaxError: cannot mix bytes and nonbytes literals',
|
||||
'SyntaxError: f-string: cannot mix bytes and nonbytes literals'
|
||||
]
|
||||
|
||||
if isinstance(wanted, list):
|
||||
return wanted, line_nr
|
||||
else:
|
||||
return [wanted], line_nr
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user