Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[4068] Fix: --line-ranges dedents a # fmt: off in the middle of a decorator #4084

Merged
merged 5 commits into from Dec 4, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGES.md
Expand Up @@ -8,7 +8,8 @@

### Stable style

<!-- Changes that affect Black's stable style -->
- Fix bug where `# fmt: off` automatically dedents when used with the `--line-ranges`
option, even when it is not within the specified line range. (#4084)

### Preview style

Expand Down
2 changes: 1 addition & 1 deletion src/black/__init__.py
Expand Up @@ -1180,7 +1180,7 @@ def _format_str_once(
for feature in {Feature.PARENTHESIZED_CONTEXT_MANAGERS}
if supports_feature(versions, feature)
}
normalize_fmt_off(src_node, mode)
normalize_fmt_off(src_node, mode, lines)
if lines:
# This should be called after normalize_fmt_off.
convert_unchanged_lines(src_node, lines)
Expand Down
23 changes: 19 additions & 4 deletions src/black/comments.py
@@ -1,7 +1,7 @@
import re
from dataclasses import dataclass
from functools import lru_cache
from typing import Final, Iterator, List, Optional, Union
from typing import Collection, Final, Iterator, List, Optional, Tuple, Union

from black.mode import Mode, Preview
from black.nodes import (
Expand Down Expand Up @@ -161,14 +161,18 @@ def make_comment(content: str) -> str:
return "#" + content


def normalize_fmt_off(node: Node, mode: Mode) -> None:
def normalize_fmt_off(
node: Node, mode: Mode, lines: Collection[Tuple[int, int]]
) -> None:
"""Convert content between `# fmt: off`/`# fmt: on` into standalone comments."""
try_again = True
while try_again:
try_again = convert_one_fmt_off_pair(node, mode)
try_again = convert_one_fmt_off_pair(node, mode, lines)


def convert_one_fmt_off_pair(node: Node, mode: Mode) -> bool:
def convert_one_fmt_off_pair(
node: Node, mode: Mode, lines: Collection[Tuple[int, int]]
) -> bool:
"""Convert content of a single `# fmt: off`/`# fmt: on` into a standalone comment.
Returns True if a pair was converted.
Expand Down Expand Up @@ -213,7 +217,18 @@ def convert_one_fmt_off_pair(node: Node, mode: Mode) -> bool:
prefix[:previous_consumed] + "\n" * comment.newlines
)
hidden_value = "".join(str(n) for n in ignored_nodes)
comment_lineno = leaf.lineno - comment.newlines
if comment.value in FMT_OFF:
fmt_off_prefix = ""
if len(lines) > 0 and not any(
comment_lineno >= line[0] and comment_lineno <= line[1]
for line in lines
):
# keeping indentation of comment by preserving original whitespaces.
fmt_off_prefix = prefix.split(comment.value)[0]
if "\n" in fmt_off_prefix:
fmt_off_prefix = fmt_off_prefix.split("\n")[-1]
standalone_comment_prefix += fmt_off_prefix
hidden_value = comment.value + "\n" + hidden_value
if _contains_fmt_skip_comment(comment.value, mode):
hidden_value += " " + comment.value
Expand Down
24 changes: 22 additions & 2 deletions tests/data/cases/line_ranges_fmt_off_decorator.py
@@ -1,4 +1,4 @@
# flags: --line-ranges=12-12
# flags: --line-ranges=12-12 --line-ranges=21-21
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines.

Expand All @@ -11,9 +11,19 @@ class MyClass:
def method():
print ( "str" )

@decor(
a=1,
# fmt: off
b=(2, 3),
# fmt: on
)
def func():
pass


# output

# flags: --line-ranges=12-12
# flags: --line-ranges=12-12 --line-ranges=21-21
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines.

Expand All @@ -25,3 +35,13 @@ class MyClass:
# fmt: on
def method():
print("str")

@decor(
a=1,
# fmt: off
b=(2, 3),
# fmt: on
)
def func():
pass