]> git.madduck.net Git - etc/vim.git/commitdiff

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Fix the handling of `# fmt: skip` when it's at a colon line (#3148)
authorYilei "Dolee" Yang <yileiyang@google.com>
Tue, 19 Jul 2022 21:26:11 +0000 (14:26 -0700)
committerGitHub <noreply@github.com>
Tue, 19 Jul 2022 21:26:11 +0000 (17:26 -0400)
When the Leaf node with `# fmt: skip` is a NEWLINE inside a `suite`
Node, the nodes to ignore should be from the siblings of the parent
`suite` Node.

There is a also a special case for the ASYNC token, where it expands
to the grandparent Node where the ASYNC token is.

This fixes GH-2646, GH-3126, GH-2680, GH-2421, GH-2339, and GH-2138.

CHANGES.md
src/black/comments.py
src/black/linegen.py
tests/data/simple_cases/fmtskip8.py [new file with mode: 0644]

index 8543a8dbfe09da072b82f86d5092746e4f5ac5a4..90c62de6b98a968eea426c14840027e089826803 100644 (file)
@@ -10,6 +10,7 @@
 
 <!-- Changes that affect Black's stable style -->
 
+- Fix incorrect handling of `# fmt: skip` on colon `:` lines. (#3148)
 - Comments are no longer deleted when a line had spaces removed around power operators
   (#2874)
 
index 23bf87fca7cb8e9a1fea521417cc80241aabfbd6..522c1a7b88caac45982d91154d6c036ad195db16 100644 (file)
@@ -9,7 +9,7 @@ if sys.version_info >= (3, 8):
 else:
     from typing_extensions import Final
 
-from blib2to3.pytree import Node, Leaf
+from blib2to3.pytree import Node, Leaf, type_repr
 from blib2to3.pgen2 import token
 
 from black.nodes import first_leaf_column, preceding_leaf, container_of
@@ -174,6 +174,11 @@ def convert_one_fmt_off_pair(node: Node, *, preview: bool) -> bool:
                 first.prefix = prefix[comment.consumed :]
             if comment.value in FMT_SKIP:
                 first.prefix = ""
+                standalone_comment_prefix = prefix
+            else:
+                standalone_comment_prefix = (
+                    prefix[:previous_consumed] + "\n" * comment.newlines
+                )
             hidden_value = "".join(str(n) for n in ignored_nodes)
             if comment.value in FMT_OFF:
                 hidden_value = comment.value + "\n" + hidden_value
@@ -195,7 +200,7 @@ def convert_one_fmt_off_pair(node: Node, *, preview: bool) -> bool:
                 Leaf(
                     STANDALONE_COMMENT,
                     hidden_value,
-                    prefix=prefix[:previous_consumed] + "\n" * comment.newlines,
+                    prefix=standalone_comment_prefix,
                 ),
             )
             return True
@@ -211,26 +216,10 @@ def generate_ignored_nodes(
     If comment is skip, returns leaf only.
     Stops at the end of the block.
     """
-    container: Optional[LN] = container_of(leaf)
     if comment.value in FMT_SKIP:
-        prev_sibling = leaf.prev_sibling
-        # Need to properly format the leaf prefix to compare it to comment.value,
-        # which is also formatted
-        comments = list_comments(leaf.prefix, is_endmarker=False, preview=preview)
-        if comments and comment.value == comments[0].value and prev_sibling is not None:
-            leaf.prefix = ""
-            siblings = [prev_sibling]
-            while (
-                "\n" not in prev_sibling.prefix
-                and prev_sibling.prev_sibling is not None
-            ):
-                prev_sibling = prev_sibling.prev_sibling
-                siblings.insert(0, prev_sibling)
-            for sibling in siblings:
-                yield sibling
-        elif leaf.parent is not None:
-            yield leaf.parent
+        yield from _generate_ignored_nodes_from_fmt_skip(leaf, comment, preview=preview)
         return
+    container: Optional[LN] = container_of(leaf)
     while container is not None and container.type != token.ENDMARKER:
         if is_fmt_on(container, preview=preview):
             return
@@ -246,6 +235,51 @@ def generate_ignored_nodes(
             container = container.next_sibling
 
 
+def _generate_ignored_nodes_from_fmt_skip(
+    leaf: Leaf, comment: ProtoComment, *, preview: bool
+) -> Iterator[LN]:
+    """Generate all leaves that should be ignored by the `# fmt: skip` from `leaf`."""
+    prev_sibling = leaf.prev_sibling
+    parent = leaf.parent
+    # Need to properly format the leaf prefix to compare it to comment.value,
+    # which is also formatted
+    comments = list_comments(leaf.prefix, is_endmarker=False, preview=preview)
+    if not comments or comment.value != comments[0].value:
+        return
+    if prev_sibling is not None:
+        leaf.prefix = ""
+        siblings = [prev_sibling]
+        while "\n" not in prev_sibling.prefix and prev_sibling.prev_sibling is not None:
+            prev_sibling = prev_sibling.prev_sibling
+            siblings.insert(0, prev_sibling)
+        for sibling in siblings:
+            yield sibling
+    elif (
+        parent is not None
+        and type_repr(parent.type) == "suite"
+        and leaf.type == token.NEWLINE
+    ):
+        # The `# fmt: skip` is on the colon line of the if/while/def/class/...
+        # statements. The ignored nodes should be previous siblings of the
+        # parent suite node.
+        leaf.prefix = ""
+        ignored_nodes: List[LN] = []
+        parent_sibling = parent.prev_sibling
+        while parent_sibling is not None and type_repr(parent_sibling.type) != "suite":
+            ignored_nodes.insert(0, parent_sibling)
+            parent_sibling = parent_sibling.prev_sibling
+        # Special case for `async_stmt` where the ASYNC token is on the
+        # grandparent node.
+        grandparent = parent.parent
+        if (
+            grandparent is not None
+            and grandparent.prev_sibling is not None
+            and grandparent.prev_sibling.type == token.ASYNC
+        ):
+            ignored_nodes.insert(0, grandparent.prev_sibling)
+        yield from iter(ignored_nodes)
+
+
 def is_fmt_on(container: LN, preview: bool) -> bool:
     """Determine whether formatting is switched on within a container.
     Determined by whether the last `# fmt:` comment is `on` or `off`.
index 1f132b7888fe9a07fda7ad738a198d70e0e4e0a4..8e8d41e239ae8e732e380ad08710c49f25156745 100644 (file)
@@ -220,7 +220,9 @@ class LineGenerator(Visitor[Line]):
         for child in children:
             yield from self.visit(child)
 
-            if child.type == token.ASYNC:
+            if child.type == token.ASYNC or child.type == STANDALONE_COMMENT:
+                # STANDALONE_COMMENT happens when `# fmt: skip` is applied on the async
+                # line.
                 break
 
         internal_stmt = next(children)
diff --git a/tests/data/simple_cases/fmtskip8.py b/tests/data/simple_cases/fmtskip8.py
new file mode 100644 (file)
index 0000000..38e9c2a
--- /dev/null
@@ -0,0 +1,62 @@
+# Make sure a leading comment is not removed.
+def some_func(  unformatted,  args  ):  # fmt: skip
+    print("I am some_func")
+    return 0
+    # Make sure this comment is not removed.
+
+
+# Make sure a leading comment is not removed.
+async def some_async_func(  unformatted,   args):  # fmt: skip
+    print("I am some_async_func")
+    await asyncio.sleep(1)
+
+
+# Make sure a leading comment is not removed.
+class SomeClass(  Unformatted,  SuperClasses  ):  # fmt: skip
+    def some_method(  self,  unformatted,  args  ):  # fmt: skip
+        print("I am some_method")
+        return 0
+
+    async def some_async_method(  self,  unformatted,  args  ):  # fmt: skip
+        print("I am some_async_method")
+        await asyncio.sleep(1)
+
+
+# Make sure a leading comment is not removed.
+if  unformatted_call(  args  ):  # fmt: skip
+    print("First branch")
+    # Make sure this is not removed.
+elif  another_unformatted_call(  args  ):  # fmt: skip
+    print("Second branch")
+else  :  # fmt: skip
+    print("Last branch")
+
+
+while  some_condition(  unformatted,  args  ):  # fmt: skip
+    print("Do something")
+
+
+for  i  in  some_iter(  unformatted,  args  ):  # fmt: skip
+    print("Do something")
+
+
+async def test_async_for():
+    async  for  i  in  some_async_iter(  unformatted,  args  ):  # fmt: skip
+        print("Do something")
+
+
+try  :  # fmt: skip
+    some_call()
+except  UnformattedError  as  ex:  # fmt: skip
+    handle_exception()
+finally  :  # fmt: skip
+    finally_call()
+
+
+with  give_me_context(  unformatted,  args  ):  # fmt: skip
+    print("Do something")
+
+
+async def test_async_with():
+    async  with  give_me_async_context(  unformatted,  args  ):  # fmt: skip
+        print("Do something")