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

Draft for Black 2023 stable style #3418

Merged
merged 39 commits into from Jan 31, 2023
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f58f446
Clean up comments.py preview mode
JelleZijlstra Dec 10, 2022
2caca04
Clean up preview in __init__.py
JelleZijlstra Dec 10, 2022
9a68676
clean up normalize_invisible_parens preview
JelleZijlstra Dec 10, 2022
00cd371
more in linegen.py
JelleZijlstra Dec 10, 2022
b6cd780
annotation_parens
JelleZijlstra Dec 10, 2022
714af8b
empty_lines_before_class_or_def_with_leading_comments
JelleZijlstra Dec 10, 2022
fe271ed
handle_trailing_commas_in_head
JelleZijlstra Dec 10, 2022
9237082
normalize_docstring_quotes_and_prefixes_properly (unused)
JelleZijlstra Dec 10, 2022
fc2acb2
one_element_subscript
JelleZijlstra Dec 10, 2022
ecfa7b6
remove_block_trailing_newline
JelleZijlstra Dec 10, 2022
11b0f1e
remove_redundant_parens
JelleZijlstra Dec 10, 2022
0152d51
skip_magic_trailing_comma_in_subscript
JelleZijlstra Dec 10, 2022
4329c9f
unused import
JelleZijlstra Dec 10, 2022
e44aa09
fix path in test
JelleZijlstra Dec 10, 2022
c4c7072
expression_skip_magic_trailing_comma.diff
JelleZijlstra Dec 10, 2022
7fbdc95
Revert "annotation_parens"
JelleZijlstra Dec 10, 2022
45fd512
Merge branch 'main' into black23
JelleZijlstra Dec 17, 2022
42624f4
fixup after merge
JelleZijlstra Dec 17, 2022
bce883b
enable long_docstring_quotes_on_newline
JelleZijlstra Dec 17, 2022
d0b42a7
put prefer_splitting_right_hand_side_of_assignments back
JelleZijlstra Dec 17, 2022
23840a5
Merge branch 'main' into black23
JelleZijlstra Dec 18, 2022
c5fc62d
add annotation_parens
JelleZijlstra Dec 18, 2022
0528f07
move some tests out of preview
JelleZijlstra Dec 18, 2022
4842546
fix a test
JelleZijlstra Dec 18, 2022
4acf335
Revert "enable long_docstring_quotes_on_newline"
JelleZijlstra Dec 18, 2022
d48f8ed
fix line-length 1 bug
JelleZijlstra Dec 18, 2022
387f3fb
fix tests
JelleZijlstra Dec 18, 2022
c9280bf
move more tests to non-preview
JelleZijlstra Dec 18, 2022
88957e5
changelog entry
JelleZijlstra Dec 18, 2022
a5bc399
delete preview_39/310
JelleZijlstra Dec 18, 2022
8aa39b6
fix changelog
JelleZijlstra Dec 18, 2022
5f95986
Merge remote-tracking branch 'upstream/main' into black23
JelleZijlstra Dec 23, 2022
70e2142
Merge branch 'main' into black23
JelleZijlstra Dec 29, 2022
7b3fd6a
Merge branch 'main' into black23
JelleZijlstra Jan 18, 2023
2b0aea9
Merge branch 'main' into black23
JelleZijlstra Jan 31, 2023
1be7214
fix up merge
JelleZijlstra Jan 31, 2023
2c0b333
now we do remove them
JelleZijlstra Jan 31, 2023
0da89c4
Merge remote-tracking branch 'upstream/main' into black23
JelleZijlstra Jan 31, 2023
1c8d157
Update style docs
ichard26 Jan 31, 2023
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
29 changes: 29 additions & 0 deletions CHANGES.md
Expand Up @@ -10,6 +10,35 @@

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

