Improve AST safety parsing error message (#2304)
Co-authored-by: Hasan Ramezani <hasan.r67@gmail.com>
This commit is contained in:
parent
2946d3b03d
commit
91773b8909
@ -20,6 +20,7 @@
|
||||
- Fixed option usage when using the `--code` flag (#2259)
|
||||
- Do not call `uvloop.install()` when _Black_ is used as a library (#2303)
|
||||
- Added `--required-version` option to require a specific version to be running (#2300)
|
||||
- Provide a more useful error when parsing fails during AST safety checks (#2304)
|
||||
- Fix incorrect custom breakpoint indices when string group contains fake f-strings
|
||||
(#2311)
|
||||
- Fix regression where `R` prefixes would be lowercased for docstrings (#2285)
|
||||
|
@ -3,7 +3,7 @@
|
||||
"""
|
||||
import ast
|
||||
import sys
|
||||
from typing import Iterable, Iterator, List, Set, Union
|
||||
from typing import Iterable, Iterator, List, Set, Union, Tuple
|
||||
|
||||
# lib2to3 fork
|
||||
from blib2to3.pytree import Node, Leaf
|
||||
@ -106,28 +106,36 @@ def lib2to3_unparse(node: Node) -> str:
|
||||
return code
|
||||
|
||||
|
||||
def parse_ast(src: str) -> Union[ast.AST, ast3.AST, ast27.AST]:
|
||||
def parse_single_version(
|
||||
src: str, version: Tuple[int, int]
|
||||
) -> Union[ast.AST, ast3.AST, ast27.AST]:
|
||||
filename = "<unknown>"
|
||||
if sys.version_info >= (3, 8):
|
||||
# TODO: support Python 4+ ;)
|
||||
for minor_version in range(sys.version_info[1], 4, -1):
|
||||
try:
|
||||
return ast.parse(src, filename, feature_version=(3, minor_version))
|
||||
except SyntaxError:
|
||||
continue
|
||||
else:
|
||||
for feature_version in (7, 6):
|
||||
try:
|
||||
return ast3.parse(src, filename, feature_version=feature_version)
|
||||
except SyntaxError:
|
||||
continue
|
||||
if ast27.__name__ == "ast":
|
||||
raise SyntaxError(
|
||||
"The requested source code has invalid Python 3 syntax.\n"
|
||||
"If you are trying to format Python 2 files please reinstall Black"
|
||||
" with the 'python2' extra: `python3 -m pip install black[python2]`."
|
||||
)
|
||||
return ast27.parse(src)
|
||||
# typed_ast is needed because of feature version limitations in the builtin ast
|
||||
if sys.version_info >= (3, 8) and version >= (3,):
|
||||
return ast.parse(src, filename, feature_version=version)
|
||||
elif version >= (3,):
|
||||
return ast3.parse(src, filename, feature_version=version[1])
|
||||
elif version == (2, 7):
|
||||
return ast27.parse(src)
|
||||
raise AssertionError("INTERNAL ERROR: Tried parsing unsupported Python version!")
|
||||
|
||||
|
||||
def parse_ast(src: str) -> Union[ast.AST, ast3.AST, ast27.AST]:
|
||||
# TODO: support Python 4+ ;)
|
||||
versions = [(3, minor) for minor in range(3, sys.version_info[1] + 1)]
|
||||
|
||||
if ast27.__name__ != "ast":
|
||||
versions.append((2, 7))
|
||||
|
||||
first_error = ""
|
||||
for version in sorted(versions, reverse=True):
|
||||
try:
|
||||
return parse_single_version(src, version)
|
||||
except SyntaxError as e:
|
||||
if not first_error:
|
||||
first_error = str(e)
|
||||
|
||||
raise SyntaxError(first_error)
|
||||
|
||||
|
||||
def stringify_ast(
|
||||
|
@ -451,38 +451,6 @@ def test_skip_magic_trailing_comma(self) -> None:
|
||||
)
|
||||
self.assertEqual(expected, actual, msg)
|
||||
|
||||
@pytest.mark.no_python2
|
||||
def test_python2_should_fail_without_optional_install(self) -> None:
|
||||
if sys.version_info < (3, 8):
|
||||
self.skipTest(
|
||||
"Python 3.6 and 3.7 will install typed-ast to work and as such will be"
|
||||
" able to parse Python 2 syntax without explicitly specifying the"
|
||||
" python2 extra"
|
||||
)
|
||||
|
||||
source = "x = 1234l"
|
||||
tmp_file = Path(black.dump_to_file(source))
|
||||
try:
|
||||
runner = BlackRunner()
|
||||
result = runner.invoke(black.main, [str(tmp_file)])
|
||||
self.assertEqual(result.exit_code, 123)
|
||||
finally:
|
||||
os.unlink(tmp_file)
|
||||
assert result.stderr_bytes is not None
|
||||
actual = (
|
||||
result.stderr_bytes.decode()
|
||||
.replace("\n", "")
|
||||
.replace("\\n", "")
|
||||
.replace("\\r", "")
|
||||
.replace("\r", "")
|
||||
)
|
||||
msg = (
|
||||
"The requested source code has invalid Python 3 syntax."
|
||||
"If you are trying to format Python 2 files please reinstall Black"
|
||||
" with the 'python2' extra: `python3 -m pip install black[python2]`."
|
||||
)
|
||||
self.assertIn(msg, actual)
|
||||
|
||||
@pytest.mark.python2
|
||||
@patch("black.dump_to_file", dump_to_stderr)
|
||||
def test_python2_print_function(self) -> None:
|
||||
|
Loading…
Reference in New Issue
Block a user