From 53c23e62df9b182edf9e7ccf726acdcf8c25846f Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Mon, 20 Mar 2023 04:22:06 +0530 Subject: [PATCH] Support files with type comment syntax errors (#3594) --- CHANGES.md | 2 ++ src/black/parsing.py | 26 ++++++++++++++----- .../type_comment_syntax_error.py | 11 ++++++++ tests/test_format.py | 7 +++++ 4 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 tests/data/type_comments/type_comment_syntax_error.py diff --git a/CHANGES.md b/CHANGES.md index 029d0bc..f5c039f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -38,6 +38,8 @@ +- Added support for formatting files with invalid type comments (#3594) + ### Performance diff --git a/src/black/parsing.py b/src/black/parsing.py index ba474c5..eaa3c36 100644 --- a/src/black/parsing.py +++ b/src/black/parsing.py @@ -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 = "" # 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]: @@ -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) diff --git a/tests/data/type_comments/type_comment_syntax_error.py b/tests/data/type_comments/type_comment_syntax_error.py new file mode 100644 index 0000000..2e5ca2e --- /dev/null +++ b/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 diff --git a/tests/test_format.py b/tests/test_format.py index 3a6cbc9..5a7b3bb 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -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) -- 2.39.5