From 45a0e24f006bccfe6e097a783bb85fa0cd1b8522 Mon Sep 17 00:00:00 2001 From: Henri Holopainen Date: Thu, 9 Nov 2023 17:25:57 +0200 Subject: [PATCH 1/9] Move comment related functions to comments.py and get rid of magic strings --- src/black/comments.py | 32 +++++++++++++++++++++++++++++--- src/black/linegen.py | 8 ++++++-- src/black/lines.py | 3 +-- src/black/nodes.py | 23 ----------------------- 4 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/black/comments.py b/src/black/comments.py index 862fc7607cc..0238f20d07f 100644 --- a/src/black/comments.py +++ b/src/black/comments.py @@ -17,15 +17,18 @@ from blib2to3.pytree import Leaf, Node # types +COMMENT_EXCEPTIONS = " !:#'" +_TYPE_PREFIX = "# type:" +_COMMENT_PREFIX = "# " +_COMMENT_LIST_SEPARATOR = ";" + LN = Union[Leaf, Node] FMT_OFF: Final = {"# fmt: off", "# fmt:off", "# yapf: disable"} FMT_SKIP: Final = {"# fmt: skip", "# fmt:skip"} FMT_ON: Final = {"# fmt: on", "# fmt:on", "# yapf: enable"} -COMMENT_EXCEPTIONS = " !:#'" -_COMMENT_PREFIX = "# " -_COMMENT_LIST_SEPARATOR = ";" +_TYPE_IGNORE: Final = {_TYPE_PREFIX + "ignore", _TYPE_PREFIX + " ignore"} @dataclass @@ -325,6 +328,29 @@ def children_contains_fmt_on(container: LN) -> bool: return False +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_PREFIX) + + +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 any(value.startswith(type_ignore) for type_ignore in _TYPE_IGNORE) + + def contains_pragma_comment(comment_list: List[Leaf]) -> bool: """ Returns: diff --git a/src/black/linegen.py b/src/black/linegen.py index e2c961d7a01..f7f889679e0 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -15,7 +15,12 @@ get_leaves_inside_matching_brackets, max_delimiter_priority_in_atom, ) -from black.comments import FMT_OFF, generate_comments, list_comments +from black.comments import ( + FMT_OFF, + generate_comments, + is_type_ignore_comment_string, + list_comments, +) from black.lines import ( Line, RHSResult, @@ -51,7 +56,6 @@ is_stub_body, is_stub_suite, is_tuple_containing_walrus, - is_type_ignore_comment_string, is_vararg, is_walrus_assignment, is_yield, diff --git a/src/black/lines.py b/src/black/lines.py index 3ade0a5f4a5..0560591f19a 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -15,6 +15,7 @@ ) from black.brackets import COMMA_PRIORITY, DOT_PRIORITY, BracketTracker +from black.comments import is_type_comment, is_type_ignore_comment from black.mode import Mode, Preview from black.nodes import ( BRACKETS, @@ -28,8 +29,6 @@ is_import, is_multiline_string, is_one_sequence_between, - is_type_comment, - is_type_ignore_comment, is_with_or_async_with_stmt, replace_child, syms, diff --git a/src/black/nodes.py b/src/black/nodes.py index 9251b0defb0..fc0c5a16b5f 100644 --- a/src/black/nodes.py +++ b/src/black/nodes.py @@ -838,29 +838,6 @@ def is_async_stmt_or_funcdef(leaf: Leaf) -> bool: ) -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:") - - -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: """Wrap `child` in parentheses. From 660112f2e68449723b62543ef32584b25831104e Mon Sep 17 00:00:00 2001 From: Henri Holopainen Date: Thu, 9 Nov 2023 17:26:18 +0200 Subject: [PATCH 2/9] Add test case --- ...preview_hug_parens_with_braces_and_square_brackets.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/data/cases/preview_hug_parens_with_braces_and_square_brackets.py b/tests/data/cases/preview_hug_parens_with_braces_and_square_brackets.py index 97b5b2e8dd1..022f68f5774 100644 --- a/tests/data/cases/preview_hug_parens_with_braces_and_square_brackets.py +++ b/tests/data/cases/preview_hug_parens_with_braces_and_square_brackets.py @@ -162,6 +162,11 @@ def foo_square_brackets(request): for individual in container["nested"] ]) +func( # type:ignore + [ # type: ignore + "a" + ] +) # output def foo_brackets(request): return JsonResponse({ @@ -343,3 +348,7 @@ def foo_square_brackets(request): # Foobar for individual in container["nested"] ]) + +func( # type:ignore + ["a"] # type: ignore +) From 3a6a229e5c041f6779e9179f67cf0a5c9c17ce9c Mon Sep 17 00:00:00 2001 From: Henri Holopainen Date: Thu, 9 Nov 2023 17:27:12 +0200 Subject: [PATCH 3/9] Don't hug parens if type ignore on opening paren --- src/black/comments.py | 8 ++++++++ src/black/linegen.py | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/src/black/comments.py b/src/black/comments.py index 0238f20d07f..2d825eeae8b 100644 --- a/src/black/comments.py +++ b/src/black/comments.py @@ -328,6 +328,14 @@ def children_contains_fmt_on(container: LN) -> bool: return False +def contains_type_ignore_comment(comment_list: List[Leaf]) -> bool: + """Return True if the given leaf contains a type comment with ignore annotation.""" + for comment in comment_list: + if is_type_ignore_comment(comment): + return True + return False + + 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 diff --git a/src/black/linegen.py b/src/black/linegen.py index f7f889679e0..d700563173f 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -17,6 +17,7 @@ ) from black.comments import ( FMT_OFF, + contains_type_ignore_comment, generate_comments, is_type_ignore_comment_string, list_comments, @@ -829,6 +830,9 @@ def _first_right_hand_split( and tail_leaves[0].opening_bracket is head_leaves[-1] and body_leaves[-1].type in [token.RBRACE, token.RSQB] and body_leaves[-1].opening_bracket is body_leaves[is_unpacking] + and not contains_type_ignore_comment( + line.comments.get(id(head_leaves[-1]), []) + ) ): head_leaves = head_leaves + body_leaves[: 1 + is_unpacking] tail_leaves = body_leaves[-1:] + tail_leaves From 3a110f8a32cb1b966374299969b0e9ccfcab99f3 Mon Sep 17 00:00:00 2001 From: Henri Holopainen Date: Thu, 9 Nov 2023 17:29:39 +0200 Subject: [PATCH 4/9] Add changelog line --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 9446927b8d1..220fbfc6fe5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,7 +12,7 @@ ### Preview style - +- Fix crash when hugging parens with #type:ignore comments (#XXXX) ### Configuration From 9fb65c6bb5a8441188202a25de0a56c603396c59 Mon Sep 17 00:00:00 2001 From: Henri Holopainen Date: Thu, 9 Nov 2023 17:31:18 +0200 Subject: [PATCH 5/9] Update PR number --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 220fbfc6fe5..a3cf622a30b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,7 +12,7 @@ ### Preview style -- Fix crash when hugging parens with #type:ignore comments (#XXXX) +- Fix crash when hugging parens with #type:ignore comments (#4037) ### Configuration From f99d14a930d4c87e491379e7b062d70bc2541c67 Mon Sep 17 00:00:00 2001 From: Henri Holopainen Date: Thu, 9 Nov 2023 17:39:35 +0200 Subject: [PATCH 6/9] Fix typo --- src/black/comments.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/black/comments.py b/src/black/comments.py index 2d825eeae8b..c1bcd3f48ee 100644 --- a/src/black/comments.py +++ b/src/black/comments.py @@ -329,7 +329,7 @@ def children_contains_fmt_on(container: LN) -> bool: def contains_type_ignore_comment(comment_list: List[Leaf]) -> bool: - """Return True if the given leaf contains a type comment with ignore annotation.""" + """Return True if the given list contains a type comment with ignore annotation.""" for comment in comment_list: if is_type_ignore_comment(comment): return True From 3d59d73173195a8474bf913dafefb683fe918772 Mon Sep 17 00:00:00 2001 From: Henri Holopainen Date: Fri, 10 Nov 2023 16:11:29 +0200 Subject: [PATCH 7/9] Add test cases --- ..._parens_with_braces_and_square_brackets.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/data/cases/preview_hug_parens_with_braces_and_square_brackets.py b/tests/data/cases/preview_hug_parens_with_braces_and_square_brackets.py index 022f68f5774..c4556deb1ac 100644 --- a/tests/data/cases/preview_hug_parens_with_braces_and_square_brackets.py +++ b/tests/data/cases/preview_hug_parens_with_braces_and_square_brackets.py @@ -167,6 +167,30 @@ def foo_square_brackets(request): "a" ] ) + +func( + [ + "a" + ] # type:ignore +) # type: ignore + +func( + [ # type:ignore + "a" # type: ignore + ] # type:ignore +) + +func( # type:ignore + [ + "a" # type: ignore + ] +) # type:ignore + +func( # type:ignore + [ # type: ignore + "a" # type:ignore + ] # type: ignore +) # type:ignore # output def foo_brackets(request): return JsonResponse({ @@ -352,3 +376,21 @@ def foo_square_brackets(request): func( # type:ignore ["a"] # type: ignore ) + +func( + ["a"] # type:ignore +) # type: ignore + +func([ # type:ignore + "a" # type: ignore +]) # type:ignore + +func([ # type:ignore + "a" # type: ignore +]) # type:ignore + +func( # type:ignore + [ # type: ignore + "a" # type:ignore + ] # type: ignore +) # type:ignore From 2b2209976a4fb8c27bfbfbc40be72dce8fd4ff14 Mon Sep 17 00:00:00 2001 From: Henri Holopainen Date: Fri, 10 Nov 2023 16:12:44 +0200 Subject: [PATCH 8/9] Make sure we don't combine two #type:ignore comments to one line --- src/black/linegen.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index d700563173f..0198f12885e 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -830,13 +830,31 @@ def _first_right_hand_split( and tail_leaves[0].opening_bracket is head_leaves[-1] and body_leaves[-1].type in [token.RBRACE, token.RSQB] and body_leaves[-1].opening_bracket is body_leaves[is_unpacking] - and not contains_type_ignore_comment( - line.comments.get(id(head_leaves[-1]), []) - ) ): - head_leaves = head_leaves + body_leaves[: 1 + is_unpacking] - tail_leaves = body_leaves[-1:] + tail_leaves - body_leaves = body_leaves[1 + is_unpacking : -1] + last_leaf_on_head_line = head_leaves[-1] + last_leaf_on_first_body_line = [ + leaf for leaf in body_leaves if leaf.lineno == body_leaves[0].lineno + ][-1] + last_leaf_on_last_body_line = [ + leaf for leaf in body_leaves if leaf.lineno == body_leaves[-1].lineno + ][-1] + last_leaf_on_tail_line = tail_leaves[-1] + + start_blocked_by_type_ignore = contains_type_ignore_comment( + line.comments.get(id(last_leaf_on_head_line), []) + ) and contains_type_ignore_comment( + line.comments.get(id(last_leaf_on_first_body_line), []) + ) + end_blocked_by_type_ignore = contains_type_ignore_comment( + line.comments.get(id(last_leaf_on_last_body_line), []) + ) and contains_type_ignore_comment( + line.comments.get(id(last_leaf_on_tail_line), []) + ) + + if not (start_blocked_by_type_ignore or end_blocked_by_type_ignore): + head_leaves = head_leaves + body_leaves[: 1 + is_unpacking] + tail_leaves = body_leaves[-1:] + tail_leaves + body_leaves = body_leaves[1 + is_unpacking : -1] head = bracket_split_build_line( head_leaves, line, opening_bracket, component=_BracketSplitComponent.head From 61bf5239dfd369934816a9b5f6b5ac0b3dc87e80 Mon Sep 17 00:00:00 2001 From: Henri Holopainen Date: Fri, 10 Nov 2023 16:14:44 +0200 Subject: [PATCH 9/9] Add myself to contributors --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index e0511bb9b7c..644c38e81c3 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -79,6 +79,7 @@ Multiple contributions by: - [Hadi Alqattan](mailto:alqattanhadizaki@gmail.com) - [Hassan Abouelela](mailto:hassan@hassanamr.com) - [Heaford](mailto:dan@heaford.com) +- Henri Holopainen - [Hugo Barrera](mailto::hugo@barrera.io) - Hugo van Kemenade - [Hynek Schlawack](mailto:hs@ox.cx)