Skip to content

Commit c0b92f3

Browse files
Prepare the 2025 stable style (#4558)
1 parent e58baf1 commit c0b92f3

28 files changed

+72
-156
lines changed

CHANGES.md

+17-2
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,29 @@
66

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

9+
This release introduces the new 2025 stable style (#4558), stabilizing
10+
the following changes:
11+
12+
- Normalize casing of Unicode escape characters in strings to lowercase (#2916)
13+
- Fix inconsistencies in whether certain strings are detected as docstrings (#4095)
14+
- Consistently add trailing commas to typed function parameters (#4164)
15+
- Remove redundant parentheses in if guards for case blocks (#4214)
16+
- Add parentheses to if clauses in case blocks when the line is too long (#4269)
17+
- Whitespace before `# fmt: skip` comments is no longer normalized (#4146)
18+
- Fix line length computation for certain expressions that involve the power operator (#4154)
19+
- Check if there is a newline before the terminating quotes of a docstring (#4185)
20+
- Fix type annotation spacing between `*` and more complex type variable tuple (#4440)
21+
22+
The following changes were not in any previous release:
23+
24+
- Remove parentheses around sole list items (#4312)
25+
926
### Stable style
1027

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

1330
- Fix formatting cells in IPython notebooks with magic methods and starting or trailing
1431
empty lines (#4484)
15-
1632
- Fix crash when formatting `with` statements containing tuple generators/unpacking
1733
(#4538)
1834

@@ -22,7 +38,6 @@
2238

2339
- Fix/remove string merging changing f-string quotes on f-strings with internal quotes
2440
(#4498)
25-
- Remove parentheses around sole list items (#4312)
2641
- Collapse multiple empty lines after an import into one (#4489)
2742
- Prevent `string_processing` and `wrap_long_dict_values_in_parens` from removing
2843
parentheses around long dictionary values (#4377)

docs/the_black_code_style/current_style.md

+5
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,11 @@ exception of [capital "R" prefixes](#rstrings-and-rstrings), unicode literal mar
250250
(`u`) are removed because they are meaningless in Python 3, and in the case of multiple
251251
characters "r" is put first as in spoken language: "raw f-string".
252252

253+
Another area where Python allows multiple ways to format a string is escape sequences.
254+
For example, `"\uabcd"` and `"\uABCD"` evaluate to the same string. _Black_ normalizes
255+
such escape sequences to lowercase, but uses uppercase for `\N` named character escapes,
256+
such as `"\N{MEETEI MAYEK LETTER HUK}"`.
257+
253258
The main reason to standardize on a single form of quotes is aesthetics. Having one kind
254259
of quotes everywhere reduces reader distraction. It will also enable a future version of
255260
_Black_ to merge consecutive string literals that ended up on the same line (see

docs/the_black_code_style/future_style.md

-21
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,6 @@ demoted from the `--preview` to the `--unstable` style, users can use the
2020

2121
Currently, the following features are included in the preview style:
2222

23-
- `hex_codes_in_unicode_sequences`: normalize casing of Unicode escape characters in
24-
strings
25-
- `unify_docstring_detection`: fix inconsistencies in whether certain strings are
26-
detected as docstrings
27-
- `no_normalize_fmt_skip_whitespace`: whitespace before `# fmt: skip` comments is no
28-
longer normalized
29-
- `typed_params_trailing_comma`: consistently add trailing commas to typed function
30-
parameters
31-
- `is_simple_lookup_for_doublestar_expression`: fix line length computation for certain
32-
expressions that involve the power operator
33-
- `docstring_check_for_newline`: checks if there is a newline before the terminating
34-
quotes of a docstring
35-
- `remove_redundant_guard_parens`: Removes redundant parentheses in `if` guards for
36-
`case` blocks.
37-
- `parens_for_long_if_clauses_in_case_block`: Adds parentheses to `if` clauses in `case`
38-
blocks when the line is too long
39-
- `pep646_typed_star_arg_type_var_tuple`: fix type annotation spacing between * and more
40-
complex type variable tuple (i.e. `def fn(*args: *tuple[*Ts, T]) -> None: pass`)
41-
- `remove_lone_list_item_parens`: remove redundant parentheses around lone list items
42-
(depends on unstable `hug_parens_with_braces_and_square_brackets` feature in some
43-
cases)
4423
- `always_one_newline_after_import`: Always force one blank line after import
4524
statements, except when the line after the import is a comment or an import statement
4625

src/black/comments.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from functools import lru_cache
55
from typing import Final, Optional, Union
66

7-
from black.mode import Mode, Preview
7+
from black.mode import Mode
88
from black.nodes import (
99
CLOSING_BRACKETS,
1010
STANDALONE_COMMENT,
@@ -235,11 +235,7 @@ def convert_one_fmt_off_pair(
235235
standalone_comment_prefix += fmt_off_prefix
236236
hidden_value = comment.value + "\n" + hidden_value
237237
if is_fmt_skip:
238-
hidden_value += (
239-
comment.leading_whitespace
240-
if Preview.no_normalize_fmt_skip_whitespace in mode
241-
else " "
242-
) + comment.value
238+
hidden_value += comment.leading_whitespace + comment.value
243239
if hidden_value.endswith("\n"):
244240
# That happens when one of the `ignored_nodes` ended with a NEWLINE
245241
# leaf (possibly followed by a DEDENT).

src/black/handle_ipynb_magics.py

+17-8
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
"time",
4444
"timeit",
4545
))
46-
TOKEN_HEX = secrets.token_hex
4746

4847

4948
@dataclasses.dataclass(frozen=True)
@@ -160,7 +159,7 @@ def mask_cell(src: str) -> tuple[str, list[Replacement]]:
160159
161160
becomes
162161
163-
"25716f358c32750e"
162+
b"25716f358c32750"
164163
'foo'
165164
166165
The replacements are returned, along with the transformed code.
@@ -192,6 +191,18 @@ def mask_cell(src: str) -> tuple[str, list[Replacement]]:
192191
return transformed, replacements
193192

194193

194+
def create_token(n_chars: int) -> str:
195+
"""Create a randomly generated token that is n_chars characters long."""
196+
assert n_chars > 0
197+
n_bytes = max(n_chars // 2 - 1, 1)
198+
token = secrets.token_hex(n_bytes)
199+
if len(token) + 3 > n_chars:
200+
token = token[:-1]
201+
# We use a bytestring so that the string does not get interpreted
202+
# as a docstring.
203+
return f'b"{token}"'
204+
205+
195206
def get_token(src: str, magic: str) -> str:
196207
"""Return randomly generated token to mask IPython magic with.
197208
@@ -201,21 +212,19 @@ def get_token(src: str, magic: str) -> str:
201212
not already present anywhere else in the cell.
202213
"""
203214
assert magic
204-
nbytes = max(len(magic) // 2 - 1, 1)
205-
token = TOKEN_HEX(nbytes)
215+
n_chars = len(magic)
216+
token = create_token(n_chars)
206217
counter = 0
207218
while token in src:
208-
token = TOKEN_HEX(nbytes)
219+
token = create_token(n_chars)
209220
counter += 1
210221
if counter > 100:
211222
raise AssertionError(
212223
"INTERNAL ERROR: Black was not able to replace IPython magic. "
213224
"Please report a bug on https://github.com/psf/black/issues. "
214225
f"The magic might be helpful: {magic}"
215226
) from None
216-
if len(token) + 2 < len(magic):
217-
token = f"{token}."
218-
return f'"{token}"'
227+
return token
219228

220229

221230
def replace_cell_magics(src: str) -> tuple[str, list[Replacement]]:

src/black/linegen.py

+7-24
Original file line numberDiff line numberDiff line change
@@ -414,10 +414,9 @@ def foo(a: (int), b: (float) = 7): ...
414414
yield from self.visit_default(node)
415415

416416
def visit_STRING(self, leaf: Leaf) -> Iterator[Line]:
417-
if Preview.hex_codes_in_unicode_sequences in self.mode:
418-
normalize_unicode_escape_sequences(leaf)
417+
normalize_unicode_escape_sequences(leaf)
419418

420-
if is_docstring(leaf, self.mode) and not re.search(r"\\\s*\n", leaf.value):
419+
if is_docstring(leaf) and not re.search(r"\\\s*\n", leaf.value):
421420
# We're ignoring docstrings with backslash newline escapes because changing
422421
# indentation of those changes the AST representation of the code.
423422
if self.mode.string_normalization:
@@ -488,10 +487,7 @@ def visit_STRING(self, leaf: Leaf) -> Iterator[Line]:
488487
and len(indent) + quote_len <= self.mode.line_length
489488
and not has_trailing_backslash
490489
):
491-
if (
492-
Preview.docstring_check_for_newline in self.mode
493-
and leaf.value[-1 - quote_len] == "\n"
494-
):
490+
if leaf.value[-1 - quote_len] == "\n":
495491
leaf.value = prefix + quote + docstring + quote
496492
else:
497493
leaf.value = prefix + quote + docstring + "\n" + indent + quote
@@ -511,10 +507,7 @@ def visit_NUMBER(self, leaf: Leaf) -> Iterator[Line]:
511507

512508
def visit_atom(self, node: Node) -> Iterator[Line]:
513509
"""Visit any atom"""
514-
if (
515-
Preview.remove_lone_list_item_parens in self.mode
516-
and len(node.children) == 3
517-
):
510+
if len(node.children) == 3:
518511
first = node.children[0]
519512
last = node.children[-1]
520513
if (first.type == token.LSQB and last.type == token.RSQB) or (
@@ -602,8 +595,7 @@ def __post_init__(self) -> None:
602595
# PEP 634
603596
self.visit_match_stmt = self.visit_match_case
604597
self.visit_case_block = self.visit_match_case
605-
if Preview.remove_redundant_guard_parens in self.mode:
606-
self.visit_guard = partial(v, keywords=Ø, parens={"if"})
598+
self.visit_guard = partial(v, keywords=Ø, parens={"if"})
607599

608600

609601
def _hugging_power_ops_line_to_string(
@@ -1132,12 +1124,7 @@ def _ensure_trailing_comma(
11321124
return False
11331125
# Don't add commas if we already have any commas
11341126
if any(
1135-
leaf.type == token.COMMA
1136-
and (
1137-
Preview.typed_params_trailing_comma not in original.mode
1138-
or not is_part_of_annotation(leaf)
1139-
)
1140-
for leaf in leaves
1127+
leaf.type == token.COMMA and not is_part_of_annotation(leaf) for leaf in leaves
11411128
):
11421129
return False
11431130

@@ -1418,11 +1405,7 @@ def normalize_invisible_parens( # noqa: C901
14181405
)
14191406

14201407
# Add parentheses around if guards in case blocks
1421-
if (
1422-
isinstance(child, Node)
1423-
and child.type == syms.guard
1424-
and Preview.parens_for_long_if_clauses_in_case_block in mode
1425-
):
1408+
if isinstance(child, Node) and child.type == syms.guard:
14261409
normalize_invisible_parens(
14271410
child, parens_after={"if"}, mode=mode, features=features
14281411
)

src/black/lines.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,7 @@ def _is_triple_quoted_string(self) -> bool:
204204
@property
205205
def is_docstring(self) -> bool:
206206
"""Is the line a docstring?"""
207-
if Preview.unify_docstring_detection not in self.mode:
208-
return self._is_triple_quoted_string
209-
return bool(self) and is_docstring(self.leaves[0], self.mode)
207+
return bool(self) and is_docstring(self.leaves[0])
210208

211209
@property
212210
def is_chained_assignment(self) -> bool:

src/black/mode.py

-12
Original file line numberDiff line numberDiff line change
@@ -196,24 +196,12 @@ def supports_feature(target_versions: set[TargetVersion], feature: Feature) -> b
196196
class Preview(Enum):
197197
"""Individual preview style features."""
198198

199-
hex_codes_in_unicode_sequences = auto()
200199
# NOTE: string_processing requires wrap_long_dict_values_in_parens
201200
# for https://github.com/psf/black/issues/3117 to be fixed.
202201
string_processing = auto()
203202
hug_parens_with_braces_and_square_brackets = auto()
204-
unify_docstring_detection = auto()
205-
no_normalize_fmt_skip_whitespace = auto()
206203
wrap_long_dict_values_in_parens = auto()
207204
multiline_string_handling = auto()
208-
typed_params_trailing_comma = auto()
209-
is_simple_lookup_for_doublestar_expression = auto()
210-
docstring_check_for_newline = auto()
211-
remove_redundant_guard_parens = auto()
212-
parens_for_long_if_clauses_in_case_block = auto()
213-
# NOTE: remove_lone_list_item_parens requires
214-
# hug_parens_with_braces_and_square_brackets to remove parens in some cases
215-
remove_lone_list_item_parens = auto()
216-
pep646_typed_star_arg_type_var_tuple = auto()
217205
always_one_newline_after_import = auto()
218206

219207

src/black/nodes.py

+4-11
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from mypy_extensions import mypyc_attr
1515

1616
from black.cache import CACHE_DIR
17-
from black.mode import Mode, Preview
17+
from black.mode import Mode
1818
from black.strings import get_string_prefix, has_triple_quotes
1919
from blib2to3 import pygram
2020
from blib2to3.pgen2 import token
@@ -244,13 +244,7 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool, mode: Mode) -> str: # no
244244
elif (
245245
prevp.type == token.STAR
246246
and parent_type(prevp) == syms.star_expr
247-
and (
248-
parent_type(prevp.parent) == syms.subscriptlist
249-
or (
250-
Preview.pep646_typed_star_arg_type_var_tuple in mode
251-
and parent_type(prevp.parent) == syms.tname_star
252-
)
253-
)
247+
and parent_type(prevp.parent) in (syms.subscriptlist, syms.tname_star)
254248
):
255249
# No space between typevar tuples or unpacking them.
256250
return NO
@@ -551,7 +545,7 @@ def is_arith_like(node: LN) -> bool:
551545
}
552546

553547

554-
def is_docstring(node: NL, mode: Mode) -> bool:
548+
def is_docstring(node: NL) -> bool:
555549
if isinstance(node, Leaf):
556550
if node.type != token.STRING:
557551
return False
@@ -561,8 +555,7 @@ def is_docstring(node: NL, mode: Mode) -> bool:
561555
return False
562556

563557
if (
564-
Preview.unify_docstring_detection in mode
565-
and node.parent
558+
node.parent
566559
and node.parent.type == syms.simple_stmt
567560
and not node.parent.prev_sibling
568561
and node.parent.parent

src/black/resources/black.schema.json

-10
Original file line numberDiff line numberDiff line change
@@ -79,20 +79,10 @@
7979
"type": "array",
8080
"items": {
8181
"enum": [
82-
"hex_codes_in_unicode_sequences",
8382
"string_processing",
8483
"hug_parens_with_braces_and_square_brackets",
85-
"unify_docstring_detection",
86-
"no_normalize_fmt_skip_whitespace",
8784
"wrap_long_dict_values_in_parens",
8885
"multiline_string_handling",
89-
"typed_params_trailing_comma",
90-
"is_simple_lookup_for_doublestar_expression",
91-
"docstring_check_for_newline",
92-
"remove_redundant_guard_parens",
93-
"parens_for_long_if_clauses_in_case_block",
94-
"remove_lone_list_item_parens",
95-
"pep646_typed_star_arg_type_var_tuple",
9686
"always_one_newline_after_import"
9787
]
9888
},

0 commit comments

Comments
 (0)