From 20ffd47bb27d027d8d4d6d7ce55ece5e10ba706f Mon Sep 17 00:00:00 2001 From: Kelvin Li <13176405+rdrll@users.noreply.github.com> Date: Wed, 21 Jun 2023 01:18:38 -0700 Subject: [PATCH 1/5] bug fixing --- src/black/linegen.py | 5 ++-- .../miscellaneous/consecutive_ignore.diff | 29 +++++++++++++++++++ .../data/miscellaneous/consecutive_ignore.py | 21 ++++++++++++++ tests/test_black.py | 21 ++++++++++++++ 4 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 tests/data/miscellaneous/consecutive_ignore.diff create mode 100644 tests/data/miscellaneous/consecutive_ignore.py diff --git a/src/black/linegen.py b/src/black/linegen.py index ad21307c311..433615e63c6 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -1399,8 +1399,9 @@ def maybe_make_parens_invisible_in_atom( if is_lpar_token(first) and is_rpar_token(last): middle = node.children[1] # make parentheses invisible - first.value = "" - last.value = "" + if not node.children[1].prefix.strip().replace(" ", "").startswith("#type:ignore"): + first.value = "" + last.value = "" maybe_make_parens_invisible_in_atom( middle, parent=parent, diff --git a/tests/data/miscellaneous/consecutive_ignore.diff b/tests/data/miscellaneous/consecutive_ignore.diff new file mode 100644 index 00000000000..27b5ff4feb8 --- /dev/null +++ b/tests/data/miscellaneous/consecutive_ignore.diff @@ -0,0 +1,29 @@ +--- [Deterministic header] ++++ [Deterministic header] +@@ -1,21 +1,15 @@ + # This is a regression test. Issue #3737 + + a = ( # type: ignore + int( # type: ignore + int( # type: ignore +- int( # type: ignore +- 6 +- ) ++ int(6) # type: ignore + ) + ) + ) + +-b = ( +- int( +- 6 +- ) +-) ++b = int(6) + +-print( "111") # type:ignore +-print( "111" ) # type:ignore +-print( "111" ) # type:ignore ++print("111") # type:ignore ++print("111") # type:ignore ++print("111") # type:ignore \ No newline at end of file diff --git a/tests/data/miscellaneous/consecutive_ignore.py b/tests/data/miscellaneous/consecutive_ignore.py new file mode 100644 index 00000000000..4bb3d7adec9 --- /dev/null +++ b/tests/data/miscellaneous/consecutive_ignore.py @@ -0,0 +1,21 @@ +# This is a regression test. Issue #3737 + +a = ( # type: ignore + int( # type: ignore + int( # type: ignore + int( # type: ignore + 6 + ) + ) + ) +) + +b = ( + int( + 6 + ) +) + +print( "111") # type:ignore +print( "111" ) # type:ignore +print( "111" ) # type:ignore \ No newline at end of file diff --git a/tests/test_black.py b/tests/test_black.py index 5f2e6f5b14c..85a9a83366d 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -280,6 +280,27 @@ def test_pep_695_version_detection(self) -> None: versions = black.detect_target_versions(root) self.assertIn(black.TargetVersion.PY312, versions) + def test_consecutive_ignore(self) -> None: + # https://github.com/psf/black/issues/3737 + + source, _ = read_data("miscellaneous", "consecutive_ignore.py") + expected, _ = read_data("miscellaneous", "consecutive_ignore.diff") + tmp_file = Path(black.dump_to_file(source)) + diff_header = re.compile( + rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d " + r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d\+\d\d:\d\d" + ) + try: + result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)]) + self.assertEqual(result.exit_code, 0) + finally: + os.unlink(tmp_file) + + actual = result.output + actual = diff_header.sub(DETERMINISTIC_HEADER, actual) + self.assertEqual(actual, expected) + print(result.output) + def test_expression_ff(self) -> None: source, expected = read_data("simple_cases", "expression.py") tmp_file = Path(black.dump_to_file(source)) From b85d90e7841d923a54408a9cd95138dbb368cb2e Mon Sep 17 00:00:00 2001 From: Kelvin Li <13176405+rdrll@users.noreply.github.com> Date: Wed, 21 Jun 2023 02:04:40 -0700 Subject: [PATCH 2/5] Update CHANGES.md --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index fd4d911287d..854eedbb2b4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,8 @@ - Fix a bug where an illegal trailing comma was added to return type annotations using PEP 604 unions (#3735) +- Fix a bug where multi-line open parenthesis magic comment like `type: ignore` were not + correctly parsed (#3737) ### Preview style From b8e98eb49c027de3adbc249948ec1a0590c34a59 Mon Sep 17 00:00:00 2001 From: Kelvin Li <13176405+rdrll@users.noreply.github.com> Date: Wed, 21 Jun 2023 02:31:09 -0700 Subject: [PATCH 3/5] Lint & Change log fixing --- CHANGES.md | 2 +- src/black/linegen.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 854eedbb2b4..aa8fe67084d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,7 +13,7 @@ - Fix a bug where an illegal trailing comma was added to return type annotations using PEP 604 unions (#3735) - Fix a bug where multi-line open parenthesis magic comment like `type: ignore` were not - correctly parsed (#3737) + correctly parsed (#3740) ### Preview style diff --git a/src/black/linegen.py b/src/black/linegen.py index 433615e63c6..f62c7738bc4 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -1399,7 +1399,12 @@ def maybe_make_parens_invisible_in_atom( if is_lpar_token(first) and is_rpar_token(last): middle = node.children[1] # make parentheses invisible - if not node.children[1].prefix.strip().replace(" ", "").startswith("#type:ignore"): + if ( + not node.children[1] + .prefix.strip() + .replace(" ", "") + .startswith("#type:ignore") + ): first.value = "" last.value = "" maybe_make_parens_invisible_in_atom( From ecb270f8a086083b1f29a5a0f123bd1bc94e5b93 Mon Sep 17 00:00:00 2001 From: Kelvin Li <13176405+rdrll@users.noreply.github.com> Date: Wed, 21 Jun 2023 23:34:17 -0700 Subject: [PATCH 4/5] updates --- .../miscellaneous/consecutive_ignore.diff | 29 ------------------- ...ne_consecutive_open_parentheses_ignore.py} | 22 +++++++++++++- tests/test_black.py | 21 -------------- 3 files changed, 21 insertions(+), 51 deletions(-) delete mode 100644 tests/data/miscellaneous/consecutive_ignore.diff rename tests/data/{miscellaneous/consecutive_ignore.py => simple_cases/multiline_consecutive_open_parentheses_ignore.py} (50%) diff --git a/tests/data/miscellaneous/consecutive_ignore.diff b/tests/data/miscellaneous/consecutive_ignore.diff deleted file mode 100644 index 27b5ff4feb8..00000000000 --- a/tests/data/miscellaneous/consecutive_ignore.diff +++ /dev/null @@ -1,29 +0,0 @@ ---- [Deterministic header] -+++ [Deterministic header] -@@ -1,21 +1,15 @@ - # This is a regression test. Issue #3737 - - a = ( # type: ignore - int( # type: ignore - int( # type: ignore -- int( # type: ignore -- 6 -- ) -+ int(6) # type: ignore - ) - ) - ) - --b = ( -- int( -- 6 -- ) --) -+b = int(6) - --print( "111") # type:ignore --print( "111" ) # type:ignore --print( "111" ) # type:ignore -+print("111") # type:ignore -+print("111") # type:ignore -+print("111") # type:ignore \ No newline at end of file diff --git a/tests/data/miscellaneous/consecutive_ignore.py b/tests/data/simple_cases/multiline_consecutive_open_parentheses_ignore.py similarity index 50% rename from tests/data/miscellaneous/consecutive_ignore.py rename to tests/data/simple_cases/multiline_consecutive_open_parentheses_ignore.py index 4bb3d7adec9..a878ae425ea 100644 --- a/tests/data/miscellaneous/consecutive_ignore.py +++ b/tests/data/simple_cases/multiline_consecutive_open_parentheses_ignore.py @@ -18,4 +18,24 @@ print( "111") # type:ignore print( "111" ) # type:ignore -print( "111" ) # type:ignore \ No newline at end of file +print( "111" ) # type:ignore + + +# output + + +# This is a regression test. Issue #3737 + +a = ( # type: ignore + int( # type: ignore + int( # type: ignore + int(6) # type: ignore + ) + ) +) + +b = int(6) + +print("111") # type:ignore +print("111") # type:ignore +print("111") # type:ignore \ No newline at end of file diff --git a/tests/test_black.py b/tests/test_black.py index 85a9a83366d..5f2e6f5b14c 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -280,27 +280,6 @@ def test_pep_695_version_detection(self) -> None: versions = black.detect_target_versions(root) self.assertIn(black.TargetVersion.PY312, versions) - def test_consecutive_ignore(self) -> None: - # https://github.com/psf/black/issues/3737 - - source, _ = read_data("miscellaneous", "consecutive_ignore.py") - expected, _ = read_data("miscellaneous", "consecutive_ignore.diff") - tmp_file = Path(black.dump_to_file(source)) - diff_header = re.compile( - rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d " - r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d\+\d\d:\d\d" - ) - try: - result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)]) - self.assertEqual(result.exit_code, 0) - finally: - os.unlink(tmp_file) - - actual = result.output - actual = diff_header.sub(DETERMINISTIC_HEADER, actual) - self.assertEqual(actual, expected) - print(result.output) - def test_expression_ff(self) -> None: source, expected = read_data("simple_cases", "expression.py") tmp_file = Path(black.dump_to_file(source)) From 5035ce6f13f1f24f9e7846905e531f9bd97fe819 Mon Sep 17 00:00:00 2001 From: Kelvin Li <13176405+rdrll@users.noreply.github.com> Date: Sat, 24 Jun 2023 09:58:21 -0700 Subject: [PATCH 5/5] functionality change for is_type_comment `is_type_comment` now specifically deals with general type comments for a leaf. `is_type_ignore_comment` now handles type comments contains ignore annotation for a leaf `is_type_ignore_comment_string` used to determine if a string is an ignore type comment --- src/black/linegen.py | 8 +++---- src/black/lines.py | 5 ++-- src/black/nodes.py | 23 +++++++++++++++---- ...ine_consecutive_open_parentheses_ignore.py | 12 +++++----- 4 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index f62c7738bc4..5ef3bbd1705 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -49,6 +49,7 @@ is_stub_body, is_stub_suite, is_tuple_containing_walrus, + is_type_ignore_comment_string, is_vararg, is_walrus_assignment, is_yield, @@ -1400,10 +1401,9 @@ def maybe_make_parens_invisible_in_atom( middle = node.children[1] # make parentheses invisible if ( - not node.children[1] - .prefix.strip() - .replace(" ", "") - .startswith("#type:ignore") + # If the prefix of `middle` includes a type comment with + # ignore annotation, then we do not remove the parentheses + not is_type_ignore_comment_string(middle.prefix.strip()) ): first.value = "" last.value = "" diff --git a/src/black/lines.py b/src/black/lines.py index daf0444d24e..ea8fe520756 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -28,6 +28,7 @@ is_multiline_string, is_one_sequence_between, is_type_comment, + is_type_ignore_comment, is_with_or_async_with_stmt, replace_child, syms, @@ -251,7 +252,7 @@ def contains_uncollapsable_type_comments(self) -> bool: for comment in comments: if is_type_comment(comment): if comment_seen or ( - not is_type_comment(comment, " ignore") + not is_type_ignore_comment(comment) and leaf_id not in ignored_ids ): return True @@ -288,7 +289,7 @@ def contains_unsplittable_type_ignore(self) -> bool: # line. for node in self.leaves[-2:]: for comment in self.comments.get(id(node), []): - if is_type_comment(comment, " ignore"): + if is_type_ignore_comment(comment): return True return False diff --git a/src/black/nodes.py b/src/black/nodes.py index 45070909df4..b019b0c6440 100644 --- a/src/black/nodes.py +++ b/src/black/nodes.py @@ -816,12 +816,27 @@ def is_async_stmt_or_funcdef(leaf: Leaf) -> bool: ) -def is_type_comment(leaf: Leaf, suffix: str = "") -> bool: - """Return True if the given leaf is a special comment. - Only returns true for type comments for now.""" +def is_type_comment(leaf: Leaf) -> bool: + """Return True if the given leaf is a type comment. This function should only + be used for general type comments (excluding ignore annotations, which should + use `is_type_ignore_comment`). Note that general type comments are no longer + used in modern version of Python, this function may be deprecated in the future.""" t = leaf.type v = leaf.value - return t in {token.COMMENT, STANDALONE_COMMENT} and v.startswith("# type:" + suffix) + return t in {token.COMMENT, STANDALONE_COMMENT} and v.startswith("# type:") + + +def is_type_ignore_comment(leaf: Leaf) -> bool: + """Return True if the given leaf is a type comment with ignore annotation.""" + t = leaf.type + v = leaf.value + return t in {token.COMMENT, STANDALONE_COMMENT} and is_type_ignore_comment_string(v) + + +def is_type_ignore_comment_string(value: str) -> bool: + """Return True if the given string match with type comment with + ignore annotation.""" + return value.startswith("# type: ignore") def wrap_in_parentheses(parent: Node, child: LN, *, visible: bool = True) -> None: diff --git a/tests/data/simple_cases/multiline_consecutive_open_parentheses_ignore.py b/tests/data/simple_cases/multiline_consecutive_open_parentheses_ignore.py index a878ae425ea..6ec8bb45408 100644 --- a/tests/data/simple_cases/multiline_consecutive_open_parentheses_ignore.py +++ b/tests/data/simple_cases/multiline_consecutive_open_parentheses_ignore.py @@ -16,9 +16,9 @@ ) ) -print( "111") # type:ignore -print( "111" ) # type:ignore -print( "111" ) # type:ignore +print( "111") # type: ignore +print( "111" ) # type: ignore +print( "111" ) # type: ignore # output @@ -36,6 +36,6 @@ b = int(6) -print("111") # type:ignore -print("111") # type:ignore -print("111") # type:ignore \ No newline at end of file +print("111") # type: ignore +print("111") # type: ignore +print("111") # type: ignore \ No newline at end of file