Fix bug where # fmt: skip is not being respected with one-liner functions (#4552)
This commit is contained in:
parent
bb802cf19a
commit
3e9dd25dad
@ -14,6 +14,9 @@
|
|||||||
|
|
||||||
<!-- Changes that affect Black's preview style -->
|
<!-- Changes that affect Black's preview style -->
|
||||||
|
|
||||||
|
- Fix a bug where one-liner functions/conditionals marked with `# fmt: skip`
|
||||||
|
would still be formatted (#4552)
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
<!-- Changes to how Black can be configured -->
|
<!-- Changes to how Black can be configured -->
|
||||||
|
@ -26,6 +26,9 @@ Currently, the following features are included in the preview style:
|
|||||||
statements, except when the line after the import is a comment or an import statement
|
statements, except when the line after the import is a comment or an import statement
|
||||||
- `wrap_long_dict_values_in_parens`: Add parentheses around long values in dictionaries
|
- `wrap_long_dict_values_in_parens`: Add parentheses around long values in dictionaries
|
||||||
([see below](labels/wrap-long-dict-values))
|
([see below](labels/wrap-long-dict-values))
|
||||||
|
- `fix_fmt_skip_in_one_liners`: Fix `# fmt: skip` behaviour on one-liner declarations,
|
||||||
|
such as `def foo(): return "mock" # fmt: skip`, where previously the declaration
|
||||||
|
would have been incorrectly collapsed.
|
||||||
|
|
||||||
(labels/unstable-features)=
|
(labels/unstable-features)=
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from typing import Final, Optional, Union
|
from typing import Final, Optional, Union
|
||||||
|
|
||||||
from black.mode import Mode
|
from black.mode import Mode, Preview
|
||||||
from black.nodes import (
|
from black.nodes import (
|
||||||
CLOSING_BRACKETS,
|
CLOSING_BRACKETS,
|
||||||
STANDALONE_COMMENT,
|
STANDALONE_COMMENT,
|
||||||
@ -270,7 +270,7 @@ def generate_ignored_nodes(
|
|||||||
Stops at the end of the block.
|
Stops at the end of the block.
|
||||||
"""
|
"""
|
||||||
if _contains_fmt_skip_comment(comment.value, mode):
|
if _contains_fmt_skip_comment(comment.value, mode):
|
||||||
yield from _generate_ignored_nodes_from_fmt_skip(leaf, comment)
|
yield from _generate_ignored_nodes_from_fmt_skip(leaf, comment, mode)
|
||||||
return
|
return
|
||||||
container: Optional[LN] = container_of(leaf)
|
container: Optional[LN] = container_of(leaf)
|
||||||
while container is not None and container.type != token.ENDMARKER:
|
while container is not None and container.type != token.ENDMARKER:
|
||||||
@ -309,11 +309,12 @@ def generate_ignored_nodes(
|
|||||||
|
|
||||||
|
|
||||||
def _generate_ignored_nodes_from_fmt_skip(
|
def _generate_ignored_nodes_from_fmt_skip(
|
||||||
leaf: Leaf, comment: ProtoComment
|
leaf: Leaf, comment: ProtoComment, mode: Mode
|
||||||
) -> Iterator[LN]:
|
) -> Iterator[LN]:
|
||||||
"""Generate all leaves that should be ignored by the `# fmt: skip` from `leaf`."""
|
"""Generate all leaves that should be ignored by the `# fmt: skip` from `leaf`."""
|
||||||
prev_sibling = leaf.prev_sibling
|
prev_sibling = leaf.prev_sibling
|
||||||
parent = leaf.parent
|
parent = leaf.parent
|
||||||
|
ignored_nodes: list[LN] = []
|
||||||
# Need to properly format the leaf prefix to compare it to comment.value,
|
# Need to properly format the leaf prefix to compare it to comment.value,
|
||||||
# which is also formatted
|
# which is also formatted
|
||||||
comments = list_comments(leaf.prefix, is_endmarker=False)
|
comments = list_comments(leaf.prefix, is_endmarker=False)
|
||||||
@ -321,11 +322,54 @@ def _generate_ignored_nodes_from_fmt_skip(
|
|||||||
return
|
return
|
||||||
if prev_sibling is not None:
|
if prev_sibling is not None:
|
||||||
leaf.prefix = ""
|
leaf.prefix = ""
|
||||||
|
|
||||||
|
if Preview.fix_fmt_skip_in_one_liners not in mode:
|
||||||
siblings = [prev_sibling]
|
siblings = [prev_sibling]
|
||||||
while "\n" not in prev_sibling.prefix and prev_sibling.prev_sibling is not None:
|
while (
|
||||||
|
"\n" not in prev_sibling.prefix
|
||||||
|
and prev_sibling.prev_sibling is not None
|
||||||
|
):
|
||||||
prev_sibling = prev_sibling.prev_sibling
|
prev_sibling = prev_sibling.prev_sibling
|
||||||
siblings.insert(0, prev_sibling)
|
siblings.insert(0, prev_sibling)
|
||||||
yield from siblings
|
yield from siblings
|
||||||
|
return
|
||||||
|
|
||||||
|
# Generates the nodes to be ignored by `fmt: skip`.
|
||||||
|
|
||||||
|
# Nodes to ignore are the ones on the same line as the
|
||||||
|
# `# fmt: skip` comment, excluding the `# fmt: skip`
|
||||||
|
# node itself.
|
||||||
|
|
||||||
|
# Traversal process (starting at the `# fmt: skip` node):
|
||||||
|
# 1. Move to the `prev_sibling` of the current node.
|
||||||
|
# 2. If `prev_sibling` has children, go to its rightmost leaf.
|
||||||
|
# 3. If there’s no `prev_sibling`, move up to the parent
|
||||||
|
# node and repeat.
|
||||||
|
# 4. Continue until:
|
||||||
|
# a. You encounter an `INDENT` or `NEWLINE` node (indicates
|
||||||
|
# start of the line).
|
||||||
|
# b. You reach the root node.
|
||||||
|
|
||||||
|
# Include all visited LEAVES in the ignored list, except INDENT
|
||||||
|
# or NEWLINE leaves.
|
||||||
|
|
||||||
|
current_node = prev_sibling
|
||||||
|
ignored_nodes = [current_node]
|
||||||
|
if current_node.prev_sibling is None and current_node.parent is not None:
|
||||||
|
current_node = current_node.parent
|
||||||
|
while "\n" not in current_node.prefix and current_node.prev_sibling is not None:
|
||||||
|
leaf_nodes = list(current_node.prev_sibling.leaves())
|
||||||
|
current_node = leaf_nodes[-1] if leaf_nodes else current_node
|
||||||
|
|
||||||
|
if current_node.type in (token.NEWLINE, token.INDENT):
|
||||||
|
current_node.prefix = ""
|
||||||
|
break
|
||||||
|
|
||||||
|
ignored_nodes.insert(0, current_node)
|
||||||
|
|
||||||
|
if current_node.prev_sibling is None and current_node.parent is not None:
|
||||||
|
current_node = current_node.parent
|
||||||
|
yield from ignored_nodes
|
||||||
elif (
|
elif (
|
||||||
parent is not None and parent.type == syms.suite and leaf.type == token.NEWLINE
|
parent is not None and parent.type == syms.suite and leaf.type == token.NEWLINE
|
||||||
):
|
):
|
||||||
@ -333,7 +377,6 @@ def _generate_ignored_nodes_from_fmt_skip(
|
|||||||
# statements. The ignored nodes should be previous siblings of the
|
# statements. The ignored nodes should be previous siblings of the
|
||||||
# parent suite node.
|
# parent suite node.
|
||||||
leaf.prefix = ""
|
leaf.prefix = ""
|
||||||
ignored_nodes: list[LN] = []
|
|
||||||
parent_sibling = parent.prev_sibling
|
parent_sibling = parent.prev_sibling
|
||||||
while parent_sibling is not None and parent_sibling.type != syms.suite:
|
while parent_sibling is not None and parent_sibling.type != syms.suite:
|
||||||
ignored_nodes.insert(0, parent_sibling)
|
ignored_nodes.insert(0, parent_sibling)
|
||||||
|
@ -203,6 +203,7 @@ class Preview(Enum):
|
|||||||
wrap_long_dict_values_in_parens = auto()
|
wrap_long_dict_values_in_parens = auto()
|
||||||
multiline_string_handling = auto()
|
multiline_string_handling = auto()
|
||||||
always_one_newline_after_import = auto()
|
always_one_newline_after_import = auto()
|
||||||
|
fix_fmt_skip_in_one_liners = auto()
|
||||||
|
|
||||||
|
|
||||||
UNSTABLE_FEATURES: set[Preview] = {
|
UNSTABLE_FEATURES: set[Preview] = {
|
||||||
|
@ -83,7 +83,8 @@
|
|||||||
"hug_parens_with_braces_and_square_brackets",
|
"hug_parens_with_braces_and_square_brackets",
|
||||||
"wrap_long_dict_values_in_parens",
|
"wrap_long_dict_values_in_parens",
|
||||||
"multiline_string_handling",
|
"multiline_string_handling",
|
||||||
"always_one_newline_after_import"
|
"always_one_newline_after_import",
|
||||||
|
"fix_fmt_skip_in_one_liners"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"description": "Enable specific features included in the `--unstable` style. Requires `--preview`. No compatibility guarantees are provided on the behavior or existence of any unstable features."
|
"description": "Enable specific features included in the `--unstable` style. Requires `--preview`. No compatibility guarantees are provided on the behavior or existence of any unstable features."
|
||||||
|
9
tests/data/cases/fmtskip10.py
Normal file
9
tests/data/cases/fmtskip10.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# flags: --preview
|
||||||
|
def foo(): return "mock" # fmt: skip
|
||||||
|
if True: print("yay") # fmt: skip
|
||||||
|
for i in range(10): print(i) # fmt: skip
|
||||||
|
|
||||||
|
j = 1 # fmt: skip
|
||||||
|
while j < 10: j += 1 # fmt: skip
|
||||||
|
|
||||||
|
b = [c for c in "A very long string that would normally generate some kind of collapse, since it is this long"] # fmt: skip
|
Loading…
Reference in New Issue
Block a user