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

Create the 2024 stable style #4106

Merged
merged 30 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
18e711f
Allow empty lines at beginning of blocks (again)
JelleZijlstra Nov 21, 2023
d753703
reformat
JelleZijlstra Nov 21, 2023
129349c
update comments
JelleZijlstra Nov 21, 2023
51bb901
Many uncontroverisal preview changes
JelleZijlstra Nov 21, 2023
c71b5e2
more relatively noncontroversial features
JelleZijlstra Nov 21, 2023
cc3780f
Update some tests
JelleZijlstra Nov 21, 2023
bb31678
Enable two more
JelleZijlstra Nov 21, 2023
337a85d
Merge branch 'main' into 241a1really
JelleZijlstra Dec 10, 2023
37f8ed0
Remove obsolete features
JelleZijlstra Dec 10, 2023
be46470
Fix up merge
JelleZijlstra Dec 10, 2023
3fd83e0
Merge branch 'main' into 241a1really
JelleZijlstra Dec 11, 2023
6e8871b
Fix the worst issues
JelleZijlstra Dec 11, 2023
10f6449
fix some tests
JelleZijlstra Dec 11, 2023
c27daf6
Update tests
JelleZijlstra Dec 11, 2023
dc2d104
Fix another dummy impl case
JelleZijlstra Dec 11, 2023
b073cbd
fix some more
JelleZijlstra Dec 11, 2023
034fd94
Merge branch 'main' into 241a1really
JelleZijlstra Dec 11, 2023
acb8c7c
unused imports
JelleZijlstra Dec 11, 2023
27c8a34
Fix feature detection for parenthesized CMs
JelleZijlstra Dec 12, 2023
0302e8d
changelog
JelleZijlstra Dec 12, 2023
6794240
Merge branch 'main' into 241a1really
JelleZijlstra Dec 12, 2023
9886590
Merge remote-tracking branch 'upstream/main' into black24
JelleZijlstra Jan 4, 2024
7f7eb72
Fix up
JelleZijlstra Jan 4, 2024
280ec86
Merge branch 'main' into black24
JelleZijlstra Jan 24, 2024
975e7f1
wrap_long_dict_values_in_parens and multiline_string_handling back to…
JelleZijlstra Jan 24, 2024
073ee3f
bad merge
JelleZijlstra Jan 24, 2024
8758f3c
fix tests
JelleZijlstra Jan 24, 2024
a9ca9a4
fix more tests
JelleZijlstra Jan 24, 2024
47449ff
Remove --preview from some tests
JelleZijlstra Jan 24, 2024
a646358
changelog
JelleZijlstra Jan 24, 2024
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
40 changes: 36 additions & 4 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,54 @@

<!-- Include any especially major or disruptive changes here -->

