dont skip formatting #%% (#2919)

Fixes #2588
This commit is contained in:
Marco Edward Gorelli 2022-03-21 21:51:07 +00:00 committed by GitHub
parent fa7f01592b
commit f87df0e3c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 57 additions and 27 deletions

View File

@ -14,6 +14,8 @@
<!-- Changes that affect Black's preview style --> <!-- Changes that affect Black's preview style -->
- Code cell separators `#%%` are now standardised to `# %%` (#2919)
### _Blackd_ ### _Blackd_
<!-- Changes to blackd --> <!-- Changes to blackd -->

View File

@ -1166,7 +1166,7 @@ def _format_str_once(src_contents: str, *, mode: Mode) -> str:
else: else:
versions = detect_target_versions(src_node, future_imports=future_imports) versions = detect_target_versions(src_node, future_imports=future_imports)
normalize_fmt_off(src_node) normalize_fmt_off(src_node, preview=mode.preview)
lines = LineGenerator(mode=mode) lines = LineGenerator(mode=mode)
elt = EmptyLineTracker(is_pyi=mode.is_pyi) elt = EmptyLineTracker(is_pyi=mode.is_pyi)
empty_line = Line(mode=mode) empty_line = Line(mode=mode)

View File

@ -23,6 +23,8 @@
FMT_PASS: Final = {*FMT_OFF, *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 = {True: " !:#'", False: " !:#'%"}
@dataclass @dataclass
class ProtoComment: class ProtoComment:
@ -42,7 +44,7 @@ class ProtoComment:
consumed: int # how many characters of the original leaf's prefix did we consume consumed: int # how many characters of the original leaf's prefix did we consume
def generate_comments(leaf: LN) -> Iterator[Leaf]: def generate_comments(leaf: LN, *, preview: bool) -> Iterator[Leaf]:
"""Clean the prefix of the `leaf` and generate comments from it, if any. """Clean the prefix of the `leaf` and generate comments from it, if any.
Comments in lib2to3 are shoved into the whitespace prefix. This happens Comments in lib2to3 are shoved into the whitespace prefix. This happens
@ -61,12 +63,16 @@ def generate_comments(leaf: LN) -> Iterator[Leaf]:
Inline comments are emitted as regular token.COMMENT leaves. Standalone Inline comments are emitted as regular token.COMMENT leaves. Standalone
are emitted with a fake STANDALONE_COMMENT token identifier. are emitted with a fake STANDALONE_COMMENT token identifier.
""" """
for pc in list_comments(leaf.prefix, is_endmarker=leaf.type == token.ENDMARKER): for pc in list_comments(
leaf.prefix, is_endmarker=leaf.type == token.ENDMARKER, preview=preview
):
yield Leaf(pc.type, pc.value, prefix="\n" * pc.newlines) yield Leaf(pc.type, pc.value, prefix="\n" * pc.newlines)
@lru_cache(maxsize=4096) @lru_cache(maxsize=4096)
def list_comments(prefix: str, *, is_endmarker: bool) -> List[ProtoComment]: def list_comments(
prefix: str, *, is_endmarker: bool, preview: bool
) -> List[ProtoComment]:
"""Return a list of :class:`ProtoComment` objects parsed from the given `prefix`.""" """Return a list of :class:`ProtoComment` objects parsed from the given `prefix`."""
result: List[ProtoComment] = [] result: List[ProtoComment] = []
if not prefix or "#" not in prefix: if not prefix or "#" not in prefix:
@ -92,7 +98,7 @@ def list_comments(prefix: str, *, is_endmarker: bool) -> List[ProtoComment]:
comment_type = token.COMMENT # simple trailing comment comment_type = token.COMMENT # simple trailing comment
else: else:
comment_type = STANDALONE_COMMENT comment_type = STANDALONE_COMMENT
comment = make_comment(line) comment = make_comment(line, preview=preview)
result.append( result.append(
ProtoComment( ProtoComment(
type=comment_type, value=comment, newlines=nlines, consumed=consumed type=comment_type, value=comment, newlines=nlines, consumed=consumed
@ -102,10 +108,10 @@ def list_comments(prefix: str, *, is_endmarker: bool) -> List[ProtoComment]:
return result return result
def make_comment(content: str) -> str: def make_comment(content: str, *, preview: bool) -> str:
"""Return a consistently formatted comment from the given `content` string. """Return a consistently formatted comment from the given `content` string.
All comments (except for "##", "#!", "#:", '#'", "#%%") should have a single All comments (except for "##", "#!", "#:", '#'") should have a single
space between the hash sign and the content. space between the hash sign and the content.
If `content` didn't start with a hash sign, one is provided. If `content` didn't start with a hash sign, one is provided.
@ -123,26 +129,26 @@ def make_comment(content: str) -> str:
and not content.lstrip().startswith("type:") and not content.lstrip().startswith("type:")
): ):
content = " " + content[1:] # Replace NBSP by a simple space content = " " + content[1:] # Replace NBSP by a simple space
if content and content[0] not in " !:#'%": if content and content[0] not in COMMENT_EXCEPTIONS[preview]:
content = " " + content content = " " + content
return "#" + content return "#" + content
def normalize_fmt_off(node: Node) -> None: def normalize_fmt_off(node: Node, *, preview: bool) -> 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, preview=preview)
def convert_one_fmt_off_pair(node: Node) -> bool: def convert_one_fmt_off_pair(node: Node, *, preview: bool) -> 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.
""" """
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, preview=preview):
if comment.value not in FMT_PASS: if comment.value not in FMT_PASS:
previous_consumed = comment.consumed previous_consumed = comment.consumed
continue continue
@ -157,7 +163,7 @@ def convert_one_fmt_off_pair(node: Node) -> bool:
if comment.value in FMT_SKIP and prev.type in WHITESPACE: if comment.value in FMT_SKIP and prev.type in WHITESPACE:
continue continue
ignored_nodes = list(generate_ignored_nodes(leaf, comment)) ignored_nodes = list(generate_ignored_nodes(leaf, comment, preview=preview))
if not ignored_nodes: if not ignored_nodes:
continue continue
@ -197,7 +203,9 @@ 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, *, preview: bool
) -> 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.
@ -221,13 +229,13 @@ def generate_ignored_nodes(leaf: Leaf, comment: ProtoComment) -> Iterator[LN]:
yield leaf.parent yield leaf.parent
return return
while container is not None and container.type != token.ENDMARKER: while container is not None and container.type != token.ENDMARKER:
if is_fmt_on(container): if is_fmt_on(container, preview=preview):
return return
# fix for fmt: on in children # fix for fmt: on in children
if contains_fmt_on_at_column(container, leaf.column): if contains_fmt_on_at_column(container, leaf.column, preview=preview):
for child in container.children: for child in container.children:
if contains_fmt_on_at_column(child, leaf.column): if contains_fmt_on_at_column(child, leaf.column, preview=preview):
return return
yield child yield child
else: else:
@ -235,12 +243,12 @@ def generate_ignored_nodes(leaf: Leaf, comment: ProtoComment) -> Iterator[LN]:
container = container.next_sibling container = container.next_sibling
def is_fmt_on(container: LN) -> bool: def is_fmt_on(container: LN, preview: bool) -> bool:
"""Determine whether formatting is switched on within a container. """Determine whether formatting is switched on within a container.
Determined by whether the last `# fmt:` comment is `on` or `off`. Determined by whether the last `# fmt:` comment is `on` or `off`.
""" """
fmt_on = False fmt_on = False
for comment in list_comments(container.prefix, is_endmarker=False): for comment in list_comments(container.prefix, is_endmarker=False, preview=preview):
if comment.value in FMT_ON: if comment.value in FMT_ON:
fmt_on = True fmt_on = True
elif comment.value in FMT_OFF: elif comment.value in FMT_OFF:
@ -248,7 +256,7 @@ def is_fmt_on(container: LN) -> bool:
return fmt_on return fmt_on
def contains_fmt_on_at_column(container: LN, column: int) -> bool: def contains_fmt_on_at_column(container: LN, column: int, *, preview: bool) -> bool:
"""Determine if children at a given column have formatting switched on.""" """Determine if children at a given column have formatting switched on."""
for child in container.children: for child in container.children:
if ( if (
@ -257,7 +265,7 @@ def contains_fmt_on_at_column(container: LN, column: int) -> bool:
or isinstance(child, Leaf) or isinstance(child, Leaf)
and child.column == column and child.column == column
): ):
if is_fmt_on(child): if is_fmt_on(child, preview=preview):
return True return True
return False return False

View File

@ -72,7 +72,7 @@ def visit_default(self, node: LN) -> Iterator[Line]:
"""Default `visit_*()` implementation. Recurses to children of `node`.""" """Default `visit_*()` implementation. Recurses to children of `node`."""
if isinstance(node, Leaf): if isinstance(node, Leaf):
any_open_brackets = self.current_line.bracket_tracker.any_open_brackets() any_open_brackets = self.current_line.bracket_tracker.any_open_brackets()
for comment in generate_comments(node): for comment in generate_comments(node, preview=self.mode.preview):
if any_open_brackets: if any_open_brackets:
# any comment within brackets is subject to splitting # any comment within brackets is subject to splitting
self.current_line.append(comment) self.current_line.append(comment)
@ -132,7 +132,7 @@ def visit_stmt(
`parens` holds a set of string leaf values immediately after which `parens` holds a set of string leaf values immediately after which
invisible parens should be put. invisible parens should be put.
""" """
normalize_invisible_parens(node, parens_after=parens) normalize_invisible_parens(node, parens_after=parens, preview=self.mode.preview)
for child in node.children: for child in node.children:
if is_name_token(child) and child.value in keywords: if is_name_token(child) and child.value in keywords:
yield from self.line() yield from self.line()
@ -141,7 +141,7 @@ def visit_stmt(
def visit_match_case(self, node: Node) -> Iterator[Line]: def visit_match_case(self, node: Node) -> Iterator[Line]:
"""Visit either a match or case statement.""" """Visit either a match or case statement."""
normalize_invisible_parens(node, parens_after=set()) normalize_invisible_parens(node, parens_after=set(), preview=self.mode.preview)
yield from self.line() yield from self.line()
for child in node.children: for child in node.children:
@ -802,7 +802,9 @@ def normalize_prefix(leaf: Leaf, *, inside_brackets: bool) -> None:
leaf.prefix = "" leaf.prefix = ""
def normalize_invisible_parens(node: Node, parens_after: Set[str]) -> None: def normalize_invisible_parens(
node: Node, parens_after: Set[str], *, preview: bool
) -> None:
"""Make existing optional parentheses invisible or create new ones. """Make existing optional parentheses invisible or create new ones.
`parens_after` is a set of string leaf values immediately after which parens `parens_after` is a set of string leaf values immediately after which parens
@ -811,7 +813,7 @@ def normalize_invisible_parens(node: Node, parens_after: Set[str]) -> None:
Standardizes on visible parentheses for single-element tuples, and keeps Standardizes on visible parentheses for single-element tuples, and keeps
existing visible parentheses for other tuples and generator expressions. existing visible parentheses for other tuples and generator expressions.
""" """
for pc in list_comments(node.prefix, is_endmarker=False): for pc in list_comments(node.prefix, is_endmarker=False, preview=preview):
if pc.value in FMT_OFF: if pc.value in FMT_OFF:
# This `node` has a prefix with `# fmt: off`, don't mess with parens. # This `node` has a prefix with `# fmt: off`, don't mess with parens.
return return
@ -820,7 +822,9 @@ def normalize_invisible_parens(node: Node, parens_after: Set[str]) -> None:
# Fixes a bug where invisible parens are not properly stripped from # Fixes a bug where invisible parens are not properly stripped from
# assignment statements that contain type annotations. # assignment statements that contain type annotations.
if isinstance(child, Node) and child.type == syms.annassign: if isinstance(child, Node) and child.type == syms.annassign:
normalize_invisible_parens(child, parens_after=parens_after) normalize_invisible_parens(
child, parens_after=parens_after, preview=preview
)
# Add parentheses around long tuple unpacking in assignments. # Add parentheses around long tuple unpacking in assignments.
if ( if (

15
tests/data/comments8.py Normal file
View File

@ -0,0 +1,15 @@
# The percent-percent comments are Spyder IDE cells.
# Both `#%%`` and `# %%` are accepted, so `black` standardises
# to the latter.
#%%
# %%
# output
# The percent-percent comments are Spyder IDE cells.
# Both `#%%`` and `# %%` are accepted, so `black` standardises
# to the latter.
# %%
# %%

View File

@ -75,6 +75,7 @@
# string processing # string processing
"cantfit", "cantfit",
"comments7", "comments7",
"comments8",
"long_strings", "long_strings",
"long_strings__edge_case", "long_strings__edge_case",
"long_strings__regression", "long_strings__regression",