- Introduce the 2023 stable style, which incorporates most aspects of last year's
preview style (#3418). Specific changes:
- Enforce empty lines before classes and functions with sticky leading comments
(#3302) (22.12.0)
- Reformat empty and whitespace-only files as either an empty file (if no newline is
present) or as a single newline character (if a newline is present) (#3348)
(22.12.0)
- Implicitly concatenated strings used as function args are now wrapped inside
parentheses (#3307) (22.12.0)
- Correctly handle trailing commas that are inside a line's leading non-nested parens
(#3370) (22.12.0)
- `--skip-string-normalization` / `-S` now prevents docstring prefixes from being
normalized as expected (#3168) (since 22.8.0)
- When using `--skip-magic-trailing-comma` or `-C`, trailing commas are stripped from
subscript expressions with more than 1 element (#3209) (22.8.0)
- Implicitly concatenated strings inside a list, set, or tuple are now wrapped inside
parentheses (#3162) (22.8.0)
- Fix a string merging/split issue when a comment is present in the middle of
implicitly concatenated strings on its own line (#3227) (22.8.0)
- Docstring quotes are no longer moved if it would violate the line length limit
(#3044, #3430) (22.6.0)
- Parentheses around return annotations are now managed (#2990) (22.6.0)
- Remove unnecessary parentheses around awaited objects (#2991) (22.6.0)
- Remove unnecessary parentheses in `with` statements (#2926) (22.6.0)
- Remove trailing newlines after code block open (#3035) (22.6.0)
- Code cell separators `#%%` are now standardised to `# %%` (#2919) (22.3.0)
- Remove unnecessary parentheses from `except` statements (#2939) (22.3.0)
- Remove unnecessary parentheses from tuple unpacking in `for` loops (#2945) (22.3.0)
- Avoid magic-trailing-comma in single-element subscripts (#2942) (22.3.0)
- Fix a crash when a colon line is marked between `# fmt: off` and `# fmt: on` (#3439)

### Preview style
Expand Down
9 changes: 3 additions & 6 deletions src/black/__init__.py
Expand Up @@ -925,9 +925,6 @@ def format_file_contents(src_contents: str, *, fast: bool, mode: Mode) -> FileCo
valid by calling :func:`assert_equivalent` and :func:`assert_stable` on it.
`mode` is passed to :func:`format_str`.
"""
if not mode.preview and not src_contents.strip():
raise NothingChanged

if mode.is_ipynb:
dst_contents = format_ipynb_string(src_contents, fast=fast, mode=mode)
else:
Expand Down Expand Up @@ -1022,7 +1019,7 @@ def format_ipynb_string(src_contents: str, *, fast: bool, mode: Mode) -> FileCon
Operate cell-by-cell, only on code cells, only for Python notebooks.
If the ``.ipynb`` originally had a trailing newline, it'll be preserved.
"""
if mode.preview and not src_contents:
if not src_contents:
raise NothingChanged

trailing_newline = src_contents[-1] == "\n"
Expand Down Expand Up @@ -1101,7 +1098,7 @@ def _format_str_once(src_contents: str, *, mode: Mode) -> str:
for feature in {Feature.PARENTHESIZED_CONTEXT_MANAGERS}
if supports_feature(versions, feature)
}
normalize_fmt_off(src_node, preview=mode.preview)
normalize_fmt_off(src_node)
lines = LineGenerator(mode=mode, features=context_manager_features)
elt = EmptyLineTracker(mode=mode)
split_line_features = {
Expand All @@ -1122,7 +1119,7 @@ def _format_str_once(src_contents: str, *, mode: Mode) -> str:
dst_contents = []
for block in dst_blocks:
dst_contents.extend(block.all_lines())
if mode.preview and not dst_contents:
if not dst_contents:
# Use decode_bytes to retrieve the correct source newline (CRLF or LF),
# and check if normalized_content has more than one line
normalized_content, _, newline = decode_bytes(src_contents.encode("utf-8"))
Expand Down
58 changes: 25 additions & 33 deletions src/black/comments.py
Expand Up @@ -29,7 +29,7 @@
FMT_PASS: Final = {*FMT_OFF, *FMT_SKIP}
FMT_ON: Final = {"# fmt: on", "# fmt:on", "# yapf: enable"}

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


@dataclass
Expand All @@ -50,7 +50,7 @@ class ProtoComment:
consumed: int # how many characters of the original leaf's prefix did we consume


def generate_comments(leaf: LN, *, preview: bool) -> Iterator[Leaf]:
def generate_comments(leaf: LN) -> 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 @@ -69,16 +69,12 @@ def generate_comments(leaf: LN, *, preview: bool) -> 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, preview=preview
):
for pc in list_comments(leaf.prefix, is_endmarker=leaf.type == token.ENDMARKER):
yield Leaf(pc.type, pc.value, prefix="\n" * pc.newlines)


@lru_cache(maxsize=4096)
def list_comments(
prefix: str, *, is_endmarker: bool, preview: bool
) -> List[ProtoComment]:
def list_comments(prefix: str, *, is_endmarker: 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 @@ -104,7 +100,7 @@ def list_comments(
comment_type = token.COMMENT # simple trailing comment
else:
comment_type = STANDALONE_COMMENT
comment = make_comment(line, preview=preview)
comment = make_comment(line)
result.append(
ProtoComment(
type=comment_type, value=comment, newlines=nlines, consumed=consumed
Expand All @@ -114,7 +110,7 @@ def list_comments(
return result


def make_comment(content: str, *, preview: bool) -> str:
def make_comment(content: str) -> str:
"""Return a consistently formatted comment from the given `content` string.

All comments (except for "##", "#!", "#:", '#'") should have a single
Expand All @@ -135,26 +131,26 @@ def make_comment(content: str, *, preview: bool) -> str:
and not content.lstrip().startswith("type:")
):
content = " " + content[1:] # Replace NBSP by a simple space
if content and content[0] not in COMMENT_EXCEPTIONS[preview]:
if content and content[0] not in COMMENT_EXCEPTIONS:
content = " " + content
return "#" + content


def normalize_fmt_off(node: Node, *, preview: bool) -> None:
def normalize_fmt_off(node: Node) -> 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, preview=preview)
try_again = convert_one_fmt_off_pair(node)


def convert_one_fmt_off_pair(node: Node, *, preview: bool) -> bool:
def convert_one_fmt_off_pair(node: Node) -> 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, preview=preview):
for comment in list_comments(leaf.prefix, is_endmarker=False):
if comment.value not in FMT_PASS:
previous_consumed = comment.consumed
continue
Expand All @@ -169,7 +165,7 @@ def convert_one_fmt_off_pair(node: Node, *, preview: bool) -> bool:
if comment.value in FMT_SKIP and prev.type in WHITESPACE:
continue

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

Expand Down Expand Up @@ -214,26 +210,24 @@ def convert_one_fmt_off_pair(node: Node, *, preview: bool) -> bool:
return False


def generate_ignored_nodes(
leaf: Leaf, comment: ProtoComment, *, preview: bool
) -> Iterator[LN]:
def generate_ignored_nodes(leaf: Leaf, comment: ProtoComment) -> Iterator[LN]:
"""Starting from the container of `leaf`, generate all leaves until `# fmt: on`.

If comment is skip, returns leaf only.
Stops at the end of the block.
"""
if comment.value in FMT_SKIP:
yield from _generate_ignored_nodes_from_fmt_skip(leaf, comment, preview=preview)
yield from _generate_ignored_nodes_from_fmt_skip(leaf, comment)
return
container: Optional[LN] = container_of(leaf)
while container is not None and container.type != token.ENDMARKER:
if is_fmt_on(container, preview=preview):
if is_fmt_on(container):
return

# fix for fmt: on in children
if children_contains_fmt_on(container, preview=preview):
if children_contains_fmt_on(container):
for index, child in enumerate(container.children):
if isinstance(child, Leaf) and is_fmt_on(child, preview=preview):
if isinstance(child, Leaf) and is_fmt_on(child):
if child.type in CLOSING_BRACKETS:
# This means `# fmt: on` is placed at a different bracket level
# than `# fmt: off`. This is an invalid use, but as a courtesy,
Expand All @@ -244,14 +238,12 @@ def generate_ignored_nodes(
if (
child.type == token.INDENT
and index < len(container.children) - 1
and children_contains_fmt_on(
container.children[index + 1], preview=preview
)
and children_contains_fmt_on(container.children[index + 1])
):
# This means `# fmt: on` is placed right after an indentation
# level, and we shouldn't swallow the previous INDENT token.
return
if children_contains_fmt_on(child, preview=preview):
if children_contains_fmt_on(child):
return
yield child
else:
Expand All @@ -264,14 +256,14 @@ def generate_ignored_nodes(


def _generate_ignored_nodes_from_fmt_skip(
leaf: Leaf, comment: ProtoComment, *, preview: bool
leaf: Leaf, comment: ProtoComment
) -> Iterator[LN]:
"""Generate all leaves that should be ignored by the `# fmt: skip` from `leaf`."""
prev_sibling = leaf.prev_sibling
parent = leaf.parent
# Need to properly format the leaf prefix to compare it to comment.value,
# which is also formatted
comments = list_comments(leaf.prefix, is_endmarker=False, preview=preview)
comments = list_comments(leaf.prefix, is_endmarker=False)
if not comments or comment.value != comments[0].value:
return
if prev_sibling is not None:
Expand Down Expand Up @@ -305,24 +297,24 @@ def _generate_ignored_nodes_from_fmt_skip(
yield from iter(ignored_nodes)


def is_fmt_on(container: LN, preview: bool) -> bool:
def is_fmt_on(container: LN) -> 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, preview=preview):
for comment in list_comments(container.prefix, is_endmarker=False):
if comment.value in FMT_ON:
fmt_on = True
elif comment.value in FMT_OFF:
fmt_on = False
return fmt_on


def children_contains_fmt_on(container: LN, *, preview: bool) -> bool:
def children_contains_fmt_on(container: LN) -> bool:
"""Determine if children have formatting switched on."""
for child in container.children:
leaf = first_leaf_of(child)
if leaf is not None and is_fmt_on(leaf, preview=preview):
if leaf is not None and is_fmt_on(leaf):
return True

return False
Expand Down