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

dont skip formatting #%% #2919

Merged
merged 6 commits into from Mar 21, 2022
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
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -14,6 +14,8 @@

<!-- Changes that affect Black's preview style -->

- Code cell separators `#%%` are now standardised to `# %%` (#2919)

### _Blackd_

<!-- Changes to blackd -->
Expand Down
2 changes: 1 addition & 1 deletion src/black/__init__.py
Expand Up @@ -1166,7 +1166,7 @@ def _format_str_once(src_contents: str, *, mode: Mode) -> str:
else:
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)
elt = EmptyLineTracker(is_pyi=mode.is_pyi)
empty_line = Line(mode=mode)
Expand Down
48 changes: 28 additions & 20 deletions src/black/comments.py
Expand Up @@ -23,6 +23,8 @@
FMT_PASS: Final = {*FMT_OFF, *FMT_SKIP}
FMT_ON: Final = {"# fmt: on", "# fmt:on", "# yapf: enable"}

COMMENT_EXCEPTIONS = {True: " !:#'", False: " !:#'%"}


@dataclass
class ProtoComment:
Expand All @@ -42,7 +44,7 @@ class ProtoComment:
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.

Comments in lib2to3 are shoved into the whitespace prefix. This happens
Expand All @@ -61,12 +63,16 @@ def generate_comments(leaf: LN) -> Iterator[Leaf]:
Inline comments are emitted as regular token.COMMENT leaves. Standalone
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)


@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`."""
result: List[ProtoComment] = []
if not prefix or "#" not in prefix:
Expand All @@ -92,7 +98,7 @@ def list_comments(prefix: str, *, is_endmarker: bool) -> List[ProtoComment]:
comment_type = token.COMMENT # simple trailing comment
else:
comment_type = STANDALONE_COMMENT
comment = make_comment(line)
comment = make_comment(line, preview=preview)
result.append(
ProtoComment(
type=comment_type, value=comment, newlines=nlines, consumed=consumed
Expand All @@ -102,10 +108,10 @@ def list_comments(prefix: str, *, is_endmarker: bool) -> List[ProtoComment]:
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.

All comments (except for "##", "#!", "#:", '#'", "#%%") should have a single
All comments (except for "##", "#!", "#:", '#'") should have a single
space between the hash sign and the content.

If `content` didn't start with a hash sign, one is provided.
Expand All @@ -123,26 +129,26 @@ def make_comment(content: str) -> str:
and not content.lstrip().startswith("type:")
):
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
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."""
try_again = True
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.

Returns True if a pair was converted.
"""
for leaf in node.leaves():
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:
previous_consumed = comment.consumed
continue
Expand All @@ -157,7 +163,7 @@ def convert_one_fmt_off_pair(node: Node) -> bool:
if comment.value in FMT_SKIP and prev.type in WHITESPACE:
continue

ignored_nodes = list(generate_ignored_nodes(leaf, comment))
ignored_nodes = list(generate_ignored_nodes(leaf, comment, preview=preview))
if not ignored_nodes:
continue

Expand Down Expand Up @@ -197,7 +203,9 @@ def convert_one_fmt_off_pair(node: Node) -> bool:
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`.

If comment is skip, returns leaf only.
Expand All @@ -221,34 +229,34 @@ def generate_ignored_nodes(leaf: Leaf, comment: ProtoComment) -> Iterator[LN]:
yield leaf.parent
return
while container is not None and container.type != token.ENDMARKER:
if is_fmt_on(container):
if is_fmt_on(container, preview=preview):
return

# 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:
if contains_fmt_on_at_column(child, leaf.column):
if contains_fmt_on_at_column(child, leaf.column, preview=preview):
return
yield child
else:
yield container
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.
Determined by whether the last `# fmt:` comment is `on` or `off`.
"""
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:
fmt_on = True
elif comment.value in FMT_OFF:
fmt_on = False
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."""
for child in container.children:
if (
Expand All @@ -257,7 +265,7 @@ def contains_fmt_on_at_column(container: LN, column: int) -> bool:
or isinstance(child, Leaf)
and child.column == column
):
if is_fmt_on(child):
if is_fmt_on(child, preview=preview):
return True

return False
Expand Down
16 changes: 10 additions & 6 deletions src/black/linegen.py
Expand Up @@ -72,7 +72,7 @@ def visit_default(self, node: LN) -> Iterator[Line]:
"""Default `visit_*()` implementation. Recurses to children of `node`."""
if isinstance(node, Leaf):
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:
# any comment within brackets is subject to splitting
self.current_line.append(comment)
Expand Down Expand Up @@ -132,7 +132,7 @@ def visit_stmt(
`parens` holds a set of string leaf values immediately after which
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:
if is_name_token(child) and child.value in keywords:
yield from self.line()
Expand All @@ -141,7 +141,7 @@ def visit_stmt(

def visit_match_case(self, node: Node) -> Iterator[Line]:
"""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()
for child in node.children:
Expand Down Expand Up @@ -802,7 +802,9 @@ def normalize_prefix(leaf: Leaf, *, inside_brackets: bool) -> None:
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.

`parens_after` is a set of string leaf values immediately after which parens
Expand All @@ -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
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:
# This `node` has a prefix with `# fmt: off`, don't mess with parens.
return
Expand All @@ -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
# assignment statements that contain type annotations.
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.
if (
Expand Down
15 changes: 15 additions & 0 deletions tests/data/comments8.py
@@ -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.

# %%
# %%
1 change: 1 addition & 0 deletions tests/test_format.py
Expand Up @@ -75,6 +75,7 @@
# string processing
"cantfit",
"comments7",
"comments8",
"long_strings",
"long_strings__edge_case",
"long_strings__regression",
Expand Down