Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support files with type comment syntax errors #3594

Merged
merged 11 commits into from Mar 19, 2023
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -29,6 +29,8 @@

<!-- Changes to the parser or to version autodetection -->

- Added support for formatting files with invalid type comments (#3594)

### Performance

<!-- Changes that improve Black's performance. -->
Expand Down
25 changes: 18 additions & 7 deletions src/black/parsing.py
Expand Up @@ -148,24 +148,29 @@ def lib2to3_unparse(node: Node) -> str:


def parse_single_version(
src: str, version: Tuple[int, int]
src: str, version: Tuple[int, int], type_comments: bool
tusharsadhwani marked this conversation as resolved.
Show resolved Hide resolved
) -> Union[ast.AST, ast3.AST]:
filename = "<unknown>"
# typed-ast is needed because of feature version limitations in the builtin ast 3.8>
if sys.version_info >= (3, 8) and version >= (3,):
return ast.parse(src, filename, feature_version=version, type_comments=True)
return ast.parse(
src, filename, feature_version=version, type_comments=type_comments
)

if _IS_PYPY:
# PyPy 3.7 doesn't support type comment tracking which is not ideal, but there's
# not much we can do as typed-ast won't work either.
if sys.version_info >= (3, 8):
return ast3.parse(src, filename, type_comments=True)
return ast3.parse(src, filename, type_comments=type_comments)
else:
return ast3.parse(src, filename)
else:
# Typed-ast is guaranteed to be used here and automatically tracks type
# comments separately.
return ast3.parse(src, filename, feature_version=version[1])
if type_comments:
# Typed-ast is guaranteed to be used here and automatically tracks type
# comments separately.
return ast3.parse(src, filename, feature_version=version[1])
else:
return ast.parse(src, filename)


def parse_ast(src: str) -> Union[ast.AST, ast3.AST]:
Expand All @@ -175,11 +180,17 @@ def parse_ast(src: str) -> Union[ast.AST, ast3.AST]:
first_error = ""
for version in sorted(versions, reverse=True):
try:
return parse_single_version(src, version)
return parse_single_version(src, version, type_comments=True)
except SyntaxError as e:
if not first_error:
first_error = str(e)

# Try to parse without type comments
tusharsadhwani marked this conversation as resolved.
Show resolved Hide resolved
try:
return parse_single_version(src, version, type_comments=False)
except SyntaxError:
pass

raise SyntaxError(first_error)


Expand Down
11 changes: 11 additions & 0 deletions tests/data/type_comments/type_comment_syntax_error.py
@@ -0,0 +1,11 @@
def foo(
# type: Foo
x): pass

# output

def foo(
# type: Foo
x,
):
pass
8 changes: 8 additions & 0 deletions tests/test_black.py
Expand Up @@ -1938,6 +1938,14 @@ def test_equivalency_ast_parse_failure_includes_error(self) -> None:
err.match("invalid character")
err.match(r"\(<unknown>, line 1\)")

def test_type_comment_syntax_error(self) -> None:
tusharsadhwani marked this conversation as resolved.
Show resolved Hide resolved
"""
Test that black is able to format python code with type comment syntax errors.
"""
source, expected = read_data("type_comments", "type_comment_syntax_error")
assert_format(source, expected)
black.assert_equivalent(source, expected)


class TestCaching:
def test_get_cache_dir(
Expand Down