Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

<!-- Changes that affect Black's stable style -->

- Fix `# fmt: skip` being ignored in nested `if` expressions with parenthesized `in`
clauses (#4903)
- Fix crash when an f-string follows a `# fmt: off` comment inside brackets (#5097)
- Add support for unpacking in comprehensions (PEP 798) and for lazy imports (PEP 810),
both new syntactic features in Python 3.15 (#5048)
Expand Down
48 changes: 44 additions & 4 deletions src/black/comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from black.mode import Mode
from black.nodes import (
CLOSING_BRACKETS,
OPENING_BRACKETS,
STANDALONE_COMMENT,
STATEMENT,
WHITESPACE,
Expand Down Expand Up @@ -448,6 +449,14 @@ def stringify_node(n: LN) -> str:

hidden_value = "".join(parts)
comment_lineno = leaf.lineno - comment.newlines
leaf_is_ignored = any(
ignored is leaf
or (
isinstance(ignored, Node)
and any(child is leaf for child in ignored.leaves())
)
for ignored in ignored_nodes
)

if contains_fmt_directive(comment.value, FMT_OFF):
fmt_off_prefix = ""
Expand All @@ -461,7 +470,7 @@ def stringify_node(n: LN) -> str:
standalone_comment_prefix += fmt_off_prefix
hidden_value = comment.value + "\n" + hidden_value

if is_fmt_skip:
if is_fmt_skip and not leaf_is_ignored:
hidden_value += comment.leading_whitespace + comment.value

if hidden_value.endswith("\n"):
Expand Down Expand Up @@ -630,6 +639,17 @@ def _get_compound_statement_header(
return header_leaves


def _find_closest_previous_sibling(node: LN) -> LN | None:
"""Find the closest previous sibling by walking up the ancestor chain."""
current: LN | None = node
while current is not None:
prev_sibling = current.prev_sibling
if prev_sibling is not None:
return prev_sibling
current = current.parent
return None


def _generate_ignored_nodes_from_fmt_skip(
leaf: Leaf, comment: ProtoComment, mode: Mode
) -> Iterator[LN]:
Expand All @@ -643,12 +663,13 @@ def _generate_ignored_nodes_from_fmt_skip(
if not comments or comment.value != comments[0].value:
return

if not prev_sibling and parent:
if prev_sibling is None and parent is not None:
prev_sibling = parent.prev_sibling

if prev_sibling is not None:
leaf.prefix = leaf.prefix[comment.consumed :]
if prev_sibling is None and comment.type == token.COMMENT:
prev_sibling = _find_closest_previous_sibling(leaf)

if prev_sibling is not None:
# Generates the nodes to be ignored by `fmt: skip`.

# Nodes to ignore are the ones on the same line as the
Expand All @@ -669,6 +690,14 @@ def _generate_ignored_nodes_from_fmt_skip(
# or NEWLINE leaves.

current_node = prev_sibling
if (
isinstance(current_node, Leaf)
and current_node.type in OPENING_BRACKETS
and current_node.parent
and current_node.parent.type == syms.atom
):
current_node = current_node.parent

ignored_nodes = [current_node]
if current_node.prev_sibling is None and current_node.parent is not None:
current_node = current_node.parent
Expand Down Expand Up @@ -734,6 +763,17 @@ def _generate_ignored_nodes_from_fmt_skip(
if header_nodes:
ignored_nodes = header_nodes + ignored_nodes

leaf_is_ignored = any(
ignored is leaf
or (
isinstance(ignored, Node)
and any(child is leaf for child in ignored.leaves())
)
for ignored in ignored_nodes
)
if not leaf_is_ignored:
leaf.prefix = leaf.prefix[comment.consumed :]

yield from ignored_nodes
elif (
parent is not None and parent.type == syms.suite and leaf.type == token.NEWLINE
Expand Down
43 changes: 43 additions & 0 deletions tests/data/cases/fmtskip_in_clause.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Single fmt: skip in multi-part if-clause
class ClassWithALongName:
Constant1 = 1
Constant2 = 2
Constant3 = 3


def test():
if (
"cond1" == "cond1"
and "cond2" == "cond2"
and 1 in ( # fmt: skip
ClassWithALongName.Constant1,
ClassWithALongName.Constant2,
ClassWithALongName.Constant3,
)
):
return True
return False


# output


# Single fmt: skip in multi-part if-clause
class ClassWithALongName:
Constant1 = 1
Constant2 = 2
Constant3 = 3


def test():
if (
"cond1" == "cond1"
and "cond2" == "cond2"
and 1 in ( # fmt: skip
ClassWithALongName.Constant1,
ClassWithALongName.Constant2,
ClassWithALongName.Constant3,
)
):
return True
return False
Loading