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 @@ -74,6 +74,8 @@
- Fix `fix_fmt_skip_in_one_liners` crashing on `with` statements (#4853)
- Fix `fix_fmt_skip_in_one_liners` crashing on annotated parameters (#4854)
- Fix new lines being added after imports with `# fmt: skip` on them (#4894)
- Add defensive checks to prevent `AttributeError` crashes when processing `# fmt: skip`
in nested expressions (#4903)

### Packaging

Expand Down
3 changes: 3 additions & 0 deletions src/black/comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,9 @@ def _generate_ignored_nodes_from_fmt_skip(
return

if Preview.fix_fmt_skip_in_one_liners in mode and not prev_sibling and parent:
# Simple one-level check: if the leaf has no prev_sibling,
# check parent's prev_sibling. This handles some nested cases
# without the complexity and indentation issues of deeper tree climbing
prev_sibling = parent.prev_sibling

if prev_sibling is not None:
Expand Down
31 changes: 20 additions & 11 deletions src/black/lines.py
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,10 @@ def is_line_short_enough(line: Line, *, mode: Mode, line_str: str = "") -> bool:
for i, leaf in enumerate(line.leaves):
if max_level_to_update == math.inf:
had_comma: int | None = None
# Skip multiline_string_handling logic for leaves without bracket_depth
# (e.g., newly created leaves not yet processed by bracket tracker)
if not hasattr(leaf, "bracket_depth"):
continue
if leaf.bracket_depth + 1 > len(commas):
commas.append(0)
elif leaf.bracket_depth + 1 < len(commas):
Expand All @@ -879,17 +883,22 @@ def is_line_short_enough(line: Line, *, mode: Mode, line_str: str = "") -> bool:
# MLS was in parens with at least one comma - force split
return False

if leaf.bracket_depth <= max_level_to_update and leaf.type == token.COMMA:
# Inside brackets, ignore trailing comma
# directly after MLS/MLS-containing expression
ignore_ctxs: list[LN | None] = [None]
ignore_ctxs += multiline_string_contexts
if (line.inside_brackets or leaf.bracket_depth > 0) and (
i != len(line.leaves) - 1 or leaf.prev_sibling not in ignore_ctxs
):
commas[leaf.bracket_depth] += 1
if max_level_to_update != math.inf:
max_level_to_update = min(max_level_to_update, leaf.bracket_depth)
# Skip bracket-depth-dependent processing for leaves without the attribute
if not hasattr(leaf, "bracket_depth"):
# Still process multiline string detection below
pass
else:
if leaf.bracket_depth <= max_level_to_update and leaf.type == token.COMMA:
# Inside brackets, ignore trailing comma
# directly after MLS/MLS-containing expression
ignore_ctxs: list[LN | None] = [None]
ignore_ctxs += multiline_string_contexts
if (line.inside_brackets or leaf.bracket_depth > 0) and (
i != len(line.leaves) - 1 or leaf.prev_sibling not in ignore_ctxs
):
commas[leaf.bracket_depth] += 1
if max_level_to_update != math.inf:
max_level_to_update = min(max_level_to_update, leaf.bracket_depth)

if is_multiline_string(leaf):
if leaf.parent and (
Expand Down
41 changes: 41 additions & 0 deletions tests/data/cases/fmtskip_nested_expression.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# flags: --preview
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

class ClassWithALongName:
Constant1 = 1
Constant2 = 2
Constant3 = 3


def test():
if (
"cond1" == "cond1"
and "cond2" == "cond2"
and 1
in ( # fmt: skip
Comment on lines +33 to +34
Copy link
Collaborator

@cobaltt7 cobaltt7 Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line shouldn't be formatted due to the fmt:skip (that's the bug in the original issue)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I will look into it

ClassWithALongName.Constant1,
ClassWithALongName.Constant2,
ClassWithALongName.Constant3,
)
):
return True
return False