Skip to content

Commit

Permalink
Support files with type comment syntax errors (#3594)
Browse files Browse the repository at this point in the history
  • Loading branch information
tusharsadhwani committed Mar 19, 2023
1 parent dba3c26 commit 53c23e6
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 7 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -38,6 +38,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
26 changes: 19 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
) -> 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,18 @@ 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
for version in sorted(versions, reverse=True):
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
7 changes: 7 additions & 0 deletions tests/test_format.py
Expand Up @@ -196,3 +196,10 @@ def test_power_op_newline() -> None:
# requires line_length=0
source, expected = read_data("miscellaneous", "power_op_newline")
assert_format(source, expected, mode=black.Mode(line_length=0))


def test_type_comment_syntax_error() -> None:
"""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)

0 comments on commit 53c23e6

Please sign in to comment.