Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

- Fix bug where comments preceding `# fmt: off`/`# fmt: on` blocks were incorrectly
removed, particularly affecting Jupytext's `# %% [markdown]` comments (#4845)
- Fix possible crash when `fmt: ` directives aren't on the top level (#4856)

### Preview style

Expand Down
26 changes: 15 additions & 11 deletions src/black/comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def make_comment(content: str, mode: Mode) -> str:
return "#"

# Preserve comments with fmt directives exactly as-is
if content.startswith("#") and _contains_fmt_directive(content):
if content.startswith("#") and contains_fmt_directive(content):
return content

if content[0] == "#":
Expand Down Expand Up @@ -205,8 +205,8 @@ def _should_process_fmt_comment(

Returns (should_process, is_fmt_off, is_fmt_skip).
"""
is_fmt_off = _contains_fmt_directive(comment.value, FMT_OFF)
is_fmt_skip = _contains_fmt_directive(comment.value, FMT_SKIP)
is_fmt_off = contains_fmt_directive(comment.value, FMT_OFF)
is_fmt_skip = contains_fmt_directive(comment.value, FMT_SKIP)

if not is_fmt_off and not is_fmt_skip:
return False, False, False
Expand Down Expand Up @@ -258,9 +258,13 @@ def _handle_comment_only_fmt_block(
fmt_off_idx = None
fmt_on_idx = None
for idx, c in enumerate(all_comments):
if fmt_off_idx is None and c.value in FMT_OFF:
if fmt_off_idx is None and contains_fmt_directive(c.value, FMT_OFF):
fmt_off_idx = idx
if fmt_off_idx is not None and idx > fmt_off_idx and c.value in FMT_ON:
if (
fmt_off_idx is not None
and idx > fmt_off_idx
and contains_fmt_directive(c.value, FMT_ON)
):
fmt_on_idx = idx
break

Expand Down Expand Up @@ -401,7 +405,7 @@ def _handle_regular_fmt_block(
parent = first.parent
prefix = first.prefix

if comment.value in FMT_OFF:
if contains_fmt_directive(comment.value, FMT_OFF):
first.prefix = prefix[comment.consumed :]
if is_fmt_skip:
first.prefix = ""
Expand All @@ -412,7 +416,7 @@ def _handle_regular_fmt_block(
hidden_value = "".join(str(n) for n in ignored_nodes)
comment_lineno = leaf.lineno - comment.newlines

if comment.value in FMT_OFF:
if contains_fmt_directive(comment.value, FMT_OFF):
fmt_off_prefix = ""
if len(lines) > 0 and not any(
line[0] <= comment_lineno <= line[1] for line in lines
Expand Down Expand Up @@ -460,7 +464,7 @@ def generate_ignored_nodes(
If comment is skip, returns leaf only.
Stops at the end of the block.
"""
if _contains_fmt_directive(comment.value, FMT_SKIP):
if contains_fmt_directive(comment.value, FMT_SKIP):
yield from _generate_ignored_nodes_from_fmt_skip(leaf, comment, mode)
return
container: LN | None = container_of(leaf)
Expand Down Expand Up @@ -703,9 +707,9 @@ def is_fmt_on(container: LN, mode: Mode) -> bool:
"""
fmt_on = False
for comment in list_comments(container.prefix, is_endmarker=False, mode=mode):
if comment.value in FMT_ON:
if contains_fmt_directive(comment.value, FMT_ON):
fmt_on = True
elif comment.value in FMT_OFF:
elif contains_fmt_directive(comment.value, FMT_OFF):
fmt_on = False
return fmt_on

Expand Down Expand Up @@ -734,7 +738,7 @@ def contains_pragma_comment(comment_list: list[Leaf]) -> bool:
return False


def _contains_fmt_directive(
def contains_fmt_directive(
comment_line: str, directives: set[str] = FMT_OFF | FMT_ON | FMT_SKIP
) -> bool:
"""
Expand Down
29 changes: 12 additions & 17 deletions src/black/linegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from black.comments import (
FMT_OFF,
FMT_ON,
_contains_fmt_directive,
contains_fmt_directive,
generate_comments,
list_comments,
)
Expand Down Expand Up @@ -387,7 +387,8 @@ def visit_ENDMARKER(self, leaf: Leaf) -> Iterator[Line]:
yield from self.line()

def visit_STANDALONE_COMMENT(self, leaf: Leaf) -> Iterator[Line]:
if not self.current_line.bracket_tracker.any_open_brackets():
any_open_brackets = self.current_line.bracket_tracker.any_open_brackets()
if not any_open_brackets:
yield from self.line()
# STANDALONE_COMMENT nodes created by our special handling in
# normalize_fmt_off for comment-only blocks have fmt:off as the first
Expand All @@ -398,18 +399,11 @@ def visit_STANDALONE_COMMENT(self, leaf: Leaf) -> Iterator[Line]:
# visit_default.
value = leaf.value
lines = value.splitlines()
if len(lines) >= 2:
# Check if first line (after stripping whitespace) is exactly a
# fmt:off directive
first_line = lines[0].lstrip()
first_is_fmt_off = first_line in FMT_OFF
# Check if last line (after stripping whitespace) is exactly a
# fmt:on directive
last_line = lines[-1].lstrip()
last_is_fmt_on = last_line in FMT_ON
is_fmt_off_block = first_is_fmt_off and last_is_fmt_on
else:
is_fmt_off_block = False
is_fmt_off_block = (
len(lines) >= 2
and contains_fmt_directive(lines[0], FMT_OFF)
and contains_fmt_directive(lines[-1], FMT_ON)
)
if is_fmt_off_block:
# This is a fmt:off/on block from normalize_fmt_off - we still need
# to process any prefix comments (like markdown comments) but append
Expand All @@ -418,7 +412,7 @@ def visit_STANDALONE_COMMENT(self, leaf: Leaf) -> Iterator[Line]:
# Only process prefix comments if there actually is a prefix with comments
if leaf.prefix and any(
line.strip().startswith("#")
and not _contains_fmt_directive(line.strip())
and not contains_fmt_directive(line.strip())
for line in leaf.prefix.split("\n")
):
for comment in generate_comments(leaf, mode=self.mode):
Expand All @@ -429,7 +423,8 @@ def visit_STANDALONE_COMMENT(self, leaf: Leaf) -> Iterator[Line]:
leaf.prefix = ""

self.current_line.append(leaf)
yield from self.line()
if not any_open_brackets:
yield from self.line()
else:
# Normal standalone comment - process through visit_default
yield from self.visit_default(leaf)
Expand Down Expand Up @@ -1484,7 +1479,7 @@ def normalize_invisible_parens( # noqa: C901
existing visible parentheses for other tuples and generator expressions.
"""
for pc in list_comments(node.prefix, is_endmarker=False, mode=mode):
if pc.value in FMT_OFF:
if contains_fmt_directive(pc.value, FMT_OFF):
# This `node` has a prefix with `# fmt: off`, don't mess with parens.
return

Expand Down
82 changes: 82 additions & 0 deletions tests/data/cases/fmtskip11.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,85 @@ def foo():

# comment 1 # fmt: skip
# comment 2

[
(1, 2),
# # fmt: off
# (3,
# 4),
# # fmt: on
(5, 6),
]

[
(1, 2),
# # fmt: off
# (3,
# 4),
# fmt: on
(5, 6),
]


[
(1, 2),
# fmt: off
# (3,
# 4),
# # fmt: on
(5, 6),
]


[
(1, 2),
# fmt: off
# (3,
# 4),
# fmt: on
(5, 6),
]

[
(1, 2),
# # fmt: off
(3,
4),
# # fmt: on
(5, 6),
]

[
(1, 2),
# # fmt: off
(3,
4),
# fmt: on
(5, 6),
]


[
(1, 2),
# fmt: off
(3,
4),
# # fmt: on
(5, 6),
]


[
(1, 2),
# fmt: off
(3,
4),
# fmt: on
(5, 6),
]


if False:
# fmt: off # some other comment
pass