[2213] Add support for single line format skip with other comments on the same line (#3959)
This commit is contained in:
parent
1d4c31aa58
commit
878937bcc3
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
<!-- Changes to how Black can be configured -->
|
- Add support for single line format skip with other comments on the same line (#3959)
|
||||||
|
|
||||||
### Packaging
|
### Packaging
|
||||||
|
|
||||||
|
@ -8,12 +8,14 @@ deliberately limited and rarely added. Previous formatting is taken into account
|
|||||||
little as possible, with rare exceptions like the magic trailing comma. The coding style
|
little as possible, with rare exceptions like the magic trailing comma. The coding style
|
||||||
used by _Black_ can be viewed as a strict subset of PEP 8.
|
used by _Black_ can be viewed as a strict subset of PEP 8.
|
||||||
|
|
||||||
_Black_ reformats entire files in place. It doesn't reformat lines that end with
|
_Black_ reformats entire files in place. It doesn't reformat lines that contain
|
||||||
`# fmt: skip` or blocks that start with `# fmt: off` and end with `# fmt: on`.
|
`# fmt: skip` or blocks that start with `# fmt: off` and end with `# fmt: on`.
|
||||||
`# fmt: on/off` must be on the same level of indentation and in the same block, meaning
|
`# fmt: skip` can be mixed with other pragmas/comments either with multiple comments
|
||||||
no unindents beyond the initial indentation level between them. It also recognizes
|
(e.g. `# fmt: skip # pylint # noqa`) or as a semicolon separeted list (e.g.
|
||||||
[YAPF](https://github.com/google/yapf)'s block comments to the same effect, as a
|
`# fmt: skip; pylint; noqa`). `# fmt: on/off` must be on the same level of indentation
|
||||||
courtesy for straddling code.
|
and in the same block, meaning no unindents beyond the initial indentation level between
|
||||||
|
them. It also recognizes [YAPF](https://github.com/google/yapf)'s block comments to the
|
||||||
|
same effect, as a courtesy for straddling code.
|
||||||
|
|
||||||
The rest of this document describes the current formatting style. If you're interested
|
The rest of this document describes the current formatting style. If you're interested
|
||||||
in trying out where the style is heading, see [future style](./future_style.md) and try
|
in trying out where the style is heading, see [future style](./future_style.md) and try
|
||||||
|
@ -1099,7 +1099,7 @@ def _format_str_once(src_contents: str, *, mode: Mode) -> str:
|
|||||||
for feature in {Feature.PARENTHESIZED_CONTEXT_MANAGERS}
|
for feature in {Feature.PARENTHESIZED_CONTEXT_MANAGERS}
|
||||||
if supports_feature(versions, feature)
|
if supports_feature(versions, feature)
|
||||||
}
|
}
|
||||||
normalize_fmt_off(src_node)
|
normalize_fmt_off(src_node, mode)
|
||||||
lines = LineGenerator(mode=mode, features=context_manager_features)
|
lines = LineGenerator(mode=mode, features=context_manager_features)
|
||||||
elt = EmptyLineTracker(mode=mode)
|
elt = EmptyLineTracker(mode=mode)
|
||||||
split_line_features = {
|
split_line_features = {
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from typing import Final, Iterator, List, Optional, Union
|
from typing import Final, Iterator, List, Optional, Union
|
||||||
|
|
||||||
|
from black.mode import Mode, Preview
|
||||||
from black.nodes import (
|
from black.nodes import (
|
||||||
CLOSING_BRACKETS,
|
CLOSING_BRACKETS,
|
||||||
STANDALONE_COMMENT,
|
STANDALONE_COMMENT,
|
||||||
@ -20,10 +21,11 @@
|
|||||||
|
|
||||||
FMT_OFF: Final = {"# fmt: off", "# fmt:off", "# yapf: disable"}
|
FMT_OFF: Final = {"# fmt: off", "# fmt:off", "# yapf: disable"}
|
||||||
FMT_SKIP: Final = {"# fmt: skip", "# fmt:skip"}
|
FMT_SKIP: Final = {"# fmt: skip", "# fmt:skip"}
|
||||||
FMT_PASS: Final = {*FMT_OFF, *FMT_SKIP}
|
|
||||||
FMT_ON: Final = {"# fmt: on", "# fmt:on", "# yapf: enable"}
|
FMT_ON: Final = {"# fmt: on", "# fmt:on", "# yapf: enable"}
|
||||||
|
|
||||||
COMMENT_EXCEPTIONS = " !:#'"
|
COMMENT_EXCEPTIONS = " !:#'"
|
||||||
|
_COMMENT_PREFIX = "# "
|
||||||
|
_COMMENT_LIST_SEPARATOR = ";"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -130,14 +132,14 @@ def make_comment(content: str) -> str:
|
|||||||
return "#" + content
|
return "#" + content
|
||||||
|
|
||||||
|
|
||||||
def normalize_fmt_off(node: Node) -> None:
|
def normalize_fmt_off(node: Node, mode: Mode) -> None:
|
||||||
"""Convert content between `# fmt: off`/`# fmt: on` into standalone comments."""
|
"""Convert content between `# fmt: off`/`# fmt: on` into standalone comments."""
|
||||||
try_again = True
|
try_again = True
|
||||||
while try_again:
|
while try_again:
|
||||||
try_again = convert_one_fmt_off_pair(node)
|
try_again = convert_one_fmt_off_pair(node, mode)
|
||||||
|
|
||||||
|
|
||||||
def convert_one_fmt_off_pair(node: Node) -> bool:
|
def convert_one_fmt_off_pair(node: Node, mode: Mode) -> bool:
|
||||||
"""Convert content of a single `# fmt: off`/`# fmt: on` into a standalone comment.
|
"""Convert content of a single `# fmt: off`/`# fmt: on` into a standalone comment.
|
||||||
|
|
||||||
Returns True if a pair was converted.
|
Returns True if a pair was converted.
|
||||||
@ -145,21 +147,27 @@ def convert_one_fmt_off_pair(node: Node) -> bool:
|
|||||||
for leaf in node.leaves():
|
for leaf in node.leaves():
|
||||||
previous_consumed = 0
|
previous_consumed = 0
|
||||||
for comment in list_comments(leaf.prefix, is_endmarker=False):
|
for comment in list_comments(leaf.prefix, is_endmarker=False):
|
||||||
if comment.value not in FMT_PASS:
|
should_pass_fmt = comment.value in FMT_OFF or _contains_fmt_skip_comment(
|
||||||
|
comment.value, mode
|
||||||
|
)
|
||||||
|
if not should_pass_fmt:
|
||||||
previous_consumed = comment.consumed
|
previous_consumed = comment.consumed
|
||||||
continue
|
continue
|
||||||
# We only want standalone comments. If there's no previous leaf or
|
# We only want standalone comments. If there's no previous leaf or
|
||||||
# the previous leaf is indentation, it's a standalone comment in
|
# the previous leaf is indentation, it's a standalone comment in
|
||||||
# disguise.
|
# disguise.
|
||||||
if comment.value in FMT_PASS and comment.type != STANDALONE_COMMENT:
|
if should_pass_fmt and comment.type != STANDALONE_COMMENT:
|
||||||
prev = preceding_leaf(leaf)
|
prev = preceding_leaf(leaf)
|
||||||
if prev:
|
if prev:
|
||||||
if comment.value in FMT_OFF and prev.type not in WHITESPACE:
|
if comment.value in FMT_OFF and prev.type not in WHITESPACE:
|
||||||
continue
|
continue
|
||||||
if comment.value in FMT_SKIP and prev.type in WHITESPACE:
|
if (
|
||||||
|
_contains_fmt_skip_comment(comment.value, mode)
|
||||||
|
and prev.type in WHITESPACE
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
ignored_nodes = list(generate_ignored_nodes(leaf, comment))
|
ignored_nodes = list(generate_ignored_nodes(leaf, comment, mode))
|
||||||
if not ignored_nodes:
|
if not ignored_nodes:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -168,7 +176,7 @@ def convert_one_fmt_off_pair(node: Node) -> bool:
|
|||||||
prefix = first.prefix
|
prefix = first.prefix
|
||||||
if comment.value in FMT_OFF:
|
if comment.value in FMT_OFF:
|
||||||
first.prefix = prefix[comment.consumed :]
|
first.prefix = prefix[comment.consumed :]
|
||||||
if comment.value in FMT_SKIP:
|
if _contains_fmt_skip_comment(comment.value, mode):
|
||||||
first.prefix = ""
|
first.prefix = ""
|
||||||
standalone_comment_prefix = prefix
|
standalone_comment_prefix = prefix
|
||||||
else:
|
else:
|
||||||
@ -178,7 +186,7 @@ def convert_one_fmt_off_pair(node: Node) -> bool:
|
|||||||
hidden_value = "".join(str(n) for n in ignored_nodes)
|
hidden_value = "".join(str(n) for n in ignored_nodes)
|
||||||
if comment.value in FMT_OFF:
|
if comment.value in FMT_OFF:
|
||||||
hidden_value = comment.value + "\n" + hidden_value
|
hidden_value = comment.value + "\n" + hidden_value
|
||||||
if comment.value in FMT_SKIP:
|
if _contains_fmt_skip_comment(comment.value, mode):
|
||||||
hidden_value += " " + comment.value
|
hidden_value += " " + comment.value
|
||||||
if hidden_value.endswith("\n"):
|
if hidden_value.endswith("\n"):
|
||||||
# That happens when one of the `ignored_nodes` ended with a NEWLINE
|
# That happens when one of the `ignored_nodes` ended with a NEWLINE
|
||||||
@ -205,13 +213,15 @@ def convert_one_fmt_off_pair(node: Node) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def generate_ignored_nodes(leaf: Leaf, comment: ProtoComment) -> Iterator[LN]:
|
def generate_ignored_nodes(
|
||||||
|
leaf: Leaf, comment: ProtoComment, mode: Mode
|
||||||
|
) -> Iterator[LN]:
|
||||||
"""Starting from the container of `leaf`, generate all leaves until `# fmt: on`.
|
"""Starting from the container of `leaf`, generate all leaves until `# fmt: on`.
|
||||||
|
|
||||||
If comment is skip, returns leaf only.
|
If comment is skip, returns leaf only.
|
||||||
Stops at the end of the block.
|
Stops at the end of the block.
|
||||||
"""
|
"""
|
||||||
if comment.value in FMT_SKIP:
|
if _contains_fmt_skip_comment(comment.value, mode):
|
||||||
yield from _generate_ignored_nodes_from_fmt_skip(leaf, comment)
|
yield from _generate_ignored_nodes_from_fmt_skip(leaf, comment)
|
||||||
return
|
return
|
||||||
container: Optional[LN] = container_of(leaf)
|
container: Optional[LN] = container_of(leaf)
|
||||||
@ -327,3 +337,32 @@ def contains_pragma_comment(comment_list: List[Leaf]) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _contains_fmt_skip_comment(comment_line: str, mode: Mode) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if the given comment contains FMT_SKIP alone or paired with other comments.
|
||||||
|
Matching styles:
|
||||||
|
# fmt:skip <-- single comment
|
||||||
|
# noqa:XXX # fmt:skip # a nice line <-- multiple comments (Preview)
|
||||||
|
# pylint:XXX; fmt:skip <-- list of comments (; separated, Preview)
|
||||||
|
"""
|
||||||
|
semantic_comment_blocks = (
|
||||||
|
[
|
||||||
|
comment_line,
|
||||||
|
*[
|
||||||
|
_COMMENT_PREFIX + comment.strip()
|
||||||
|
for comment in comment_line.split(_COMMENT_PREFIX)[1:]
|
||||||
|
],
|
||||||
|
*[
|
||||||
|
_COMMENT_PREFIX + comment.strip()
|
||||||
|
for comment in comment_line.strip(_COMMENT_PREFIX).split(
|
||||||
|
_COMMENT_LIST_SEPARATOR
|
||||||
|
)
|
||||||
|
],
|
||||||
|
]
|
||||||
|
if Preview.single_line_format_skip_with_multiple_comments in mode
|
||||||
|
else [comment_line]
|
||||||
|
)
|
||||||
|
|
||||||
|
return any(comment in FMT_SKIP for comment in semantic_comment_blocks)
|
||||||
|
@ -192,6 +192,7 @@ class Preview(Enum):
|
|||||||
fix_power_op_line_length = auto()
|
fix_power_op_line_length = auto()
|
||||||
hug_parens_with_braces_and_square_brackets = auto()
|
hug_parens_with_braces_and_square_brackets = auto()
|
||||||
allow_empty_first_line_before_new_block_or_comment = auto()
|
allow_empty_first_line_before_new_block_or_comment = auto()
|
||||||
|
single_line_format_skip_with_multiple_comments = auto()
|
||||||
|
|
||||||
|
|
||||||
class Deprecated(UserWarning):
|
class Deprecated(UserWarning):
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
# flags: --preview
|
||||||
|
foo = 123 # fmt: skip # noqa: E501 # pylint
|
||||||
|
bar = (
|
||||||
|
123 ,
|
||||||
|
( 1 + 5 ) # pylint # fmt:skip
|
||||||
|
)
|
||||||
|
baz = "a" + "b" # pylint; fmt: skip; noqa: E501
|
||||||
|
skip_will_not_work = "a" + "b" # pylint fmt:skip
|
||||||
|
skip_will_not_work2 = "a" + "b" # some text; fmt:skip happens to be part of it
|
||||||
|
|
||||||
|
# output
|
||||||
|
|
||||||
|
foo = 123 # fmt: skip # noqa: E501 # pylint
|
||||||
|
bar = (
|
||||||
|
123 ,
|
||||||
|
( 1 + 5 ) # pylint # fmt:skip
|
||||||
|
)
|
||||||
|
baz = "a" + "b" # pylint; fmt: skip; noqa: E501
|
||||||
|
skip_will_not_work = "a" + "b" # pylint fmt:skip
|
||||||
|
skip_will_not_work2 = "a" + "b" # some text; fmt:skip happens to be part of it
|
Loading…
Reference in New Issue
Block a user