This release introduces the new 2024 stable style (#4106), stabilizing the following
changes:

- Add parentheses around `if`-`else` expressions (#2278)
- Dummy class and function implementations consisting only of `...` are formatted more
compactly (#3796)
- If an assignment statement is too long, we now prefer splitting on the right-hand side
(#3368)
- Hex codes in Unicode escape sequences are now standardized to lowercase (#2916)
- Allow empty first lines at the beginning of most blocks (#3967, #4061)
- Add parentheses around long type annotations (#3899)
- Standardize on a single newline after module docstrings (#3932)
- Fix incorrect magic trailing comma handling in return types (#3916)
- Remove blank lines before class docstrings (#3692)
- Wrap multiple context managers in parentheses if combined in a single `with` statement
(#3489)
- Fix bug in line length calculations for power operations (#3942)
- Add trailing commas to collection literals even if there's a comment after the last
entry (#3393)
- When using `--skip-magic-trailing-comma` or `-C`, trailing commas are stripped from
subscript expressions with more than 1 element (#3209)
- Add extra blank lines in stubs in a few cases (#3564, #3862)
- Accept raw strings as docstrings (#3947)
- Split long lines in case blocks (#4024)
- Stop removing spaces from walrus operators within subscripts (#3823)
- Fix incorrect formatting of certain async statements (#3609)
- Allow combining `# fmt: skip` with other comments (#3959)

### Stable style

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

### Preview style

<!-- Changes that affect Black's preview style -->
Several bug fixes were made in features that are moved to the stable style in this
release:

- Fix comment handling when parenthesising conditional expressions (#4134)
- Format module docstrings the same as class and function docstrings (#4095)
- Fix bug where spaces were not added around parenthesized walruses in subscripts,
unlike other binary operators (#4109)
- Remove empty lines before docstrings in async functions (#4132)
- Address a missing case in the change to allow empty lines at the beginning of all
blocks, except immediately before a docstring (#4130)
- For stubs, fix logic to enforce empty line after nested classes with bodies (#4141)

### Preview style

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

- Format module docstrings the same as class and function docstrings (#4095)
- Fix crash when using a walrus in a dictionary (#4155)
- Fix unnecessary parentheses when wrapping long dicts (#4135)

Expand Down
32 changes: 14 additions & 18 deletions src/black/comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from functools import lru_cache
from typing import Collection, Final, Iterator, List, Optional, Tuple, Union

from black.mode import Mode, Preview
from black.mode import Mode
from black.nodes import (
CLOSING_BRACKETS,
STANDALONE_COMMENT,
Expand Down Expand Up @@ -390,22 +390,18 @@ def _contains_fmt_skip_comment(comment_line: str, mode: Mode) -> bool:
# 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]
)
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
)
],
]

return any(comment in FMT_SKIP for comment in semantic_comment_blocks)
95 changes: 26 additions & 69 deletions src/black/linegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,8 @@ def line(self, indent: int = 0) -> Iterator[Line]:
self.current_line.depth += indent
return # Line is empty, don't emit. Creating a new one unnecessary.

if (
Preview.improved_async_statements_handling in self.mode
and len(self.current_line.leaves) == 1
and is_async_stmt_or_funcdef(self.current_line.leaves[0])
if len(self.current_line.leaves) == 1 and is_async_stmt_or_funcdef(
self.current_line.leaves[0]
):
# Special case for async def/for/with statements. `visit_async_stmt`
# adds an `ASYNC` leaf then visits the child def/for/with statement
Expand Down Expand Up @@ -164,20 +162,19 @@ def visit_default(self, node: LN) -> Iterator[Line]:
def visit_test(self, node: Node) -> Iterator[Line]:
"""Visit an `x if y else z` test"""

if Preview.parenthesize_conditional_expressions in self.mode:
already_parenthesized = (
node.prev_sibling and node.prev_sibling.type == token.LPAR
)
already_parenthesized = (
node.prev_sibling and node.prev_sibling.type == token.LPAR
)

if not already_parenthesized:
# Similar to logic in wrap_in_parentheses
lpar = Leaf(token.LPAR, "")
rpar = Leaf(token.RPAR, "")
prefix = node.prefix
node.prefix = ""
lpar.prefix = prefix
node.insert_child(0, lpar)
node.append_child(rpar)
if not already_parenthesized:
# Similar to logic in wrap_in_parentheses
lpar = Leaf(token.LPAR, "")
rpar = Leaf(token.RPAR, "")
prefix = node.prefix
node.prefix = ""
lpar.prefix = prefix
node.insert_child(0, lpar)
node.append_child(rpar)

yield from self.visit_default(node)

Expand Down Expand Up @@ -292,9 +289,7 @@ def visit_match_case(self, node: Node) -> Iterator[Line]:

def visit_suite(self, node: Node) -> Iterator[Line]:
"""Visit a suite."""
if (
self.mode.is_pyi or Preview.dummy_implementations in self.mode
) and is_stub_suite(node, self.mode):
if is_stub_suite(node):
yield from self.visit(node.children[2])
else:
yield from self.visit_default(node)
Expand All @@ -308,23 +303,15 @@ def visit_simple_stmt(self, node: Node) -> Iterator[Line]:
prev_type = child.type

if node.parent and node.parent.type in STATEMENT:
if Preview.dummy_implementations in self.mode:
condition = is_parent_function_or_class(node)
else:
condition = self.mode.is_pyi
if condition and is_stub_body(node):
if is_parent_function_or_class(node) and is_stub_body(node):
yield from self.visit_default(node)
else:
yield from self.line(+1)
yield from self.visit_default(node)
yield from self.line(-1)

else:
if (
not (self.mode.is_pyi or Preview.dummy_implementations in self.mode)
or not node.parent
or not is_stub_suite(node.parent, self.mode)
):
if not node.parent or not is_stub_suite(node.parent):
yield from self.line()
yield from self.visit_default(node)

Expand All @@ -342,11 +329,7 @@ def visit_async_stmt(self, node: Node) -> Iterator[Line]:
break

internal_stmt = next(children)
if Preview.improved_async_statements_handling in self.mode:
yield from self.visit(internal_stmt)
else:
for child in internal_stmt.children:
yield from self.visit(child)
yield from self.visit(internal_stmt)

def visit_decorators(self, node: Node) -> Iterator[Line]:
"""Visit decorators."""
Expand Down Expand Up @@ -420,10 +403,9 @@ def foo(a: int, b: float = 7): ...

def foo(a: (int), b: (float) = 7): ...
"""
if Preview.parenthesize_long_type_hints in self.mode:
assert len(node.children) == 3
if maybe_make_parens_invisible_in_atom(node.children[2], parent=node):
wrap_in_parentheses(node, node.children[2], visible=False)
assert len(node.children) == 3
if maybe_make_parens_invisible_in_atom(node.children[2], parent=node):
wrap_in_parentheses(node, node.children[2], visible=False)

yield from self.visit_default(node)

Expand Down Expand Up @@ -529,13 +511,7 @@ def __post_init__(self) -> None:
self.visit_with_stmt = partial(v, keywords={"with"}, parens={"with"})
self.visit_classdef = partial(v, keywords={"class"}, parens=Ø)

# When this is moved out of preview, add ":" directly to ASSIGNMENTS in nodes.py
if Preview.parenthesize_long_type_hints in self.mode:
assignments = ASSIGNMENTS | {":"}
else:
assignments = ASSIGNMENTS
self.visit_expr_stmt = partial(v, keywords=Ø, parens=assignments)

self.visit_expr_stmt = partial(v, keywords=Ø, parens=ASSIGNMENTS)
self.visit_return_stmt = partial(v, keywords={"return"}, parens={"return"})
self.visit_import_from = partial(v, keywords=Ø, parens={"import"})
self.visit_del_stmt = partial(v, keywords=Ø, parens={"del"})
Expand Down Expand Up @@ -576,9 +552,7 @@ def transform_line(
# We need the line string when power operators are hugging to determine if we should
# split the line. Default to line_str, if no power operator are present on the line.
line_str_hugging_power_ops = (
(_hugging_power_ops_line_to_string(line, features, mode) or line_str)
if Preview.fix_power_op_line_length in mode
else line_str
_hugging_power_ops_line_to_string(line, features, mode) or line_str
)

ll = mode.line_length
Expand Down Expand Up @@ -688,9 +662,6 @@ def should_split_funcdef_with_rhs(line: Line, mode: Mode) -> bool:
"""If a funcdef has a magic trailing comma in the return type, then we should first
split the line with rhs to respect the comma.
"""
if Preview.respect_magic_trailing_comma_in_return_type not in mode:
return False

return_type_leaves: List[Leaf] = []
in_return_type = False

Expand Down Expand Up @@ -919,9 +890,6 @@ def _maybe_split_omitting_optional_parens(
try:
# The RHSResult Omitting Optional Parens.
rhs_oop = _first_right_hand_split(line, omit=omit)
prefer_splitting_rhs_mode = (
Preview.prefer_splitting_right_hand_side_of_assignments in line.mode
)
is_split_right_after_equal = (
len(rhs.head.leaves) >= 2 and rhs.head.leaves[-2].type == token.EQUAL
)
Expand All @@ -937,8 +905,7 @@ def _maybe_split_omitting_optional_parens(
)
if (
not (
prefer_splitting_rhs_mode
and is_split_right_after_equal
is_split_right_after_equal
and rhs_head_contains_brackets
and rhs_head_short_enough
and rhs_head_explode_blocked_by_magic_trailing_comma
Expand Down Expand Up @@ -1224,11 +1191,7 @@ def append_to_line(leaf: Leaf) -> Iterator[Line]:
trailing_comma_safe and Feature.TRAILING_COMMA_IN_CALL in features
)

if (
Preview.add_trailing_comma_consistently in mode
and last_leaf.type == STANDALONE_COMMENT
and leaf_idx == last_non_comment_leaf
):
if last_leaf.type == STANDALONE_COMMENT and leaf_idx == last_non_comment_leaf:
current_line = _safe_add_trailing_comma(
trailing_comma_safe, delimiter_priority, current_line
)
Expand Down Expand Up @@ -1315,11 +1278,7 @@ def normalize_invisible_parens( # noqa: C901

# Fixes a bug where invisible parens are not properly wrapped around
# case blocks.
if (
isinstance(child, Node)
and child.type == syms.case_block
and Preview.long_case_block_line_splitting in mode
):
if isinstance(child, Node) and child.type == syms.case_block:
normalize_invisible_parens(
child, parens_after={"case"}, mode=mode, features=features
)
Expand Down Expand Up @@ -1374,7 +1333,6 @@ def normalize_invisible_parens( # noqa: C901
and child.next_sibling is not None
and child.next_sibling.type == token.COLON
and child.value == "case"
and Preview.long_case_block_line_splitting in mode
):
# A special patch for "case case:" scenario, the second occurrence
# of case will be not parsed as a Python keyword.
Expand Down Expand Up @@ -1448,7 +1406,6 @@ def _maybe_wrap_cms_in_parens(
"""
if (
Feature.PARENTHESIZED_CONTEXT_MANAGERS not in features
or Preview.wrap_multiple_context_managers_in_parens not in mode
or len(node.children) <= 2
# If it's an atom, it's already wrapped in parens.
or node.children[1].type == syms.atom
Expand Down