diff --git a/CHANGES.md b/CHANGES.md index e9ffd6bb9f5..f17cd7fdc9d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,8 @@ ### 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 diff --git a/src/black/__init__.py b/src/black/__init__.py index b33beeeeb23..04f6d8c58de 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -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) diff --git a/src/black/comments.py b/src/black/comments.py index 8a0e925fdc0..25413121199 100644 --- a/src/black/comments.py +++ b/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 ( @@ -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. @@ -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 diff --git a/tests/data/cases/line_ranges_fmt_off_decorator.py b/tests/data/cases/line_ranges_fmt_off_decorator.py index 14aa1dda02d..065bf4328d7 100644 --- a/tests/data/cases/line_ranges_fmt_off_decorator.py +++ b/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. @@ -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. @@ -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 +