From 96a590398fd4d119e0f87de5a4356e206254cb16 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 7 Dec 2025 16:04:18 -0600 Subject: [PATCH 01/22] Sort lists Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- docs/the_black_code_style/future_style.md | 96 +++++++++++------------ src/black/mode.py | 16 ++-- src/black/resources/black.schema.json | 14 ++-- 3 files changed, 60 insertions(+), 66 deletions(-) diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index 07bc5258d92..11418fe0abb 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -14,59 +14,28 @@ experimental, feedback and issue reports are highly encouraged! Currently, the following features are included in the preview style: - `always_one_newline_after_import`: Always force one blank line after import - statements, except when the line after the import is a comment or an import statement -- `wrap_long_dict_values_in_parens`: Add parentheses around long values in dictionaries - ([see below](labels/wrap-long-dict-values)) -- `fix_fmt_skip_in_one_liners`: Fix `# fmt: skip` behaviour on one-liner declarations, + statements, except when the line after the import is a comment or an import statement. +- `fix_fmt_skip_in_one_liners`: Fix `# fmt: skip` behavior on one-liner declarations, such as `def foo(): return "mock" # fmt: skip`, where previously the declaration would have been incorrectly collapsed. -- `standardize_type_comments`: Format type comments which have zero or more spaces - between `#` and `type:` or between `type:` and value to `# type: (value)` -- `wrap_comprehension_in`: Wrap the `in` clause of list and dictionary comprehensions - across lines if it would otherwise exceed the maximum line length. -- `remove_parens_around_except_types`: Remove parentheses around multiple exception - types in `except` and `except*` without `as`. See PEP 758 for details. -- `normalize_cr_newlines`: Add `\r` style newlines to the potential newlines to - normalize file newlines both from and to. - `fix_module_docstring_detection`: Fix module docstrings being treated as normal - strings if preceeded by comments. + strings if preceded by comments. - `fix_type_expansion_split`: Fix type expansions split in generic functions. +- `multiline_string_handling`: Make expressions involving multiline strings more + compact. ([see below](labels/multiline-string-handling)) +- `normalize_cr_newlines`: Add `\r` style newlines to the potential newlines to + normalize file newlines both from and to. +- `remove_parens_around_except_types`: Remove parentheses around multiple exception + types in `except` and `except*` without `as`. See PEP 758 for details. - `remove_parens_from_assignment_lhs`: Remove unnecessary parentheses from the left-hand side of assignments while preserving magic trailing commas and intentional multiline - formatting. For example, `(b) = a()[0]` becomes `b = a()[0]`, and `(c, *_) = a()` - becomes `c, *_ = a()`, but `(d,) = a()` is preserved as it defines a single-element - tuple. -- `multiline_string_handling`: more compact formatting of expressions involving - multiline strings ([see below](labels/multiline-string-handling)) -- `fix_module_docstring_detection`: Fix module docstrings being treated as normal - strings if preceeded by comments. - -(labels/wrap-long-dict-values)= - -### Improved parentheses management in dicts - -For dict literals with long values, they are now wrapped in parentheses. Unnecessary -parentheses are now removed. For example: - -```python -my_dict = { - "a key in my dict": a_very_long_variable - * and_a_very_long_function_call() - / 100000.0, - "another key": (short_value), -} -``` - -will be changed to: - -```python -my_dict = { - "a key in my dict": ( - a_very_long_variable * and_a_very_long_function_call() / 100000.0 - ), - "another key": short_value, -} -``` + formatting. +- `standardize_type_comments`: Format type comments which have zero or more spaces + between `#` and `type:` or between `type:` and value to `# type: (value)`. +- `wrap_comprehension_in`: Wrap the `in` clause of list and dictionary comprehensions + across lines if it would otherwise exceed the maximum line length. +- `wrap_long_dict_values_in_parens`: Add parentheses around long values in dictionaries. + ([see below](labels/wrap-long-dict-values)) (labels/multiline-string-handling)= @@ -118,6 +87,33 @@ foobar """.replace("\n", "") ``` +(labels/wrap-long-dict-values)= + +### Improved parentheses management in dicts + +For dict literals with long values, they are now wrapped in parentheses. Unnecessary +parentheses are now removed. For example: + +```python +my_dict = { + "a key in my dict": a_very_long_variable + * and_a_very_long_function_call() + / 100000.0, + "another key": (short_value), +} +``` + +will be changed to: + +```python +my_dict = { + "a key in my dict": ( + a_very_long_variable * and_a_very_long_function_call() / 100000.0 + ), + "another key": short_value, +} +``` + ## Unstable style (labels/unstable-style)= @@ -135,9 +131,9 @@ demoted from the `--preview` to the `--unstable` style, users can use the The unstable style additionally includes the following features: -- `hug_parens_with_braces_and_square_brackets`: more compact formatting of nested - brackets ([see below](labels/hug-parens)) -- `string_processing`: split long string literals and related changes +- `hug_parens_with_braces_and_square_brackets`: More compact formatting of nested + brackets. ([see below](labels/hug-parens)) +- `string_processing`: Split long string literals and related changes. ([see below](labels/string-processing)) (labels/hug-parens)= diff --git a/src/black/mode.py b/src/black/mode.py index 702f580e979..3a7c08eb5ab 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -226,23 +226,21 @@ class Preview(Enum): # for https://github.com/psf/black/issues/3117 to be fixed. string_processing = auto() hug_parens_with_braces_and_square_brackets = auto() - wrap_long_dict_values_in_parens = auto() - multiline_string_handling = auto() always_one_newline_after_import = auto() fix_fmt_skip_in_one_liners = auto() - standardize_type_comments = auto() - wrap_comprehension_in = auto() - # Remove parentheses around multiple exception types in except and - # except* without as. See PEP 758 for details. - remove_parens_around_except_types = auto() - normalize_cr_newlines = auto() fix_module_docstring_detection = auto() fix_type_expansion_split = auto() + multiline_string_handling = auto() + normalize_cr_newlines = auto() + remove_parens_around_except_types = auto() remove_parens_from_assignment_lhs = auto() + standardize_type_comments = auto() + wrap_comprehension_in = auto() + wrap_long_dict_values_in_parens = auto() UNSTABLE_FEATURES: set[Preview] = { - # Many issues, see summary in https://github.com/psf/black/issues/4042 + # Many issues, see summary in https://github.com/psf/black/issues/4208 Preview.string_processing, # See issue #4036 (crash), #4098, #4099 (proposed tweaks) Preview.hug_parens_with_braces_and_square_brackets, diff --git a/src/black/resources/black.schema.json b/src/black/resources/black.schema.json index bed70a4bb22..e4ed3dbce79 100644 --- a/src/black/resources/black.schema.json +++ b/src/black/resources/black.schema.json @@ -82,17 +82,17 @@ "enum": [ "string_processing", "hug_parens_with_braces_and_square_brackets", - "wrap_long_dict_values_in_parens", - "multiline_string_handling", "always_one_newline_after_import", "fix_fmt_skip_in_one_liners", - "standardize_type_comments", - "wrap_comprehension_in", - "remove_parens_around_except_types", - "normalize_cr_newlines", "fix_module_docstring_detection", "fix_type_expansion_split", - "remove_parens_from_assignment_lhs" + "multiline_string_handling", + "normalize_cr_newlines", + "remove_parens_around_except_types", + "remove_parens_from_assignment_lhs", + "standardize_type_comments", + "wrap_comprehension_in", + "wrap_long_dict_values_in_parens" ] }, "description": "Enable specific features included in the `--unstable` style. Requires `--preview`. No compatibility guarantees are provided on the behavior or existence of any unstable features." From 581ad213c3eac44f073be252a2a5bf8e4a5f3e53 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 7 Dec 2025 16:04:49 -0600 Subject: [PATCH 02/22] always_one_newline_after_import Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- docs/the_black_code_style/future_style.md | 2 -- src/black/lines.py | 1 - src/black/mode.py | 1 - src/black/resources/black.schema.json | 1 - 4 files changed, 5 deletions(-) diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index 11418fe0abb..c4a07be7a8d 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -13,8 +13,6 @@ experimental, feedback and issue reports are highly encouraged! Currently, the following features are included in the preview style: -- `always_one_newline_after_import`: Always force one blank line after import - statements, except when the line after the import is a comment or an import statement. - `fix_fmt_skip_in_one_liners`: Fix `# fmt: skip` behavior on one-liner declarations, such as `def foo(): return "mock" # fmt: skip`, where previously the declaration would have been incorrectly collapsed. diff --git a/src/black/lines.py b/src/black/lines.py index fffd9f7120f..ad81a559ace 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -695,7 +695,6 @@ def _maybe_empty_lines(self, current_line: Line) -> tuple[int, int]: and self.previous_line.depth == 0 and current_line.depth == 0 and not current_line.is_import - and Preview.always_one_newline_after_import in self.mode ): return 1, 0 diff --git a/src/black/mode.py b/src/black/mode.py index 3a7c08eb5ab..0eef3cbe468 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -226,7 +226,6 @@ class Preview(Enum): # for https://github.com/psf/black/issues/3117 to be fixed. string_processing = auto() hug_parens_with_braces_and_square_brackets = auto() - always_one_newline_after_import = auto() fix_fmt_skip_in_one_liners = auto() fix_module_docstring_detection = auto() fix_type_expansion_split = auto() diff --git a/src/black/resources/black.schema.json b/src/black/resources/black.schema.json index e4ed3dbce79..478aa344f83 100644 --- a/src/black/resources/black.schema.json +++ b/src/black/resources/black.schema.json @@ -82,7 +82,6 @@ "enum": [ "string_processing", "hug_parens_with_braces_and_square_brackets", - "always_one_newline_after_import", "fix_fmt_skip_in_one_liners", "fix_module_docstring_detection", "fix_type_expansion_split", From f833ceaf7397bcc6cd98406dc937cefcdf7774d8 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 7 Dec 2025 16:05:09 -0600 Subject: [PATCH 03/22] fix_fmt_skip_in_one_liners Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- docs/the_black_code_style/future_style.md | 3 --- src/black/comments.py | 15 ++------------- src/black/mode.py | 1 - src/black/resources/black.schema.json | 1 - 4 files changed, 2 insertions(+), 18 deletions(-) diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index c4a07be7a8d..a24fd40a874 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -13,9 +13,6 @@ experimental, feedback and issue reports are highly encouraged! Currently, the following features are included in the preview style: -- `fix_fmt_skip_in_one_liners`: Fix `# fmt: skip` behavior on one-liner declarations, - such as `def foo(): return "mock" # fmt: skip`, where previously the declaration would - have been incorrectly collapsed. - `fix_module_docstring_detection`: Fix module docstrings being treated as normal strings if preceded by comments. - `fix_type_expansion_split`: Fix type expansions split in generic functions. diff --git a/src/black/comments.py b/src/black/comments.py index 96ce67d92f8..55251a24b5d 100644 --- a/src/black/comments.py +++ b/src/black/comments.py @@ -645,7 +645,7 @@ def _generate_ignored_nodes_from_fmt_skip( if not comments or comment.value != comments[0].value: return - if Preview.fix_fmt_skip_in_one_liners in mode and not prev_sibling and parent: + if not prev_sibling and parent: # If the current leaf doesn't have a previous sibling, it might be deeply nested # (e.g. inside a list, function call, etc.). We need to climb up the tree # to find the previous sibling on the same line. @@ -690,17 +690,6 @@ def _generate_ignored_nodes_from_fmt_skip( if prev_sibling is not None: leaf.prefix = leaf.prefix[comment.consumed :] - if Preview.fix_fmt_skip_in_one_liners not in mode: - siblings = [prev_sibling] - while ( - "\n" not in prev_sibling.prefix - and prev_sibling.prev_sibling is not None - ): - prev_sibling = prev_sibling.prev_sibling - siblings.insert(0, prev_sibling) - yield from siblings - return - # Generates the nodes to be ignored by `fmt: skip`. # Nodes to ignore are the ones on the same line as the @@ -779,7 +768,7 @@ def _generate_ignored_nodes_from_fmt_skip( current_node = current_node.parent # Special handling for compound statements with semicolon-separated bodies - if Preview.fix_fmt_skip_in_one_liners in mode and isinstance(parent, Node): + if isinstance(parent, Node): body_node = _find_compound_statement_context(parent) if body_node is not None: header_nodes = _get_compound_statement_header(body_node, parent) diff --git a/src/black/mode.py b/src/black/mode.py index 0eef3cbe468..d8be4cf0d7b 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -226,7 +226,6 @@ class Preview(Enum): # for https://github.com/psf/black/issues/3117 to be fixed. string_processing = auto() hug_parens_with_braces_and_square_brackets = auto() - fix_fmt_skip_in_one_liners = auto() fix_module_docstring_detection = auto() fix_type_expansion_split = auto() multiline_string_handling = auto() diff --git a/src/black/resources/black.schema.json b/src/black/resources/black.schema.json index 478aa344f83..b039f2d8b19 100644 --- a/src/black/resources/black.schema.json +++ b/src/black/resources/black.schema.json @@ -82,7 +82,6 @@ "enum": [ "string_processing", "hug_parens_with_braces_and_square_brackets", - "fix_fmt_skip_in_one_liners", "fix_module_docstring_detection", "fix_type_expansion_split", "multiline_string_handling", From b973b53a6ef4820147f627e1828be2fc37a4be3e Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 7 Dec 2025 16:05:59 -0600 Subject: [PATCH 04/22] fix_module_docstring_detection Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- docs/the_black_code_style/future_style.md | 2 -- src/black/lines.py | 18 ++++-------------- src/black/mode.py | 1 - src/black/resources/black.schema.json | 1 - 4 files changed, 4 insertions(+), 18 deletions(-) diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index a24fd40a874..4a1cbb9d5d8 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -13,8 +13,6 @@ experimental, feedback and issue reports are highly encouraged! Currently, the following features are included in the preview style: -- `fix_module_docstring_detection`: Fix module docstrings being treated as normal - strings if preceded by comments. - `fix_type_expansion_split`: Fix type expansions split in generic functions. - `multiline_string_handling`: Make expressions involving multiline strings more compact. ([see below](labels/multiline-string-handling)) diff --git a/src/black/lines.py b/src/black/lines.py index ad81a559ace..37be855efd3 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -559,20 +559,10 @@ def maybe_empty_lines(self, current_line: Line) -> LinesBlock: before, after = self._maybe_empty_lines(current_line) previous_after = self.previous_block.after if self.previous_block else 0 before = max(0, before - previous_after) - if Preview.fix_module_docstring_detection in self.mode: - # Always have one empty line after a module docstring - if self._line_is_module_docstring(current_line): - before = 1 - else: - if ( - # Always have one empty line after a module docstring - self.previous_block - and self.previous_block.previous_block is None - and len(self.previous_block.original_line.leaves) == 1 - and self.previous_block.original_line.is_docstring - and not (current_line.is_class or current_line.is_def) - ): - before = 1 + + # Always have one empty line after a module docstring + if self._line_is_module_docstring(current_line): + before = 1 block = LinesBlock( mode=self.mode, diff --git a/src/black/mode.py b/src/black/mode.py index d8be4cf0d7b..8233a1ec5e3 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -226,7 +226,6 @@ class Preview(Enum): # for https://github.com/psf/black/issues/3117 to be fixed. string_processing = auto() hug_parens_with_braces_and_square_brackets = auto() - fix_module_docstring_detection = auto() fix_type_expansion_split = auto() multiline_string_handling = auto() normalize_cr_newlines = auto() diff --git a/src/black/resources/black.schema.json b/src/black/resources/black.schema.json index b039f2d8b19..afef0dff228 100644 --- a/src/black/resources/black.schema.json +++ b/src/black/resources/black.schema.json @@ -82,7 +82,6 @@ "enum": [ "string_processing", "hug_parens_with_braces_and_square_brackets", - "fix_module_docstring_detection", "fix_type_expansion_split", "multiline_string_handling", "normalize_cr_newlines", From c8f2baab4f68039509eeaf56f370df65eee10953 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 7 Dec 2025 16:08:53 -0600 Subject: [PATCH 05/22] fix_type_expansion_split Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- docs/the_black_code_style/future_style.md | 1 - src/black/linegen.py | 3 +-- src/black/mode.py | 1 - src/black/resources/black.schema.json | 1 - 4 files changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index 4a1cbb9d5d8..378c6da4b02 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -13,7 +13,6 @@ experimental, feedback and issue reports are highly encouraged! Currently, the following features are included in the preview style: -- `fix_type_expansion_split`: Fix type expansions split in generic functions. - `multiline_string_handling`: Make expressions involving multiline strings more compact. ([see below](labels/multiline-string-handling)) - `normalize_cr_newlines`: Add `\r` style newlines to the potential newlines to diff --git a/src/black/linegen.py b/src/black/linegen.py index 8d6fa30c49d..6c1811cbf9a 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -893,8 +893,7 @@ def left_hand_split( current_leaves.append(leaf) if current_leaves is head_leaves: if leaf.type == leaf_type and ( - Preview.fix_type_expansion_split not in mode - or not (leaf_type == token.LPAR and depth > 0) + not (leaf_type == token.LPAR and depth > 0) ): matching_bracket = leaf current_leaves = body_leaves diff --git a/src/black/mode.py b/src/black/mode.py index 8233a1ec5e3..355b59b2380 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -226,7 +226,6 @@ class Preview(Enum): # for https://github.com/psf/black/issues/3117 to be fixed. string_processing = auto() hug_parens_with_braces_and_square_brackets = auto() - fix_type_expansion_split = auto() multiline_string_handling = auto() normalize_cr_newlines = auto() remove_parens_around_except_types = auto() diff --git a/src/black/resources/black.schema.json b/src/black/resources/black.schema.json index afef0dff228..289a26146af 100644 --- a/src/black/resources/black.schema.json +++ b/src/black/resources/black.schema.json @@ -82,7 +82,6 @@ "enum": [ "string_processing", "hug_parens_with_braces_and_square_brackets", - "fix_type_expansion_split", "multiline_string_handling", "normalize_cr_newlines", "remove_parens_around_except_types", From 561f2ced74995f2796c69447b39ce41290a88900 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 7 Dec 2025 16:10:35 -0600 Subject: [PATCH 06/22] multiline_string_handling Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- docs/the_black_code_style/future_style.md | 52 ----------------------- src/black/lines.py | 9 +--- src/black/mode.py | 1 - src/black/resources/black.schema.json | 1 - tests/test_black.py | 2 +- 5 files changed, 2 insertions(+), 63 deletions(-) diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index 378c6da4b02..7605bd30ef5 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -13,8 +13,6 @@ experimental, feedback and issue reports are highly encouraged! Currently, the following features are included in the preview style: -- `multiline_string_handling`: Make expressions involving multiline strings more - compact. ([see below](labels/multiline-string-handling)) - `normalize_cr_newlines`: Add `\r` style newlines to the potential newlines to normalize file newlines both from and to. - `remove_parens_around_except_types`: Remove parentheses around multiple exception @@ -29,56 +27,6 @@ Currently, the following features are included in the preview style: - `wrap_long_dict_values_in_parens`: Add parentheses around long values in dictionaries. ([see below](labels/wrap-long-dict-values)) -(labels/multiline-string-handling)= - -### Improved multiline string handling - -_Black_ is smarter when formatting multiline strings, especially in function arguments, -to avoid introducing extra line breaks. Previously, it would always consider multiline -strings as not fitting on a single line. With this new feature, _Black_ looks at the -context around the multiline string to decide if it should be inlined or split to a -separate line. For example, when a multiline string is passed to a function, _Black_ -will only split the multiline string if a line is too long or if multiple arguments are -being passed. - -For example, _Black_ will reformat - -```python -textwrap.dedent( - """\ - This is a - multiline string -""" -) -``` - -to: - -```python -textwrap.dedent("""\ - This is a - multiline string -""") -``` - -And: - -```python -MULTILINE = """ -foobar -""".replace( - "\n", "" -) -``` - -to: - -```python -MULTILINE = """ -foobar -""".replace("\n", "") -``` - (labels/wrap-long-dict-values)= ### Improved parentheses management in dicts diff --git a/src/black/lines.py b/src/black/lines.py index 37be855efd3..ed0354c2926 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -820,13 +820,6 @@ def is_line_short_enough(line: Line, *, mode: Mode, line_str: str = "") -> bool: if not line_str: line_str = line_to_string(line) - if Preview.multiline_string_handling not in mode: - return ( - str_width(line_str) <= mode.line_length - and "\n" not in line_str # multiline strings - and not line.contains_standalone_comments() - ) - if line.contains_standalone_comments(): return False if "\n" not in line_str: @@ -852,7 +845,7 @@ def is_line_short_enough(line: Line, *, mode: Mode, line_str: str = "") -> bool: for i, leaf in enumerate(line.leaves): if max_level_to_update == math.inf: had_comma: int | None = None - # Skip multiline_string_handling logic for leaves without bracket_depth + # Skip multiline string handling logic for leaves without bracket_depth # (e.g., newly created leaves not yet processed by bracket tracker) if not hasattr(leaf, "bracket_depth"): continue diff --git a/src/black/mode.py b/src/black/mode.py index 355b59b2380..1ceffc1d722 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -226,7 +226,6 @@ class Preview(Enum): # for https://github.com/psf/black/issues/3117 to be fixed. string_processing = auto() hug_parens_with_braces_and_square_brackets = auto() - multiline_string_handling = auto() normalize_cr_newlines = auto() remove_parens_around_except_types = auto() remove_parens_from_assignment_lhs = auto() diff --git a/src/black/resources/black.schema.json b/src/black/resources/black.schema.json index 289a26146af..707b26977f4 100644 --- a/src/black/resources/black.schema.json +++ b/src/black/resources/black.schema.json @@ -82,7 +82,6 @@ "enum": [ "string_processing", "hug_parens_with_braces_and_square_brackets", - "multiline_string_handling", "normalize_cr_newlines", "remove_parens_around_except_types", "remove_parens_from_assignment_lhs", diff --git a/tests/test_black.py b/tests/test_black.py index 32ed3b27a5c..065d6ef0db8 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -2416,7 +2416,7 @@ def test_cache_key(self) -> None: # If you are looking to remove one of these features, just # replace it with any other feature. values = [ - {Preview.multiline_string_handling}, + {Preview.wrap_comprehension_in}, {Preview.string_processing}, ] elif field.type is bool: From 4f4d8ce395784411faefc45d46f2fa3c4d3354fc Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 7 Dec 2025 16:16:57 -0600 Subject: [PATCH 07/22] normalize_cr_newlines Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- docs/the_black_code_style/future_style.md | 2 - scripts/fuzz.py | 5 -- src/black/__init__.py | 75 ++++++++--------------- src/black/mode.py | 1 - src/black/resources/black.schema.json | 1 - src/blackd/__init__.py | 9 --- tests/test_black.py | 2 +- 7 files changed, 26 insertions(+), 69 deletions(-) diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index 7605bd30ef5..cd703cd6d80 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -13,8 +13,6 @@ experimental, feedback and issue reports are highly encouraged! Currently, the following features are included in the preview style: -- `normalize_cr_newlines`: Add `\r` style newlines to the potential newlines to - normalize file newlines both from and to. - `remove_parens_around_except_types`: Remove parentheses around multiple exception types in `except` and `except*` without `as`. See PEP 758 for details. - `remove_parens_from_assignment_lhs`: Remove unnecessary parentheses from the left-hand diff --git a/scripts/fuzz.py b/scripts/fuzz.py index 44537c22d37..915a036b4ae 100644 --- a/scripts/fuzz.py +++ b/scripts/fuzz.py @@ -38,11 +38,6 @@ def test_idempotent_any_syntatically_valid_python( src_contents: str, mode: black.FileMode ) -> None: - if ( - "#\r" in src_contents or "\\\n" in src_contents - ) and black.Preview.normalize_cr_newlines not in mode: - return - # Before starting, let's confirm that the input string is valid Python: compile(src_contents, "", "exec") # else the bug is in hypothesmith diff --git a/src/black/__init__.py b/src/black/__init__.py index 180f5883b1d..b126f877bdb 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -1010,10 +1010,8 @@ def format_stdin_to_stdout( if content is None: src, encoding, newline = decode_bytes(sys.stdin.buffer.read(), mode) - elif Preview.normalize_cr_newlines in mode: - src, encoding, newline = content, "utf-8", "\n" else: - src, encoding, newline = content, "utf-8", "" + src, encoding, newline = content, "utf-8", "\n" dst = src try: @@ -1029,12 +1027,8 @@ def format_stdin_to_stdout( ) if write_back == WriteBack.YES: # Make sure there's a newline after the content - if Preview.normalize_cr_newlines in mode: - if dst and dst[-1] != "\n" and dst[-1] != "\r": - dst += newline - else: - if dst and dst[-1] != "\n": - dst += "\n" + if dst and dst[-1] != "\n" and dst[-1] != "\r": + dst += newline f.write(dst) elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF): now = datetime.now(timezone.utc) @@ -1224,16 +1218,13 @@ def f( def _format_str_once( src_contents: str, *, mode: Mode, lines: Collection[tuple[int, int]] = () ) -> str: - if Preview.normalize_cr_newlines in mode: - normalized_contents, _, newline_type = decode_bytes( - src_contents.encode("utf-8"), mode - ) + normalized_contents, _, newline_type = decode_bytes( + src_contents.encode("utf-8"), mode + ) - src_node = lib2to3_parse( - normalized_contents.lstrip(), target_versions=mode.target_versions - ) - else: - src_node = lib2to3_parse(src_contents.lstrip(), mode.target_versions) + src_node = lib2to3_parse( + normalized_contents.lstrip(), target_versions=mode.target_versions + ) dst_blocks: list[LinesBlock] = [] if mode.target_versions: @@ -1280,22 +1271,9 @@ def _format_str_once( for block in dst_blocks: dst_contents.extend(block.all_lines()) if not dst_contents: - if Preview.normalize_cr_newlines in mode: - if "\n" in normalized_contents: - return newline_type - else: - # 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"), mode - ) - if "\n" in normalized_content: - return newline - return "" - if Preview.normalize_cr_newlines in mode: - return "".join(dst_contents).replace("\n", newline_type) - else: - return "".join(dst_contents) + if "\n" in normalized_contents: + return newline_type + return "".join(dst_contents).replace("\n", newline_type) def decode_bytes(src: bytes, mode: Mode) -> tuple[FileContent, Encoding, NewLine]: @@ -1309,24 +1287,21 @@ def decode_bytes(src: bytes, mode: Mode) -> tuple[FileContent, Encoding, NewLine if not lines: return "", encoding, "\n" - if Preview.normalize_cr_newlines in mode: - if lines[0][-2:] == b"\r\n": - if b"\r" in lines[0][:-2]: - newline = "\r" - else: - newline = "\r\n" - elif lines[0][-1:] == b"\n": - if b"\r" in lines[0][:-1]: - newline = "\r" - else: - newline = "\n" + if lines[0][-2:] == b"\r\n": + if b"\r" in lines[0][:-2]: + newline = "\r" else: - if b"\r" in lines[0]: - newline = "\r" - else: - newline = "\n" + newline = "\r\n" + elif lines[0][-1:] == b"\n": + if b"\r" in lines[0][:-1]: + newline = "\r" + else: + newline = "\n" else: - newline = "\r\n" if lines[0][-2:] == b"\r\n" else "\n" + if b"\r" in lines[0]: + newline = "\r" + else: + newline = "\n" srcbuf.seek(0) with io.TextIOWrapper(srcbuf, encoding) as tiow: diff --git a/src/black/mode.py b/src/black/mode.py index 1ceffc1d722..e3ec3ae0649 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -226,7 +226,6 @@ class Preview(Enum): # for https://github.com/psf/black/issues/3117 to be fixed. string_processing = auto() hug_parens_with_braces_and_square_brackets = auto() - normalize_cr_newlines = auto() remove_parens_around_except_types = auto() remove_parens_from_assignment_lhs = auto() standardize_type_comments = auto() diff --git a/src/black/resources/black.schema.json b/src/black/resources/black.schema.json index 707b26977f4..3e411300fe4 100644 --- a/src/black/resources/black.schema.json +++ b/src/black/resources/black.schema.json @@ -82,7 +82,6 @@ "enum": [ "string_processing", "hug_parens_with_braces_and_square_brackets", - "normalize_cr_newlines", "remove_parens_around_except_types", "remove_parens_from_assignment_lhs", "standardize_type_comments", diff --git a/src/blackd/__init__.py b/src/blackd/__init__.py index 2f9a516d6e5..5587d349b26 100644 --- a/src/blackd/__init__.py +++ b/src/blackd/__init__.py @@ -130,15 +130,6 @@ async def handle(request: web.Request, executor: Executor) -> web.Response: executor, partial(black.format_file_contents, req_str, fast=fast, mode=mode) ) - if Preview.normalize_cr_newlines not in mode: - # Preserve CRLF line endings - nl = req_str.find("\n") - if nl > 0 and req_str[nl - 1] == "\r": - formatted_str = formatted_str.replace("\n", "\r\n") - # If, after swapping line endings, nothing changed, then say so - if formatted_str == req_str: - raise black.NothingChanged - # Put the source first line back req_str = header + req_str formatted_str = header + formatted_str diff --git a/tests/test_black.py b/tests/test_black.py index 065d6ef0db8..fbde56431c2 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -2081,7 +2081,7 @@ def test_carriage_return_edge_cases(self) -> None: ) def test_preview_newline_type_detection(self) -> None: - mode = Mode(enabled_features={Preview.normalize_cr_newlines}) + mode = Mode() newline_types = ["A\n", "A\r\n", "A\r"] for test_case in itertools.permutations(newline_types): assert black.format_str("".join(test_case), mode=mode) == test_case[0] * 3 From b195bc1d65010525e293719a7d8465f1770ce143 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 7 Dec 2025 16:18:13 -0600 Subject: [PATCH 08/22] remove_parens_around_except_types Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- docs/the_black_code_style/future_style.md | 2 -- src/black/linegen.py | 4 +--- src/black/mode.py | 1 - src/black/resources/black.schema.json | 1 - 4 files changed, 1 insertion(+), 7 deletions(-) diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index cd703cd6d80..823c16f509a 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -13,8 +13,6 @@ experimental, feedback and issue reports are highly encouraged! Currently, the following features are included in the preview style: -- `remove_parens_around_except_types`: Remove parentheses around multiple exception - types in `except` and `except*` without `as`. See PEP 758 for details. - `remove_parens_from_assignment_lhs`: Remove unnecessary parentheses from the left-hand side of assignments while preserving magic trailing commas and intentional multiline formatting. diff --git a/src/black/linegen.py b/src/black/linegen.py index 6c1811cbf9a..6ec75c32c82 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -1801,12 +1801,10 @@ def maybe_make_parens_invisible_in_atom( # and option to skip this check for `for` and `with` statements. not remove_brackets_around_comma and max_delimiter_priority_in_atom(node) >= COMMA_PRIORITY - # Skip this check in Preview mode in order to # Remove parentheses around multiple exception types in except and # except* without as. See PEP 758 for details. and not ( - Preview.remove_parens_around_except_types in mode - and Feature.UNPARENTHESIZED_EXCEPT_TYPES in features + Feature.UNPARENTHESIZED_EXCEPT_TYPES in features # is a tuple and is_tuple(node) # has a parent node diff --git a/src/black/mode.py b/src/black/mode.py index e3ec3ae0649..d88bce40a39 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -226,7 +226,6 @@ class Preview(Enum): # for https://github.com/psf/black/issues/3117 to be fixed. string_processing = auto() hug_parens_with_braces_and_square_brackets = auto() - remove_parens_around_except_types = auto() remove_parens_from_assignment_lhs = auto() standardize_type_comments = auto() wrap_comprehension_in = auto() diff --git a/src/black/resources/black.schema.json b/src/black/resources/black.schema.json index 3e411300fe4..58a420c5d8f 100644 --- a/src/black/resources/black.schema.json +++ b/src/black/resources/black.schema.json @@ -82,7 +82,6 @@ "enum": [ "string_processing", "hug_parens_with_braces_and_square_brackets", - "remove_parens_around_except_types", "remove_parens_from_assignment_lhs", "standardize_type_comments", "wrap_comprehension_in", From e3927b55c098b125d36d81b12ed5efb9ccd9e78c Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 7 Dec 2025 16:25:04 -0600 Subject: [PATCH 09/22] remove_parens_from_assignment_lhs Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- docs/the_black_code_style/future_style.md | 3 --- src/black/linegen.py | 4 +--- src/black/mode.py | 1 - src/black/resources/black.schema.json | 1 - 4 files changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index 823c16f509a..43bfa8b43c5 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -13,9 +13,6 @@ experimental, feedback and issue reports are highly encouraged! Currently, the following features are included in the preview style: -- `remove_parens_from_assignment_lhs`: Remove unnecessary parentheses from the left-hand - side of assignments while preserving magic trailing commas and intentional multiline - formatting. - `standardize_type_comments`: Format type comments which have zero or more spaces between `#` and `type:` or between `type:` and value to `# type: (value)`. - `wrap_comprehension_in`: Wrap the `in` clause of list and dictionary comprehensions diff --git a/src/black/linegen.py b/src/black/linegen.py index 6ec75c32c82..cca917d50fd 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -1518,10 +1518,8 @@ def normalize_invisible_parens( ): check_lpar = True - # Check for assignment LHS with preview feature enabled if ( - Preview.remove_parens_from_assignment_lhs in mode - and index == 0 + index == 0 and isinstance(child, Node) and child.type == syms.atom and node.type == syms.expr_stmt diff --git a/src/black/mode.py b/src/black/mode.py index d88bce40a39..5e62c2709b5 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -226,7 +226,6 @@ class Preview(Enum): # for https://github.com/psf/black/issues/3117 to be fixed. string_processing = auto() hug_parens_with_braces_and_square_brackets = auto() - remove_parens_from_assignment_lhs = auto() standardize_type_comments = auto() wrap_comprehension_in = auto() wrap_long_dict_values_in_parens = auto() diff --git a/src/black/resources/black.schema.json b/src/black/resources/black.schema.json index 58a420c5d8f..091161f5c30 100644 --- a/src/black/resources/black.schema.json +++ b/src/black/resources/black.schema.json @@ -82,7 +82,6 @@ "enum": [ "string_processing", "hug_parens_with_braces_and_square_brackets", - "remove_parens_from_assignment_lhs", "standardize_type_comments", "wrap_comprehension_in", "wrap_long_dict_values_in_parens" From 55b902cc2a17bd7128f47f24e544ccbf512399f8 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 7 Dec 2025 16:26:10 -0600 Subject: [PATCH 10/22] standardize_type_comments Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- docs/the_black_code_style/future_style.md | 2 -- src/black/comments.py | 3 +-- src/black/mode.py | 1 - src/black/nodes.py | 19 +++++-------------- src/black/resources/black.schema.json | 1 - 5 files changed, 6 insertions(+), 20 deletions(-) diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index 43bfa8b43c5..b7c75687b3f 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -13,8 +13,6 @@ experimental, feedback and issue reports are highly encouraged! Currently, the following features are included in the preview style: -- `standardize_type_comments`: Format type comments which have zero or more spaces - between `#` and `type:` or between `type:` and value to `# type: (value)`. - `wrap_comprehension_in`: Wrap the `in` clause of list and dictionary comprehensions across lines if it would otherwise exceed the maximum line length. - `wrap_long_dict_values_in_parens`: Add parentheses around long values in dictionaries. diff --git a/src/black/comments.py b/src/black/comments.py index 55251a24b5d..5eb46f687e6 100644 --- a/src/black/comments.py +++ b/src/black/comments.py @@ -177,8 +177,7 @@ def make_comment(content: str, mode: Mode) -> str: ): content = " " + content[1:] # Replace NBSP by a simple space if ( - Preview.standardize_type_comments in mode - and content + content and "\N{NO-BREAK SPACE}" not in content and is_type_comment_string("#" + content, mode=mode) ): diff --git a/src/black/mode.py b/src/black/mode.py index 5e62c2709b5..c2d98d92781 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -226,7 +226,6 @@ class Preview(Enum): # for https://github.com/psf/black/issues/3117 to be fixed. string_processing = auto() hug_parens_with_braces_and_square_brackets = auto() - standardize_type_comments = auto() wrap_comprehension_in = auto() wrap_long_dict_values_in_parens = auto() diff --git a/src/black/nodes.py b/src/black/nodes.py index 96bc20f20b3..2fb3913af54 100644 --- a/src/black/nodes.py +++ b/src/black/nodes.py @@ -8,7 +8,7 @@ from mypy_extensions import mypyc_attr from black.cache import CACHE_DIR -from black.mode import Mode, Preview +from black.mode import Mode from black.strings import get_string_prefix, has_triple_quotes from blib2to3 import pygram from blib2to3.pgen2 import token @@ -940,11 +940,7 @@ def is_type_comment(leaf: Leaf, mode: Mode) -> bool: def is_type_comment_string(value: str, mode: Mode) -> bool: - if Preview.standardize_type_comments in mode: - is_valid = value.startswith("#") and value[1:].lstrip().startswith("type:") - else: - is_valid = value.startswith("# type:") - return is_valid + return value.startswith("#") and value[1:].lstrip().startswith("type:") def is_type_ignore_comment(leaf: Leaf, mode: Mode) -> bool: @@ -959,14 +955,9 @@ def is_type_ignore_comment(leaf: Leaf, mode: Mode) -> bool: def is_type_ignore_comment_string(value: str, mode: Mode) -> bool: """Return True if the given string match with type comment with ignore annotation.""" - if Preview.standardize_type_comments in mode: - is_valid = is_type_comment_string(value, mode) and value.split(":", 1)[ - 1 - ].lstrip().startswith("ignore") - else: - is_valid = value.startswith("# type: ignore") - - return is_valid + return is_type_comment_string(value, mode) and value.split(":", 1)[ + 1 + ].lstrip().startswith("ignore") def wrap_in_parentheses(parent: Node, child: LN, *, visible: bool = True) -> None: diff --git a/src/black/resources/black.schema.json b/src/black/resources/black.schema.json index 091161f5c30..5fa62c65aa5 100644 --- a/src/black/resources/black.schema.json +++ b/src/black/resources/black.schema.json @@ -82,7 +82,6 @@ "enum": [ "string_processing", "hug_parens_with_braces_and_square_brackets", - "standardize_type_comments", "wrap_comprehension_in", "wrap_long_dict_values_in_parens" ] From e8b6091e67493e061c1af014595e7e940db9ce28 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 7 Dec 2025 16:58:10 -0600 Subject: [PATCH 11/22] Update changelog Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- CHANGES.md | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 76593423944..23ffde24cde 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,43 @@ # Change Log +## Unreleased + +### Highlights + +- Introduces the 2026 stable style (#4892), stabilizing the following changes: + - `multiline_string_handling`: Make expressions involving multiline strings more + compact. (#1879) + - `wrap_long_dict_values_in_parens`: Add parentheses around long values in + dictionaries. (#3440) + - `wrap_comprehension_in`: Wrap the `in` clause of list and dictionary comprehensions + across lines if it would otherwise exceed the maximum line length. (#4699) + - `remove_parens_from_assignment_lhs`: Remove unnecessary parentheses from the + left-hand side of assignments while preserving magic trailing commas and intentional + multiline formatting. (#4865) + - `fix_type_expansion_split`: Fix type expansions split in generic functions. (#4777) + - `standardize_type_comments`: Format type comments which have zero or more spaces + between `#` and `type:` or between `type:` and value to `# type: (value)`. (#4645) + - `always_one_newline_after_import`: Always force one blank line after import + statements, except when the line after the import is a comment or an import + statement. (#4489) + - `remove_parens_around_except_types`: Remove parentheses around multiple exception + types in `except` and `except*` without `as`. See PEP 758 for details. (#4720) + - `fix_module_docstring_detection`: Fix module docstrings being treated as normal + strings if preceded by comments. (#4764) + - `normalize_cr_newlines`: Add `\r` style newlines to the potential newlines to + normalize file newlines both from and to. (#4710) + - `fix_fmt_skip_in_one_liners`: Fix `# fmt: skip` behavior on one-liner declarations, + such as `def foo(): return "mock" # fmt: skip`, where previously the declaration + would have been incorrectly collapsed. (#4800) + ## 25.12.0 +Please test out the draft 2026 style in version 26.1a1 and +[share your feedback](https://github.com/psf/black/issues/4875)! This style will be +finalized in the January release (26.1.0). This release (25.12.0) will still produce the +2025 style. Most but not all of the changes in `--preview` will be in the 2026 stable +style. + ### Highlights - Black no longer supports running with Python 3.9 (#4842) @@ -25,14 +61,10 @@ ### Packaging - - - Releases now include arm64 Windows binaries and wheels (#4814) ### Integrations - - - Add `output-file` input to GitHub Action `psf/black` to write formatter output to a file for artifact capture and log cleanliness (#4824) From 0e2409f5578cc690e938d42fb77aa9137bf096de Mon Sep 17 00:00:00 2001 From: Kai Sforza Date: Sun, 7 Dec 2025 17:22:33 -0600 Subject: [PATCH 12/22] Regenerate `_width_table.py` & add tests for the Khmer language (#4253) Co-authored-by: cobalt <61329810+cobaltt7@users.noreply.github.com> Closes #4235. --- CHANGES.md | 4 + src/black/_width_table.py | 394 ++---------------- .../preview_long_strings__east_asian_width.py | 31 ++ 3 files changed, 59 insertions(+), 370 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 23ffde24cde..71c62b30ac2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -30,6 +30,10 @@ such as `def foo(): return "mock" # fmt: skip`, where previously the declaration would have been incorrectly collapsed. (#4800) +### Stable style + +- Regenerated the `_width_table.py` and added tests for the Khmer language (#4253) + ## 25.12.0 Please test out the draft 2026 style in version 26.1a1 and diff --git a/src/black/_width_table.py b/src/black/_width_table.py index 5f6ff9febc3..517535701f7 100644 --- a/src/black/_width_table.py +++ b/src/black/_width_table.py @@ -1,182 +1,10 @@ # Generated by make_width_table.py -# wcwidth 0.2.6 -# Unicode 15.0.0 +# wcwidth 0.2.14 +# Unicode 17.0.0 from typing import Final WIDTH_TABLE: Final[list[tuple[int, int, int]]] = [ - (0, 0, 0), - (1, 31, -1), - (127, 159, -1), - (768, 879, 0), - (1155, 1161, 0), - (1425, 1469, 0), - (1471, 1471, 0), - (1473, 1474, 0), - (1476, 1477, 0), - (1479, 1479, 0), - (1552, 1562, 0), - (1611, 1631, 0), - (1648, 1648, 0), - (1750, 1756, 0), - (1759, 1764, 0), - (1767, 1768, 0), - (1770, 1773, 0), - (1809, 1809, 0), - (1840, 1866, 0), - (1958, 1968, 0), - (2027, 2035, 0), - (2045, 2045, 0), - (2070, 2073, 0), - (2075, 2083, 0), - (2085, 2087, 0), - (2089, 2093, 0), - (2137, 2139, 0), - (2200, 2207, 0), - (2250, 2273, 0), - (2275, 2306, 0), - (2362, 2362, 0), - (2364, 2364, 0), - (2369, 2376, 0), - (2381, 2381, 0), - (2385, 2391, 0), - (2402, 2403, 0), - (2433, 2433, 0), - (2492, 2492, 0), - (2497, 2500, 0), - (2509, 2509, 0), - (2530, 2531, 0), - (2558, 2558, 0), - (2561, 2562, 0), - (2620, 2620, 0), - (2625, 2626, 0), - (2631, 2632, 0), - (2635, 2637, 0), - (2641, 2641, 0), - (2672, 2673, 0), - (2677, 2677, 0), - (2689, 2690, 0), - (2748, 2748, 0), - (2753, 2757, 0), - (2759, 2760, 0), - (2765, 2765, 0), - (2786, 2787, 0), - (2810, 2815, 0), - (2817, 2817, 0), - (2876, 2876, 0), - (2879, 2879, 0), - (2881, 2884, 0), - (2893, 2893, 0), - (2901, 2902, 0), - (2914, 2915, 0), - (2946, 2946, 0), - (3008, 3008, 0), - (3021, 3021, 0), - (3072, 3072, 0), - (3076, 3076, 0), - (3132, 3132, 0), - (3134, 3136, 0), - (3142, 3144, 0), - (3146, 3149, 0), - (3157, 3158, 0), - (3170, 3171, 0), - (3201, 3201, 0), - (3260, 3260, 0), - (3263, 3263, 0), - (3270, 3270, 0), - (3276, 3277, 0), - (3298, 3299, 0), - (3328, 3329, 0), - (3387, 3388, 0), - (3393, 3396, 0), - (3405, 3405, 0), - (3426, 3427, 0), - (3457, 3457, 0), - (3530, 3530, 0), - (3538, 3540, 0), - (3542, 3542, 0), - (3633, 3633, 0), - (3636, 3642, 0), - (3655, 3662, 0), - (3761, 3761, 0), - (3764, 3772, 0), - (3784, 3790, 0), - (3864, 3865, 0), - (3893, 3893, 0), - (3895, 3895, 0), - (3897, 3897, 0), - (3953, 3966, 0), - (3968, 3972, 0), - (3974, 3975, 0), - (3981, 3991, 0), - (3993, 4028, 0), - (4038, 4038, 0), - (4141, 4144, 0), - (4146, 4151, 0), - (4153, 4154, 0), - (4157, 4158, 0), - (4184, 4185, 0), - (4190, 4192, 0), - (4209, 4212, 0), - (4226, 4226, 0), - (4229, 4230, 0), - (4237, 4237, 0), - (4253, 4253, 0), (4352, 4447, 2), - (4957, 4959, 0), - (5906, 5908, 0), - (5938, 5939, 0), - (5970, 5971, 0), - (6002, 6003, 0), - (6068, 6069, 0), - (6071, 6077, 0), - (6086, 6086, 0), - (6089, 6099, 0), - (6109, 6109, 0), - (6155, 6157, 0), - (6159, 6159, 0), - (6277, 6278, 0), - (6313, 6313, 0), - (6432, 6434, 0), - (6439, 6440, 0), - (6450, 6450, 0), - (6457, 6459, 0), - (6679, 6680, 0), - (6683, 6683, 0), - (6742, 6742, 0), - (6744, 6750, 0), - (6752, 6752, 0), - (6754, 6754, 0), - (6757, 6764, 0), - (6771, 6780, 0), - (6783, 6783, 0), - (6832, 6862, 0), - (6912, 6915, 0), - (6964, 6964, 0), - (6966, 6970, 0), - (6972, 6972, 0), - (6978, 6978, 0), - (7019, 7027, 0), - (7040, 7041, 0), - (7074, 7077, 0), - (7080, 7081, 0), - (7083, 7085, 0), - (7142, 7142, 0), - (7144, 7145, 0), - (7149, 7149, 0), - (7151, 7153, 0), - (7212, 7219, 0), - (7222, 7223, 0), - (7376, 7378, 0), - (7380, 7392, 0), - (7394, 7400, 0), - (7405, 7405, 0), - (7412, 7412, 0), - (7416, 7417, 0), - (7616, 7679, 0), - (8203, 8207, 0), - (8232, 8238, 0), - (8288, 8291, 0), - (8400, 8432, 0), (8986, 8987, 2), (9001, 9002, 2), (9193, 9196, 2), @@ -184,8 +12,10 @@ (9203, 9203, 2), (9725, 9726, 2), (9748, 9749, 2), + (9776, 9783, 2), (9800, 9811, 2), (9855, 9855, 2), + (9866, 9871, 2), (9875, 9875, 2), (9889, 9889, 2), (9898, 9899, 2), @@ -211,186 +41,34 @@ (11035, 11036, 2), (11088, 11088, 2), (11093, 11093, 2), - (11503, 11505, 0), - (11647, 11647, 0), - (11744, 11775, 0), (11904, 11929, 2), (11931, 12019, 2), (12032, 12245, 2), - (12272, 12283, 2), - (12288, 12329, 2), - (12330, 12333, 0), - (12334, 12350, 2), + (12272, 12329, 2), + (12336, 12350, 2), (12353, 12438, 2), - (12441, 12442, 0), (12443, 12543, 2), (12549, 12591, 2), (12593, 12686, 2), - (12688, 12771, 2), - (12784, 12830, 2), + (12688, 12773, 2), + (12783, 12830, 2), (12832, 12871, 2), - (12880, 19903, 2), - (19968, 42124, 2), + (12880, 42124, 2), (42128, 42182, 2), - (42607, 42610, 0), - (42612, 42621, 0), - (42654, 42655, 0), - (42736, 42737, 0), - (43010, 43010, 0), - (43014, 43014, 0), - (43019, 43019, 0), - (43045, 43046, 0), - (43052, 43052, 0), - (43204, 43205, 0), - (43232, 43249, 0), - (43263, 43263, 0), - (43302, 43309, 0), - (43335, 43345, 0), (43360, 43388, 2), - (43392, 43394, 0), - (43443, 43443, 0), - (43446, 43449, 0), - (43452, 43453, 0), - (43493, 43493, 0), - (43561, 43566, 0), - (43569, 43570, 0), - (43573, 43574, 0), - (43587, 43587, 0), - (43596, 43596, 0), - (43644, 43644, 0), - (43696, 43696, 0), - (43698, 43700, 0), - (43703, 43704, 0), - (43710, 43711, 0), - (43713, 43713, 0), - (43756, 43757, 0), - (43766, 43766, 0), - (44005, 44005, 0), - (44008, 44008, 0), - (44013, 44013, 0), (44032, 55203, 2), (63744, 64255, 2), - (64286, 64286, 0), - (65024, 65039, 0), (65040, 65049, 2), - (65056, 65071, 0), (65072, 65106, 2), (65108, 65126, 2), (65128, 65131, 2), (65281, 65376, 2), (65504, 65510, 2), - (66045, 66045, 0), - (66272, 66272, 0), - (66422, 66426, 0), - (68097, 68099, 0), - (68101, 68102, 0), - (68108, 68111, 0), - (68152, 68154, 0), - (68159, 68159, 0), - (68325, 68326, 0), - (68900, 68903, 0), - (69291, 69292, 0), - (69373, 69375, 0), - (69446, 69456, 0), - (69506, 69509, 0), - (69633, 69633, 0), - (69688, 69702, 0), - (69744, 69744, 0), - (69747, 69748, 0), - (69759, 69761, 0), - (69811, 69814, 0), - (69817, 69818, 0), - (69826, 69826, 0), - (69888, 69890, 0), - (69927, 69931, 0), - (69933, 69940, 0), - (70003, 70003, 0), - (70016, 70017, 0), - (70070, 70078, 0), - (70089, 70092, 0), - (70095, 70095, 0), - (70191, 70193, 0), - (70196, 70196, 0), - (70198, 70199, 0), - (70206, 70206, 0), - (70209, 70209, 0), - (70367, 70367, 0), - (70371, 70378, 0), - (70400, 70401, 0), - (70459, 70460, 0), - (70464, 70464, 0), - (70502, 70508, 0), - (70512, 70516, 0), - (70712, 70719, 0), - (70722, 70724, 0), - (70726, 70726, 0), - (70750, 70750, 0), - (70835, 70840, 0), - (70842, 70842, 0), - (70847, 70848, 0), - (70850, 70851, 0), - (71090, 71093, 0), - (71100, 71101, 0), - (71103, 71104, 0), - (71132, 71133, 0), - (71219, 71226, 0), - (71229, 71229, 0), - (71231, 71232, 0), - (71339, 71339, 0), - (71341, 71341, 0), - (71344, 71349, 0), - (71351, 71351, 0), - (71453, 71455, 0), - (71458, 71461, 0), - (71463, 71467, 0), - (71727, 71735, 0), - (71737, 71738, 0), - (71995, 71996, 0), - (71998, 71998, 0), - (72003, 72003, 0), - (72148, 72151, 0), - (72154, 72155, 0), - (72160, 72160, 0), - (72193, 72202, 0), - (72243, 72248, 0), - (72251, 72254, 0), - (72263, 72263, 0), - (72273, 72278, 0), - (72281, 72283, 0), - (72330, 72342, 0), - (72344, 72345, 0), - (72752, 72758, 0), - (72760, 72765, 0), - (72767, 72767, 0), - (72850, 72871, 0), - (72874, 72880, 0), - (72882, 72883, 0), - (72885, 72886, 0), - (73009, 73014, 0), - (73018, 73018, 0), - (73020, 73021, 0), - (73023, 73029, 0), - (73031, 73031, 0), - (73104, 73105, 0), - (73109, 73109, 0), - (73111, 73111, 0), - (73459, 73460, 0), - (73472, 73473, 0), - (73526, 73530, 0), - (73536, 73536, 0), - (73538, 73538, 0), - (78912, 78912, 0), - (78919, 78933, 0), - (92912, 92916, 0), - (92976, 92982, 0), - (94031, 94031, 0), - (94095, 94098, 0), (94176, 94179, 2), - (94180, 94180, 0), - (94192, 94193, 2), - (94208, 100343, 2), - (100352, 101589, 2), - (101632, 101640, 2), + (94194, 94198, 2), + (94208, 101589, 2), + (101631, 101662, 2), + (101760, 101874, 2), (110576, 110579, 2), (110581, 110587, 2), (110589, 110590, 2), @@ -400,32 +78,8 @@ (110933, 110933, 2), (110948, 110951, 2), (110960, 111355, 2), - (113821, 113822, 0), - (118528, 118573, 0), - (118576, 118598, 0), - (119143, 119145, 0), - (119163, 119170, 0), - (119173, 119179, 0), - (119210, 119213, 0), - (119362, 119364, 0), - (121344, 121398, 0), - (121403, 121452, 0), - (121461, 121461, 0), - (121476, 121476, 0), - (121499, 121503, 0), - (121505, 121519, 0), - (122880, 122886, 0), - (122888, 122904, 0), - (122907, 122913, 0), - (122915, 122916, 0), - (122918, 122922, 0), - (123023, 123023, 0), - (123184, 123190, 0), - (123566, 123566, 0), - (123628, 123631, 0), - (124140, 124143, 0), - (125136, 125142, 0), - (125252, 125258, 0), + (119552, 119638, 2), + (119648, 119670, 2), (126980, 126980, 2), (127183, 127183, 2), (127374, 127374, 2), @@ -443,7 +97,8 @@ (127951, 127955, 2), (127968, 127984, 2), (127988, 127988, 2), - (127992, 128062, 2), + (127992, 127994, 2), + (128000, 128062, 2), (128064, 128064, 2), (128066, 128252, 2), (128255, 128317, 2), @@ -456,7 +111,7 @@ (128640, 128709, 2), (128716, 128716, 2), (128720, 128722, 2), - (128725, 128727, 2), + (128725, 128728, 2), (128732, 128735, 2), (128747, 128748, 2), (128756, 128764, 2), @@ -466,13 +121,12 @@ (129340, 129349, 2), (129351, 129535, 2), (129648, 129660, 2), - (129664, 129672, 2), - (129680, 129725, 2), - (129727, 129733, 2), - (129742, 129755, 2), - (129760, 129768, 2), - (129776, 129784, 2), + (129664, 129674, 2), + (129678, 129734, 2), + (129736, 129736, 2), + (129741, 129756, 2), + (129759, 129770, 2), + (129775, 129784, 2), (131072, 196605, 2), (196608, 262141, 2), - (917760, 917999, 0), ] diff --git a/tests/data/cases/preview_long_strings__east_asian_width.py b/tests/data/cases/preview_long_strings__east_asian_width.py index 022b0452522..a886257526d 100644 --- a/tests/data/cases/preview_long_strings__east_asian_width.py +++ b/tests/data/cases/preview_long_strings__east_asian_width.py @@ -5,6 +5,20 @@ hangul = '코드포인트 수는 적으나 실제 터미널이나 에디터에서 렌더링될 땐 너무 길어서 줄바꿈이 필요한 문자열' hanzi = '中文測試:代碼點數量少,但在真正的終端模擬器或編輯器中呈現時太長,因此需要換行的字符串。' japanese = 'コードポイントの数は少ないが、実際の端末エミュレータやエディタでレンダリングされる時は長すぎる為、改行が要る文字列' +khmer = 'សម្រស់ទាវ២០២២ មិនធម្មតា ឥឡូវកំពុងរកតួ នេនទុំ និងពេជ្រ ប្រញាប់ឡើងទាន់គេមានបញ្ហាត្រូវថតឡើងវិញ ប្រញាប់ឡើងទាន់គេមានបញ្ហាត្រូវថតឡើងវិញ' +# Should stay the same +khmer_same = [ + "text, expected_language", + [ + ( + ( + "សម្រស់ទាវ២០២២ មិនធម្មតា ឥឡូវកំពុងរកតួ នេនទុំ និងពេជ្រ" + " ប្រញាប់ឡើងទាន់គេមានបញ្ហាត្រូវថតឡើងវិញ " + ), + "km", + ), # Khmer + ], +] # output @@ -24,3 +38,20 @@ "実際の端末エミュレータやエディタでレンダリングされる時は長すぎる為、" "改行が要る文字列" ) +khmer = ( + "សម្រស់ទាវ២០២២ មិនធម្មតា ឥឡូវកំពុងរកតួ នេនទុំ និងពេជ្រ" + " ប្រញាប់ឡើងទាន់គេមានបញ្ហាត្រូវថតឡើងវិញ ប្រញាប់ឡើងទាន់គេមានបញ្ហាត្រូវថតឡើងវិញ" +) +# Should stay the same +khmer_same = [ + "text, expected_language", + [ + ( + ( + "សម្រស់ទាវ២០២២ មិនធម្មតា ឥឡូវកំពុងរកតួ នេនទុំ និងពេជ្រ" + " ប្រញាប់ឡើងទាន់គេមានបញ្ហាត្រូវថតឡើងវិញ " + ), + "km", + ), # Khmer + ], +] \ No newline at end of file From b431e55ee828700104a9dbeb4e39d9678856ede3 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 7 Dec 2025 17:36:49 -0600 Subject: [PATCH 13/22] Update most tests Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- tests/data/cases/composition.py | 4 +--- tests/data/cases/composition_no_trailing_comma.py | 6 ++---- tests/data/cases/fmtonoff.py | 1 + tests/data/cases/fmtskip_nested_if.py | 1 - tests/data/cases/skip_magic_trailing_comma.py | 2 +- tests/data/line_ranges_formatted/basic.py | 1 - tests/test_black.py | 14 +++++++------- 7 files changed, 12 insertions(+), 17 deletions(-) diff --git a/tests/data/cases/composition.py b/tests/data/cases/composition.py index e429f15e669..0798d3f3b29 100644 --- a/tests/data/cases/composition.py +++ b/tests/data/cases/composition.py @@ -161,9 +161,7 @@ def tricky_asserts(self) -> None: 8 STORE_ATTR 0 (x) 10 LOAD_CONST 0 (None) 12 RETURN_VALUE - """ % ( - _C.__init__.__code__.co_firstlineno + 1, - ) + """ % (_C.__init__.__code__.co_firstlineno + 1,) assert ( expectedexpectedexpectedexpectedexpectedexpectedexpectedexpectedexpect diff --git a/tests/data/cases/composition_no_trailing_comma.py b/tests/data/cases/composition_no_trailing_comma.py index f17b89dea8d..03b249e2556 100644 --- a/tests/data/cases/composition_no_trailing_comma.py +++ b/tests/data/cases/composition_no_trailing_comma.py @@ -181,9 +181,9 @@ def tricky_asserts(self) -> None: ) - # output + class C: def test(self) -> None: with patch("black.out", print): @@ -347,9 +347,7 @@ def tricky_asserts(self) -> None: 8 STORE_ATTR 0 (x) 10 LOAD_CONST 0 (None) 12 RETURN_VALUE - """ % ( - _C.__init__.__code__.co_firstlineno + 1, - ) + """ % (_C.__init__.__code__.co_firstlineno + 1,) assert ( expectedexpectedexpectedexpectedexpectedexpectedexpectedexpectedexpect diff --git a/tests/data/cases/fmtonoff.py b/tests/data/cases/fmtonoff.py index 56d99b8caf5..9d4e9999907 100644 --- a/tests/data/cases/fmtonoff.py +++ b/tests/data/cases/fmtonoff.py @@ -250,6 +250,7 @@ def single_literal_yapf_disable(): from third_party import X, Y, Z from library import some_connection, some_decorator + # fmt: off from third_party import (X, Y, Z) diff --git a/tests/data/cases/fmtskip_nested_if.py b/tests/data/cases/fmtskip_nested_if.py index 1a436eb3dc3..7e2cb4a40c3 100644 --- a/tests/data/cases/fmtskip_nested_if.py +++ b/tests/data/cases/fmtskip_nested_if.py @@ -1,4 +1,3 @@ -# flags: --preview --no-preview-line-length-1 class ClassWithALongName: Constant1 = 1 Constant2 = 2 diff --git a/tests/data/cases/skip_magic_trailing_comma.py b/tests/data/cases/skip_magic_trailing_comma.py index 4dda5df40f0..1ef34af4f11 100644 --- a/tests/data/cases/skip_magic_trailing_comma.py +++ b/tests/data/cases/skip_magic_trailing_comma.py @@ -70,6 +70,6 @@ func1(arg1).func2(arg2).func3(arg3).func4(arg4).func5(arg5) -(a, b, c, d) = func1(arg1) and func2(arg2) +a, b, c, d = func1(arg1) and func2(arg2) func(argument1, (one, two), argument4, argument5, argument6) diff --git a/tests/data/line_ranges_formatted/basic.py b/tests/data/line_ranges_formatted/basic.py index b419b1f16ae..9d5567d36b6 100644 --- a/tests/data/line_ranges_formatted/basic.py +++ b/tests/data/line_ranges_formatted/basic.py @@ -5,7 +5,6 @@ Literal, ) - # fmt: off class Unformatted: def should_also_work(self): diff --git a/tests/test_black.py b/tests/test_black.py index fbde56431c2..c5e0993450b 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -2059,17 +2059,17 @@ def test_carriage_return_edge_cases(self) -> None: "try:\\\r# type: ignore\n pass\nfinally:\n pass\n", mode=black.FileMode(), ) - == "try: # type: ignore\n pass\nfinally:\n pass\n" + == "try: # type: ignore\r pass\rfinally:\r pass\r" ) - assert black.format_str("{\r}", mode=black.FileMode()) == "{}\n" - assert black.format_str("pass #\r#\n", mode=black.FileMode()) == "pass #\n#\n" + assert black.format_str("{\r}", mode=black.FileMode()) == "{}\r" + assert black.format_str("pass #\r#\n", mode=black.FileMode()) == "pass #\r#\r" - assert black.format_str("x=\\\r\n1", mode=black.FileMode()) == "x = 1\n" + assert black.format_str("x=\\\r\n1", mode=black.FileMode()) == "x = 1\r\n" assert black.format_str("x=\\\n1", mode=black.FileMode()) == "x = 1\n" - assert black.format_str("x=\\\r1", mode=black.FileMode()) == "x = 1\n" + assert black.format_str("x=\\\r1", mode=black.FileMode()) == "x = 1\r" assert ( black.format_str("class A\\\r\n:...", mode=black.FileMode()) - == "class A: ...\n" + == "class A: ...\r\n" ) assert ( black.format_str("class A\\\n:...", mode=black.FileMode()) @@ -2077,7 +2077,7 @@ def test_carriage_return_edge_cases(self) -> None: ) assert ( black.format_str("class A\\\r:...", mode=black.FileMode()) - == "class A: ...\n" + == "class A: ...\r" ) def test_preview_newline_type_detection(self) -> None: From 2fd8bc6fba31be0ed2d33b9fd3322e4d92808cae Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 7 Dec 2025 17:27:15 -0600 Subject: [PATCH 14/22] Remove unused imports Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- src/black/comments.py | 2 +- src/black/lines.py | 2 +- src/blackd/__init__.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/black/comments.py b/src/black/comments.py index 5eb46f687e6..338d8accf45 100644 --- a/src/black/comments.py +++ b/src/black/comments.py @@ -4,7 +4,7 @@ from functools import lru_cache from typing import Final, Union -from black.mode import Mode, Preview +from black.mode import Mode from black.nodes import ( CLOSING_BRACKETS, OPENING_BRACKETS, diff --git a/src/black/lines.py b/src/black/lines.py index ed0354c2926..253c0e58e1a 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -5,7 +5,7 @@ from typing import Optional, TypeVar, Union, cast from black.brackets import COMMA_PRIORITY, DOT_PRIORITY, BracketTracker -from black.mode import Mode, Preview +from black.mode import Mode from black.nodes import ( BRACKETS, CLOSING_BRACKETS, diff --git a/src/blackd/__init__.py b/src/blackd/__init__.py index 5587d349b26..432ca81a5ae 100644 --- a/src/blackd/__init__.py +++ b/src/blackd/__init__.py @@ -22,7 +22,6 @@ import black from _black_version import version as __version__ from black.concurrency import maybe_install_uvloop -from black.mode import Preview # This is used internally by tests to shut down the server prematurely _stop_signal = asyncio.Event() From 36378d1e0e9481b09ba10b2cbf60c7f3a043a594 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 7 Dec 2025 17:39:02 -0600 Subject: [PATCH 15/22] Update changelog Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- CHANGES.md | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 71c62b30ac2..efd11f13bab 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,30 +5,26 @@ ### Highlights - Introduces the 2026 stable style (#4892), stabilizing the following changes: - - `multiline_string_handling`: Make expressions involving multiline strings more - compact. (#1879) - - `wrap_long_dict_values_in_parens`: Add parentheses around long values in - dictionaries. (#3440) - - `wrap_comprehension_in`: Wrap the `in` clause of list and dictionary comprehensions - across lines if it would otherwise exceed the maximum line length. (#4699) - - `remove_parens_from_assignment_lhs`: Remove unnecessary parentheses from the - left-hand side of assignments while preserving magic trailing commas and intentional - multiline formatting. (#4865) - - `fix_type_expansion_split`: Fix type expansions split in generic functions. (#4777) - - `standardize_type_comments`: Format type comments which have zero or more spaces - between `#` and `type:` or between `type:` and value to `# type: (value)`. (#4645) - `always_one_newline_after_import`: Always force one blank line after import statements, except when the line after the import is a comment or an import statement. (#4489) - - `remove_parens_around_except_types`: Remove parentheses around multiple exception - types in `except` and `except*` without `as`. See PEP 758 for details. (#4720) + - `fix_fmt_skip_in_one_liners`: Fix `# fmt: skip` behavior on one-liner declarations, + such as `def foo(): return "mock" # fmt: skip`, where previously the declaration + would have been incorrectly collapsed. (#4800) - `fix_module_docstring_detection`: Fix module docstrings being treated as normal strings if preceded by comments. (#4764) + - `fix_type_expansion_split`: Fix type expansions split in generic functions. (#4777) + - `multiline_string_handling`: Make expressions involving multiline strings more + compact. (#1879) - `normalize_cr_newlines`: Add `\r` style newlines to the potential newlines to normalize file newlines both from and to. (#4710) - - `fix_fmt_skip_in_one_liners`: Fix `# fmt: skip` behavior on one-liner declarations, - such as `def foo(): return "mock" # fmt: skip`, where previously the declaration - would have been incorrectly collapsed. (#4800) + - `remove_parens_around_except_types`: Remove parentheses around multiple exception + types in `except` and `except*` without `as`. See PEP 758 for details. (#4720) + - `remove_parens_from_assignment_lhs`: Remove unnecessary parentheses from the + left-hand side of assignments while preserving magic trailing commas and intentional + multiline formatting. (#4865) + - `standardize_type_comments`: Format type comments which have zero or more spaces + between `#` and `type:` or between `type:` and value to `# type: (value)`. (#4645) ### Stable style From 275834d982330a53931a70c404d27c69c4f27e38 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 7 Dec 2025 18:27:04 -0600 Subject: [PATCH 16/22] Fix newlines being added after imports with `# fmt: skip` on them Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- src/black/lines.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/black/lines.py b/src/black/lines.py index 253c0e58e1a..e69d724ad64 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -685,6 +685,7 @@ def _maybe_empty_lines(self, current_line: Line) -> tuple[int, int]: and self.previous_line.depth == 0 and current_line.depth == 0 and not current_line.is_import + and not current_line.is_fmt_pass_converted(first_leaf_matches=is_import) ): return 1, 0 From e8e64f1cc428cc64c600c73faeb37a2cc53b3340 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 7 Dec 2025 18:33:22 -0600 Subject: [PATCH 17/22] Remove newline on fmt:off line after import Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- tests/data/cases/fmtonoff.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/data/cases/fmtonoff.py b/tests/data/cases/fmtonoff.py index 9d4e9999907..56d99b8caf5 100644 --- a/tests/data/cases/fmtonoff.py +++ b/tests/data/cases/fmtonoff.py @@ -250,7 +250,6 @@ def single_literal_yapf_disable(): from third_party import X, Y, Z from library import some_connection, some_decorator - # fmt: off from third_party import (X, Y, Z) From 3e305ddb49e47f158eb681ed1374f9c36c36046c Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Fri, 19 Dec 2025 08:17:44 -0600 Subject: [PATCH 18/22] update changelog Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- CHANGES.md | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7595babd620..134b89ce2bc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,24 +7,24 @@ - Introduces the 2026 stable style (#4892), stabilizing the following changes: - `always_one_newline_after_import`: Always force one blank line after import statements, except when the line after the import is a comment or an import - statement. (#4489) + statement (#4489) - `fix_fmt_skip_in_one_liners`: Fix `# fmt: skip` behavior on one-liner declarations, such as `def foo(): return "mock" # fmt: skip`, where previously the declaration - would have been incorrectly collapsed. (#4800) + would have been incorrectly collapsed (#4800) - `fix_module_docstring_detection`: Fix module docstrings being treated as normal - strings if preceded by comments. (#4764) - - `fix_type_expansion_split`: Fix type expansions split in generic functions. (#4777) + strings if preceded by comments (#4764) + - `fix_type_expansion_split`: Fix type expansions split in generic functions (#4777) - `multiline_string_handling`: Make expressions involving multiline strings more - compact. (#1879) + compact (#1879) - `normalize_cr_newlines`: Add `\r` style newlines to the potential newlines to - normalize file newlines both from and to. (#4710) + normalize file newlines both from and to (#4710) - `remove_parens_around_except_types`: Remove parentheses around multiple exception - types in `except` and `except*` without `as`. See PEP 758 for details. (#4720) + types in `except` and `except*` without `as` (#4720) - `remove_parens_from_assignment_lhs`: Remove unnecessary parentheses from the left-hand side of assignments while preserving magic trailing commas and intentional - multiline formatting. (#4865) + multiline formatting (#4865) - `standardize_type_comments`: Format type comments which have zero or more spaces - between `#` and `type:` or between `type:` and value to `# type: (value)`. (#4645) + between `#` and `type:` or between `type:` and value to `# type: (value)` (#4645) ### Stable style @@ -32,11 +32,12 @@ ## 25.12.0 -Please test out the draft 2026 style in version 26.1a1 and -[share your feedback](https://github.com/psf/black/issues/4875)! This style will be -finalized in the January release (26.1.0). This release (25.12.0) will still produce the -2025 style. Most but not all of the changes in `--preview` will be in the 2026 stable -style. +Please test out the draft 2026 style in version 26.1a1! This style will be finalized in +the January release (26.1.0). Most of the changes in `--preview` will be in the 2026 +stable style, but not all. +[Please share your feedback!](https://github.com/psf/black/issues/4042) + +This release (25.12.0) will still produce the 2025 style. ### Highlights From 8ea125e68fa76ecc104d9efd44786a96fce2b303 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sat, 17 Jan 2026 15:15:43 -0600 Subject: [PATCH 19/22] Bump wcwidth version in pyproject.toml to avoid downgrading the used version in the future Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 19ff3c182fc..e5ec27afe38 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,7 +100,7 @@ diff-shades = [ ] diff-shades-comment = ["click>=8.1.7", "packaging>=22.0", "urllib3"] -width-table = ["wcwidth>=0.2.6"] +width-table = ["wcwidth>=0.2.14"] [project.scripts] black = "black:patched_main" From 4b821176b2d90b6021469482d1ff7ee4c9b42f07 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sat, 17 Jan 2026 20:51:18 -0600 Subject: [PATCH 20/22] Update flags in tests Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- tests/data/cases/comments5.py | 5 +---- .../cases/{docstring_preview.py => docstring2.py} | 0 tests/data/cases/fmtskip10.py | 1 - tests/data/cases/fmtskip12.py | 2 -- tests/data/cases/fmtskip13.py | 2 -- ...ort_line_collapse.py => import_line_collapse.py} | 1 - tests/data/cases/module_docstring_after_comment.py | 1 - ...ew_multiline_strings.py => multiline_strings.py} | 13 ++++--------- tests/data/cases/remove_except_types_parens.py | 2 +- .../cases/remove_except_types_parens_pre_py314.py | 2 +- tests/data/cases/remove_parens_from_lhs.py | 1 - ...ype_comments.py => standardize_type_comments.py} | 1 - tests/data/cases/type_expansion.py | 2 +- tests/data/cases/walrus_in_dict.py | 3 --- 14 files changed, 8 insertions(+), 28 deletions(-) rename tests/data/cases/{docstring_preview.py => docstring2.py} (100%) rename tests/data/cases/{preview_import_line_collapse.py => import_line_collapse.py} (99%) rename tests/data/cases/{preview_multiline_strings.py => multiline_strings.py} (97%) rename tests/data/cases/{preview_standardize_type_comments.py => standardize_type_comments.py} (95%) diff --git a/tests/data/cases/comments5.py b/tests/data/cases/comments5.py index 4270d3a09a2..f86e54732e8 100644 --- a/tests/data/cases/comments5.py +++ b/tests/data/cases/comments5.py @@ -56,13 +56,10 @@ def decorated1(): ... def decorated1(): ... -# Note: this is fixed in -# Preview.empty_lines_before_class_or_def_with_leading_comments. -# In the current style, the user will have to split those lines by hand. some_instruction -# This comment should be split from `some_instruction` by two lines but isn't. +# This comment should be split from `some_instruction` by two lines. def g(): ... diff --git a/tests/data/cases/docstring_preview.py b/tests/data/cases/docstring2.py similarity index 100% rename from tests/data/cases/docstring_preview.py rename to tests/data/cases/docstring2.py diff --git a/tests/data/cases/fmtskip10.py b/tests/data/cases/fmtskip10.py index f4f4981eb26..640ac4ddec9 100644 --- a/tests/data/cases/fmtskip10.py +++ b/tests/data/cases/fmtskip10.py @@ -1,4 +1,3 @@ -# flags: --preview def foo(): return "mock" # fmt: skip if True: print("yay") # fmt: skip for i in range(10): print(i) # fmt: skip diff --git a/tests/data/cases/fmtskip12.py b/tests/data/cases/fmtskip12.py index 3af6b4443a1..217fc2b5bb2 100644 --- a/tests/data/cases/fmtskip12.py +++ b/tests/data/cases/fmtskip12.py @@ -1,5 +1,3 @@ -# flags: --preview - with open("file.txt") as f: content = f.read() # fmt: skip # Ideally, only the last line would be ignored diff --git a/tests/data/cases/fmtskip13.py b/tests/data/cases/fmtskip13.py index f3abcba9219..1f2d53437cb 100644 --- a/tests/data/cases/fmtskip13.py +++ b/tests/data/cases/fmtskip13.py @@ -1,5 +1,3 @@ -# flags: --preview - t = ( {"foo": "very long string", "bar": "another very long string", "baz": "we should run out of space by now"}, # fmt: skip {"foo": "bar"}, diff --git a/tests/data/cases/preview_import_line_collapse.py b/tests/data/cases/import_line_collapse.py similarity index 99% rename from tests/data/cases/preview_import_line_collapse.py rename to tests/data/cases/import_line_collapse.py index 74ae349a2ca..2c6c915f539 100644 --- a/tests/data/cases/preview_import_line_collapse.py +++ b/tests/data/cases/import_line_collapse.py @@ -1,4 +1,3 @@ -# flags: --preview from middleman.authentication import validate_oauth_token diff --git a/tests/data/cases/module_docstring_after_comment.py b/tests/data/cases/module_docstring_after_comment.py index 6a755b36dd2..5709b92b197 100644 --- a/tests/data/cases/module_docstring_after_comment.py +++ b/tests/data/cases/module_docstring_after_comment.py @@ -1,4 +1,3 @@ -# flags: --preview #!/python # regression test for #4762 diff --git a/tests/data/cases/preview_multiline_strings.py b/tests/data/cases/multiline_strings.py similarity index 97% rename from tests/data/cases/preview_multiline_strings.py rename to tests/data/cases/multiline_strings.py index 9596357b287..10662d8dc38 100644 --- a/tests/data/cases/preview_multiline_strings.py +++ b/tests/data/cases/multiline_strings.py @@ -1,4 +1,3 @@ -# flags: --preview """cow say""", call(3, "dogsay", textwrap.dedent("""dove @@ -449,10 +448,8 @@ def foo(): """Sxxxxxxx xxxxxxxx, xxxxxxx xx xxxxxxxxx xxxxxxxxxxxxx xxxxxxx xxxxxxxxx xxx-xxxxxxxxxx xxxxxx xx xxx-xxxxxx""" ), - "xxxxxxxx": ( - """Sxxxxxxx xxxxxxxx, xxxxxxx xx xxxxxxxxx - xxxxxxxxxxxxx xxxxxxx xxxxxxxxx xxx-xxxxxxxxxx xxxxxx xx xxx-xxxxxx""" - ), + "xxxxxxxx": """Sxxxxxxx xxxxxxxx, xxxxxxx xx xxxxxxxxx + xxxxxxxxxxxxx xxxxxxx xxxxxxxxx xxx-xxxxxxxxxx xxxxxx xx xxx-xxxxxx""", }, } @@ -471,14 +468,12 @@ def foo(): a a""" ), - "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( - """ + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": """ a a a a -a""" - ), +a""", } a = ( diff --git a/tests/data/cases/remove_except_types_parens.py b/tests/data/cases/remove_except_types_parens.py index 71f2d229d3a..463be7e52d4 100644 --- a/tests/data/cases/remove_except_types_parens.py +++ b/tests/data/cases/remove_except_types_parens.py @@ -1,4 +1,4 @@ -# flags: --preview --minimum-version=3.14 +# flags: --minimum-version=3.14 # SEE PEP 758 FOR MORE DETAILS # remains unchanged try: diff --git a/tests/data/cases/remove_except_types_parens_pre_py314.py b/tests/data/cases/remove_except_types_parens_pre_py314.py index 9f3a3b25652..acd2458c8a9 100644 --- a/tests/data/cases/remove_except_types_parens_pre_py314.py +++ b/tests/data/cases/remove_except_types_parens_pre_py314.py @@ -1,4 +1,4 @@ -# flags: --preview --minimum-version=3.11 +# flags: --minimum-version=3.11 # SEE PEP 758 FOR MORE DETAILS # remains unchanged try: diff --git a/tests/data/cases/remove_parens_from_lhs.py b/tests/data/cases/remove_parens_from_lhs.py index 92d0731b87b..b172aa748f7 100644 --- a/tests/data/cases/remove_parens_from_lhs.py +++ b/tests/data/cases/remove_parens_from_lhs.py @@ -1,4 +1,3 @@ -# flags: --preview # Remove unnecessary parentheses from LHS of assignments diff --git a/tests/data/cases/preview_standardize_type_comments.py b/tests/data/cases/standardize_type_comments.py similarity index 95% rename from tests/data/cases/preview_standardize_type_comments.py rename to tests/data/cases/standardize_type_comments.py index 2ab45533cf9..0b3e63c92f7 100644 --- a/tests/data/cases/preview_standardize_type_comments.py +++ b/tests/data/cases/standardize_type_comments.py @@ -1,4 +1,3 @@ -# flags: --preview def foo( a, #type:int b, #type: str diff --git a/tests/data/cases/type_expansion.py b/tests/data/cases/type_expansion.py index 6cd7a0b7736..d5cbc6e7274 100644 --- a/tests/data/cases/type_expansion.py +++ b/tests/data/cases/type_expansion.py @@ -1,4 +1,4 @@ -# flags: --preview --minimum-version=3.12 +# flags: --minimum-version=3.12 def f1[T: (int, str)](a,): pass diff --git a/tests/data/cases/walrus_in_dict.py b/tests/data/cases/walrus_in_dict.py index 68ec5d5df2f..33af6ab907f 100644 --- a/tests/data/cases/walrus_in_dict.py +++ b/tests/data/cases/walrus_in_dict.py @@ -1,9 +1,6 @@ -# flags: --preview -# This is testing an issue that is specific to the preview style (wrap_long_dict_values_in_parens) { "is_update": (up := commit.hash in update_hashes) } # output -# This is testing an issue that is specific to the preview style (wrap_long_dict_values_in_parens) {"is_update": (up := commit.hash in update_hashes)} From b4865ffaf54eb4b4d498a0afb6a88ece94da9cba Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sat, 17 Jan 2026 20:58:52 -0600 Subject: [PATCH 21/22] change pathspec version in changelog Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 826a605dce2..ac995151407 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -36,7 +36,7 @@ The following change was not in any previous stable release: - Regenerated the `_width_table.py` and added tests for the Khmer language (#4253) -This release alo bumps `pathspec` to v1.0.0 and fixes inconsistencies with Git's +This release alo bumps `pathspec` to v1 and fixes inconsistencies with Git's `.gitignore` logic (#4958). Now, files will be ignored if a pattern matches them, even if the parent directory is directly unignored. For example, Black would previously format `exclude/not_this/foo.py` with this `.gitignore`: From ca22ceeae685d364c9921f50274b860248528b4b Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sat, 17 Jan 2026 22:10:36 -0600 Subject: [PATCH 22/22] Run release.py Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- CHANGES.md | 45 +-------------------- docs/integrations/source_version_control.md | 6 +-- docs/usage_and_configuration/the_basics.md | 6 +-- 3 files changed, 8 insertions(+), 49 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ac995151407..ee3cb917ff5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,14 +1,9 @@ # Change Log -## Unreleased - - +## 26.1.0 ### Highlights - - Introduces the 2026 stable style (#4892), stabilizing the following changes: - `always_one_newline_after_import`: Always force one blank line after import @@ -58,49 +53,14 @@ This new behavior matches Git. The leading `*/` are only necessary if you wish t matching subdirectories (like the previous behavior did), and not just matching root directories. -### Stable style - - - -### Preview style - - - -### Configuration - - - -### Packaging - - - -### Parser - - - -### Performance - - - ### Output - - -### _Blackd_ - - +- Explicitly shutdown the multiprocessing manager when run in diff mode too (#4952) ### Integrations - - - Upgraded PyPI upload workflow to use Trusted Publishing (#4611) -### Documentation - - - ## 25.12.0 ### Highlights @@ -109,7 +69,6 @@ directories. ### Stable style -- Fix Shutdown multiprocessing Manager in schedule_formatting (#4952) - Fix bug where comments preceding `# fmt: off`/`# fmt: on` blocks were incorrectly removed, particularly affecting Jupytext's `# %% [markdown]` comments (#4845) - Fix crash when multiple `# fmt: skip` comments are used in a multi-part if-clause, on diff --git a/docs/integrations/source_version_control.md b/docs/integrations/source_version_control.md index fb1ba3a1dbd..e3333fd978d 100644 --- a/docs/integrations/source_version_control.md +++ b/docs/integrations/source_version_control.md @@ -8,7 +8,7 @@ Use [pre-commit](https://pre-commit.com/). Once you repos: # Using this mirror lets us use mypyc-compiled black, which is about 2x faster - repo: https://github.com/psf/black-pre-commit-mirror - rev: 25.12.0 + rev: 26.1.0 hooks: - id: black # It is recommended to specify the latest version of Python @@ -35,7 +35,7 @@ include Jupyter Notebooks. To use this hook, simply replace the hook's `id: blac repos: # Using this mirror lets us use mypyc-compiled black, which is about 2x faster - repo: https://github.com/psf/black-pre-commit-mirror - rev: 25.12.0 + rev: 26.1.0 hooks: - id: black-jupyter # It is recommended to specify the latest version of Python @@ -66,7 +66,7 @@ Configure exclusions directly in `.pre-commit-config.yaml`: ```yaml repos: - repo: https://github.com/psf/black-pre-commit-mirror - rev: 25.12.0 + rev: 26.1.0 hooks: - id: black exclude: ^migrations/|^generated/ diff --git a/docs/usage_and_configuration/the_basics.md b/docs/usage_and_configuration/the_basics.md index 02d31311b81..72de5d28ef3 100644 --- a/docs/usage_and_configuration/the_basics.md +++ b/docs/usage_and_configuration/the_basics.md @@ -278,8 +278,8 @@ configuration file for consistent results across environments. ```console $ black --version -black, 25.12.0 (compiled: yes) -$ black --required-version 25.12.0 -c "format = 'this'" +black, 26.1.0 (compiled: yes) +$ black --required-version 26.1.0 -c "format = 'this'" format = "this" $ black --required-version 31.5b2 -c "still = 'beta?!'" Oh no! 💥 💔 💥 The required version does not match the running version! @@ -380,7 +380,7 @@ You can check the version of _Black_ you have installed using the `--version` fl ```console $ black --version -black, 25.12.0 +black, 26.1.0 ``` #### `--config`