diff --git a/CHANGES.md b/CHANGES.md index 9446927b8d1..f4bc1f2a91b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,9 @@ +- Treat lines marked with common pragma markers (e.g. `# noqa`) the same as + `# type: ignore` -- don't split them, but allow merging (#4039) + ### Preview style diff --git a/src/black/comments.py b/src/black/comments.py index 862fc7607cc..a48bd1be01e 100644 --- a/src/black/comments.py +++ b/src/black/comments.py @@ -6,6 +6,7 @@ from black.mode import Mode, Preview from black.nodes import ( CLOSING_BRACKETS, + PRAGMA_COMMENT_MARKER, STANDALONE_COMMENT, WHITESPACE, container_of, @@ -333,7 +334,7 @@ def contains_pragma_comment(comment_list: List[Leaf]) -> bool: pylint). """ for comment in comment_list: - if comment.value.startswith(("# type:", "# noqa", "# pylint:")): + if comment.value.startswith(PRAGMA_COMMENT_MARKER): return True return False diff --git a/src/black/linegen.py b/src/black/linegen.py index e2c961d7a01..c3d5d871d48 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -47,11 +47,11 @@ is_name_token, is_one_sequence_between, is_one_tuple, + is_pragma_comment_string, is_rpar_token, is_stub_body, is_stub_suite, is_tuple_containing_walrus, - is_type_ignore_comment_string, is_vararg, is_walrus_assignment, is_yield, @@ -1521,7 +1521,7 @@ def maybe_make_parens_invisible_in_atom( if ( # 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()) + not is_pragma_comment_string(middle.prefix.strip()) ): first.value = "" last.value = "" diff --git a/src/black/lines.py b/src/black/lines.py index 3ade0a5f4a5..3064d5fee5d 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -28,8 +28,8 @@ is_import, is_multiline_string, is_one_sequence_between, + is_pragma_comment, is_type_comment, - is_type_ignore_comment, is_with_or_async_with_stmt, replace_child, syms, @@ -285,8 +285,7 @@ def contains_uncollapsable_type_comments(self) -> bool: for comment in comments: if is_type_comment(comment): if comment_seen or ( - not is_type_ignore_comment(comment) - and leaf_id not in ignored_ids + not is_pragma_comment(comment) and leaf_id not in ignored_ids ): return True @@ -322,7 +321,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_ignore_comment(comment): + if is_pragma_comment(comment): return True return False diff --git a/src/black/nodes.py b/src/black/nodes.py index 9251b0defb0..ee0ba28bc3f 100644 --- a/src/black/nodes.py +++ b/src/black/nodes.py @@ -134,6 +134,8 @@ BRACKETS: Final = OPENING_BRACKETS | CLOSING_BRACKETS ALWAYS_NO_SPACE: Final = CLOSING_BRACKETS | {token.COMMA, STANDALONE_COMMENT} +PRAGMA_COMMENT_MARKER: Final = ("# type:", "# noqa", "# pylint:") + RARROW = 55 @@ -848,17 +850,17 @@ def is_type_comment(leaf: Leaf) -> bool: 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.""" +def is_pragma_comment(leaf: Leaf) -> bool: + """Return True if the given leaf is a pragma comment. Pragma comments cause lines to + be unsplittable, but mergeable.""" t = leaf.type v = leaf.value - return t in {token.COMMENT, STANDALONE_COMMENT} and is_type_ignore_comment_string(v) + return t in {token.COMMENT, STANDALONE_COMMENT} and is_pragma_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 is_pragma_comment_string(value: str) -> bool: + """Return True if the given string matches a known pragma comment.""" + return value.startswith(PRAGMA_COMMENT_MARKER) def wrap_in_parentheses(parent: Node, child: LN, *, visible: bool = True) -> None: diff --git a/tests/data/cases/comments6.py b/tests/data/cases/comments6.py index 735c6aa6d7a..7f4fee8df42 100644 --- a/tests/data/cases/comments6.py +++ b/tests/data/cases/comments6.py @@ -116,3 +116,38 @@ def func( ) aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) # type: ignore[arg-type] + + +def func( + a=some_list[0], # type: int +): # type: () -> int + c = call( + 0.0123, + 0.0456, + 0.0789, + 0.0123, + 0.0456, + 0.0789, + 0.0123, + 0.0456, + 0.0789, + a[-1], # noqa + ) + + c = call( + "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa", "aaaaaaaa" # noqa + ) + + +result = ( # aaa + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +) + +AAAAAAAAAAAAA = [AAAAAAAAAAAAA] + SHARED_AAAAAAAAAAAAA + USER_AAAAAAAAAAAAA + AAAAAAAAAAAAA # noqa + +call_to_some_function_asdf( + foo, + [AAAAAAAAAAAAAAAAAAAAAAA, AAAAAAAAAAAAAAAAAAAAAAA, AAAAAAAAAAAAAAAAAAAAAAA, BBBBBBBBBBBB], # noqa +) + +aaaaaaaaaaaaa, bbbbbbbbb = map(list, map(itertools.chain.from_iterable, zip(*items))) # noqa: A000 \ No newline at end of file diff --git a/tests/data/cases/torture.py b/tests/data/cases/torture.py index 2a194759a82..eb30556d591 100644 --- a/tests/data/cases/torture.py +++ b/tests/data/cases/torture.py @@ -13,7 +13,7 @@ class A: def foo(self): for _ in range(10): - aaaaaaaaaaaaaaaaaaa = bbbbbbbbbbbbbbb.cccccccccc( # pylint: disable=no-member + aaaaaaaaaaaaaaaaaaa = bbbbbbbbbbbbbbb.cccccccccc( # notpylint: disable=no-member xxxxxxxxxxxx ) @@ -59,7 +59,7 @@ def foo(self): for _ in range(10): aaaaaaaaaaaaaaaaaaa = bbbbbbbbbbbbbbb.cccccccccc( xxxxxxxxxxxx - ) # pylint: disable=no-member + ) # notpylint: disable=no-member def test(self, othr):