From f1711f1d4f41bd6c47d32c7de9ed89703f585b95 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 23 Jan 2025 19:01:12 -0800 Subject: [PATCH 1/8] changelog --- CHANGES.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 416aabcdf13..195a1e02a15 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,13 +6,29 @@ +This release introduces the new 2025 stable style (#4558), stabilizing +the following changes: + +- Normalize casing of Unicode escape characters in strings to lowercase (#2916) +- Fix inconsistencies in whether certain strings are detected as docstrings (#4095) +- Consistently add trailing commas to typed function parameters (#4164) +- Remove redundant parentheses in if guards for case blocks (#4214) +- Add parentheses to if clauses in case blocks when the line is too long (#4269) +- Whitespace before `# fmt: skip` comments is no longer normalized (#4146) +- Fix line length computation for certain expressions that involve the power operator (#4154) +- Check if there is a newline before the terminating quotes of a docstring (#4185) +- Fix type annotation spacing between `*` and more complex type variable tuple (#4440) + +The following changes were not in any previous release: + +- Remove parentheses around sole list items (#4312) + ### Stable style - Fix formatting cells in IPython notebooks with magic methods and starting or trailing empty lines (#4484) - - Fix crash when formatting `with` statements containing tuple generators/unpacking (#4538) @@ -22,7 +38,6 @@ - Fix/remove string merging changing f-string quotes on f-strings with internal quotes (#4498) -- Remove parentheses around sole list items (#4312) - Collapse multiple empty lines after an import into one (#4489) - Prevent `string_processing` and `wrap_long_dict_values_in_parens` from removing parentheses around long dictionary values (#4377) From 8117db06ac7124af01a085e9d17f6fd1d8a1fb89 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 23 Jan 2025 19:20:28 -0800 Subject: [PATCH 2/8] remove preview features --- docs/the_black_code_style/current_style.md | 5 +++ docs/the_black_code_style/future_style.md | 21 ----------- src/black/comments.py | 8 +--- src/black/linegen.py | 22 +++-------- src/black/lines.py | 4 +- src/black/mode.py | 12 ------ src/black/nodes.py | 8 ++-- src/black/trans.py | 44 ++++------------------ tests/data/cases/module_docstring_2.py | 5 +-- tests/test_black.py | 4 +- 10 files changed, 29 insertions(+), 104 deletions(-) diff --git a/docs/the_black_code_style/current_style.md b/docs/the_black_code_style/current_style.md index 68cd6175e3e..e43a93451d5 100644 --- a/docs/the_black_code_style/current_style.md +++ b/docs/the_black_code_style/current_style.md @@ -250,6 +250,11 @@ exception of [capital "R" prefixes](#rstrings-and-rstrings), unicode literal mar (`u`) are removed because they are meaningless in Python 3, and in the case of multiple characters "r" is put first as in spoken language: "raw f-string". +Another area where Python allows multiple ways to format a string is escape sequences. +For example, `"\uabcd"` and `"\uABCD"` evaluate to the same string. _Black_ normalizes +such escape sequences to lowercase, but uses uppercase for `\N` named character escapes, +such as `"\N{MEETEI MAYEK LETTER HUK}"`. + The main reason to standardize on a single form of quotes is aesthetics. Having one kind of quotes everywhere reduces reader distraction. It will also enable a future version of _Black_ to merge consecutive string literals that ended up on the same line (see diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index bc3e233ed3d..480834c42e1 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -20,27 +20,6 @@ demoted from the `--preview` to the `--unstable` style, users can use the Currently, the following features are included in the preview style: -- `hex_codes_in_unicode_sequences`: normalize casing of Unicode escape characters in - strings -- `unify_docstring_detection`: fix inconsistencies in whether certain strings are - detected as docstrings -- `no_normalize_fmt_skip_whitespace`: whitespace before `# fmt: skip` comments is no - longer normalized -- `typed_params_trailing_comma`: consistently add trailing commas to typed function - parameters -- `is_simple_lookup_for_doublestar_expression`: fix line length computation for certain - expressions that involve the power operator -- `docstring_check_for_newline`: checks if there is a newline before the terminating - quotes of a docstring -- `remove_redundant_guard_parens`: Removes redundant parentheses in `if` guards for - `case` blocks. -- `parens_for_long_if_clauses_in_case_block`: Adds parentheses to `if` clauses in `case` - blocks when the line is too long -- `pep646_typed_star_arg_type_var_tuple`: fix type annotation spacing between * and more - complex type variable tuple (i.e. `def fn(*args: *tuple[*Ts, T]) -> None: pass`) -- `remove_lone_list_item_parens`: remove redundant parentheses around lone list items - (depends on unstable `hug_parens_with_braces_and_square_brackets` feature in some - cases) - `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 diff --git a/src/black/comments.py b/src/black/comments.py index b7aeca2a6a7..f42a51033db 100644 --- a/src/black/comments.py +++ b/src/black/comments.py @@ -4,7 +4,7 @@ from functools import lru_cache from typing import Final, Optional, Union -from black.mode import Mode, Preview +from black.mode import Mode from black.nodes import ( CLOSING_BRACKETS, STANDALONE_COMMENT, @@ -235,11 +235,7 @@ def convert_one_fmt_off_pair( standalone_comment_prefix += fmt_off_prefix hidden_value = comment.value + "\n" + hidden_value if is_fmt_skip: - hidden_value += ( - comment.leading_whitespace - if Preview.no_normalize_fmt_skip_whitespace in mode - else " " - ) + comment.value + hidden_value += comment.leading_whitespace + comment.value if hidden_value.endswith("\n"): # That happens when one of the `ignored_nodes` ended with a NEWLINE # leaf (possibly followed by a DEDENT). diff --git a/src/black/linegen.py b/src/black/linegen.py index 90ca5da8587..94487bb721d 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -414,10 +414,9 @@ def foo(a: (int), b: (float) = 7): ... yield from self.visit_default(node) def visit_STRING(self, leaf: Leaf) -> Iterator[Line]: - if Preview.hex_codes_in_unicode_sequences in self.mode: - normalize_unicode_escape_sequences(leaf) + normalize_unicode_escape_sequences(leaf) - if is_docstring(leaf, self.mode) and not re.search(r"\\\s*\n", leaf.value): + if is_docstring(leaf) and not re.search(r"\\\s*\n", leaf.value): # We're ignoring docstrings with backslash newline escapes because changing # indentation of those changes the AST representation of the code. if self.mode.string_normalization: @@ -488,10 +487,7 @@ def visit_STRING(self, leaf: Leaf) -> Iterator[Line]: and len(indent) + quote_len <= self.mode.line_length and not has_trailing_backslash ): - if ( - Preview.docstring_check_for_newline in self.mode - and leaf.value[-1 - quote_len] == "\n" - ): + if leaf.value[-1 - quote_len] == "\n": leaf.value = prefix + quote + docstring + quote else: leaf.value = prefix + quote + docstring + "\n" + indent + quote @@ -512,8 +508,7 @@ def visit_NUMBER(self, leaf: Leaf) -> Iterator[Line]: def visit_atom(self, node: Node) -> Iterator[Line]: """Visit any atom""" if ( - Preview.remove_lone_list_item_parens in self.mode - and len(node.children) == 3 + len(node.children) == 3 ): first = node.children[0] last = node.children[-1] @@ -602,8 +597,7 @@ def __post_init__(self) -> None: # PEP 634 self.visit_match_stmt = self.visit_match_case self.visit_case_block = self.visit_match_case - if Preview.remove_redundant_guard_parens in self.mode: - self.visit_guard = partial(v, keywords=Ø, parens={"if"}) + self.visit_guard = partial(v, keywords=Ø, parens={"if"}) def _hugging_power_ops_line_to_string( @@ -1133,10 +1127,7 @@ def _ensure_trailing_comma( # Don't add commas if we already have any commas if any( leaf.type == token.COMMA - and ( - Preview.typed_params_trailing_comma not in original.mode - or not is_part_of_annotation(leaf) - ) + and not is_part_of_annotation(leaf) for leaf in leaves ): return False @@ -1421,7 +1412,6 @@ def normalize_invisible_parens( # noqa: C901 if ( isinstance(child, Node) and child.type == syms.guard - and Preview.parens_for_long_if_clauses_in_case_block in mode ): normalize_invisible_parens( child, parens_after={"if"}, mode=mode, features=features diff --git a/src/black/lines.py b/src/black/lines.py index dd91bf898f2..2a719def3c9 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -204,9 +204,7 @@ def _is_triple_quoted_string(self) -> bool: @property def is_docstring(self) -> bool: """Is the line a docstring?""" - if Preview.unify_docstring_detection not in self.mode: - return self._is_triple_quoted_string - return bool(self) and is_docstring(self.leaves[0], self.mode) + return bool(self) and is_docstring(self.leaves[0]) @property def is_chained_assignment(self) -> bool: diff --git a/src/black/mode.py b/src/black/mode.py index 96f72cc2ded..a9135946c8a 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -196,24 +196,12 @@ def supports_feature(target_versions: set[TargetVersion], feature: Feature) -> b class Preview(Enum): """Individual preview style features.""" - hex_codes_in_unicode_sequences = auto() # NOTE: string_processing requires wrap_long_dict_values_in_parens # for https://github.com/psf/black/issues/3117 to be fixed. string_processing = auto() hug_parens_with_braces_and_square_brackets = auto() - unify_docstring_detection = auto() - no_normalize_fmt_skip_whitespace = auto() wrap_long_dict_values_in_parens = auto() multiline_string_handling = auto() - typed_params_trailing_comma = auto() - is_simple_lookup_for_doublestar_expression = auto() - docstring_check_for_newline = auto() - remove_redundant_guard_parens = auto() - parens_for_long_if_clauses_in_case_block = auto() - # NOTE: remove_lone_list_item_parens requires - # hug_parens_with_braces_and_square_brackets to remove parens in some cases - remove_lone_list_item_parens = auto() - pep646_typed_star_arg_type_var_tuple = auto() always_one_newline_after_import = auto() diff --git a/src/black/nodes.py b/src/black/nodes.py index f3688cd4f1b..00c0329dbf2 100644 --- a/src/black/nodes.py +++ b/src/black/nodes.py @@ -247,8 +247,7 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool, mode: Mode) -> str: # no and ( parent_type(prevp.parent) == syms.subscriptlist or ( - Preview.pep646_typed_star_arg_type_var_tuple in mode - and parent_type(prevp.parent) == syms.tname_star + parent_type(prevp.parent) == syms.tname_star ) ) ): @@ -551,7 +550,7 @@ def is_arith_like(node: LN) -> bool: } -def is_docstring(node: NL, mode: Mode) -> bool: +def is_docstring(node: NL) -> bool: if isinstance(node, Leaf): if node.type != token.STRING: return False @@ -561,8 +560,7 @@ def is_docstring(node: NL, mode: Mode) -> bool: return False if ( - Preview.unify_docstring_detection in mode - and node.parent + node.parent and node.parent.type == syms.simple_stmt and not node.parent.prev_sibling and node.parent.parent diff --git a/src/black/trans.py b/src/black/trans.py index b885bc65ace..a64cff8fe07 100644 --- a/src/black/trans.py +++ b/src/black/trans.py @@ -13,7 +13,7 @@ from black.comments import contains_pragma_comment from black.lines import Line, append_leaves -from black.mode import Feature, Mode, Preview +from black.mode import Feature, Mode from black.nodes import ( CLOSING_BRACKETS, OPENING_BRACKETS, @@ -82,18 +82,14 @@ def is_simple_lookup(index: int, kind: Literal[1, -1]) -> bool: # Brackets and parentheses indicate calls, subscripts, etc. ... # basically stuff that doesn't count as "simple". Only a NAME lookup # or dotted lookup (eg. NAME.NAME) is OK. - if Preview.is_simple_lookup_for_doublestar_expression not in mode: - return original_is_simple_lookup_func(line, index, kind) - + if kind == -1: + return handle_is_simple_look_up_prev( + line, index, {token.RPAR, token.RSQB} + ) else: - if kind == -1: - return handle_is_simple_look_up_prev( - line, index, {token.RPAR, token.RSQB} - ) - else: - return handle_is_simple_lookup_forward( - line, index, {token.LPAR, token.LSQB} - ) + return handle_is_simple_lookup_forward( + line, index, {token.LPAR, token.LSQB} + ) def is_simple_operand(index: int, kind: Literal[1, -1]) -> bool: # An operand is considered "simple" if's a NAME, a numeric CONSTANT, a simple @@ -139,30 +135,6 @@ def is_simple_operand(index: int, kind: Literal[1, -1]) -> bool: yield new_line -def original_is_simple_lookup_func( - line: Line, index: int, step: Literal[1, -1] -) -> bool: - if step == -1: - disallowed = {token.RPAR, token.RSQB} - else: - disallowed = {token.LPAR, token.LSQB} - - while 0 <= index < len(line.leaves): - current = line.leaves[index] - if current.type in disallowed: - return False - if current.type not in {token.NAME, token.DOT} or current.value == "for": - # If the current token isn't disallowed, we'll assume this is - # simple as only the disallowed tokens are semantically - # attached to this lookup expression we're checking. Also, - # stop early if we hit the 'for' bit of a comprehension. - return True - - index += step - - return True - - def handle_is_simple_look_up_prev(line: Line, index: int, disallowed: set[int]) -> bool: """ Handling the determination of is_simple_lookup for the lines prior to the doublestar diff --git a/tests/data/cases/module_docstring_2.py b/tests/data/cases/module_docstring_2.py index ac486096c02..b1d0aaf4ab9 100644 --- a/tests/data/cases/module_docstring_2.py +++ b/tests/data/cases/module_docstring_2.py @@ -1,7 +1,6 @@ -# flags: --preview """I am a very helpful module docstring. -With trailing spaces (only removed with unify_docstring_detection on): +With trailing spaces: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, @@ -39,7 +38,7 @@ # output """I am a very helpful module docstring. -With trailing spaces (only removed with unify_docstring_detection on): +With trailing spaces: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, diff --git a/tests/test_black.py b/tests/test_black.py index 98d8ff886d7..31bc34d4b89 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -2353,8 +2353,8 @@ 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.docstring_check_for_newline}, - {Preview.hex_codes_in_unicode_sequences}, + {Preview.multiline_string_handling}, + {Preview.string_processing}, ] elif field.type is bool: values = [True, False] From eb3d8dbf16fd99a25b984e678df6686c2748a7dd Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 23 Jan 2025 19:21:30 -0800 Subject: [PATCH 3/8] reformat --- src/black/linegen.py | 13 +++---------- src/black/nodes.py | 7 +------ src/black/trans.py | 4 +--- 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index 94487bb721d..6bf34b7690d 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -507,9 +507,7 @@ def visit_NUMBER(self, leaf: Leaf) -> Iterator[Line]: def visit_atom(self, node: Node) -> Iterator[Line]: """Visit any atom""" - if ( - len(node.children) == 3 - ): + if len(node.children) == 3: first = node.children[0] last = node.children[-1] if (first.type == token.LSQB and last.type == token.RSQB) or ( @@ -1126,9 +1124,7 @@ def _ensure_trailing_comma( return False # Don't add commas if we already have any commas if any( - leaf.type == token.COMMA - and not is_part_of_annotation(leaf) - for leaf in leaves + leaf.type == token.COMMA and not is_part_of_annotation(leaf) for leaf in leaves ): return False @@ -1409,10 +1405,7 @@ def normalize_invisible_parens( # noqa: C901 ) # Add parentheses around if guards in case blocks - if ( - isinstance(child, Node) - and child.type == syms.guard - ): + if isinstance(child, Node) and child.type == syms.guard: normalize_invisible_parens( child, parens_after={"if"}, mode=mode, features=features ) diff --git a/src/black/nodes.py b/src/black/nodes.py index 00c0329dbf2..4a9ba5f5565 100644 --- a/src/black/nodes.py +++ b/src/black/nodes.py @@ -244,12 +244,7 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool, mode: Mode) -> str: # no elif ( prevp.type == token.STAR and parent_type(prevp) == syms.star_expr - and ( - parent_type(prevp.parent) == syms.subscriptlist - or ( - parent_type(prevp.parent) == syms.tname_star - ) - ) + and parent_type(prevp.parent) in (syms.subscriptlist, syms.tname_star) ): # No space between typevar tuples or unpacking them. return NO diff --git a/src/black/trans.py b/src/black/trans.py index a64cff8fe07..fabc7051108 100644 --- a/src/black/trans.py +++ b/src/black/trans.py @@ -83,9 +83,7 @@ def is_simple_lookup(index: int, kind: Literal[1, -1]) -> bool: # basically stuff that doesn't count as "simple". Only a NAME lookup # or dotted lookup (eg. NAME.NAME) is OK. if kind == -1: - return handle_is_simple_look_up_prev( - line, index, {token.RPAR, token.RSQB} - ) + return handle_is_simple_look_up_prev(line, index, {token.RPAR, token.RSQB}) else: return handle_is_simple_lookup_forward( line, index, {token.LPAR, token.LSQB} From 0fb062136272f86d3e5a5e063a83704f712d54b5 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 23 Jan 2025 19:22:30 -0800 Subject: [PATCH 4/8] update schema --- src/black/resources/black.schema.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/black/resources/black.schema.json b/src/black/resources/black.schema.json index f1d471e7616..b9b61489136 100644 --- a/src/black/resources/black.schema.json +++ b/src/black/resources/black.schema.json @@ -79,20 +79,10 @@ "type": "array", "items": { "enum": [ - "hex_codes_in_unicode_sequences", "string_processing", "hug_parens_with_braces_and_square_brackets", - "unify_docstring_detection", - "no_normalize_fmt_skip_whitespace", "wrap_long_dict_values_in_parens", "multiline_string_handling", - "typed_params_trailing_comma", - "is_simple_lookup_for_doublestar_expression", - "docstring_check_for_newline", - "remove_redundant_guard_parens", - "parens_for_long_if_clauses_in_case_block", - "remove_lone_list_item_parens", - "pep646_typed_star_arg_type_var_tuple", "always_one_newline_after_import" ] }, From 3a140a53205aee05cef802e945f65bb8c44472d7 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 23 Jan 2025 19:30:53 -0800 Subject: [PATCH 5/8] fix a test --- tests/data/cases/{preview_cantfit.py => cantfit.py} | 1 - .../{docstring_newline_preview.py => docstring_newline.py} | 1 - tests/data/cases/fmtskip9.py | 1 - ...ormat_unicode_escape_seq.py => format_unicode_escape_seq.py} | 1 - tests/data/cases/funcdef_return_type_trailing_comma.py | 2 +- tests/data/cases/is_simple_lookup_for_doublestar_expression.py | 1 - ...s__type_annotations.py => long_strings__type_annotations.py} | 1 - tests/data/cases/no_blank_line_before_docstring.py | 1 - tests/data/cases/pattern_matching_with_if_stmt.py | 2 +- ...ype_var_tuple.py => pep646_typed_star_arg_type_var_tuple.py} | 2 +- ...lone_list_item_parens.py => remove_lone_list_item_parens.py} | 1 - tests/data/cases/remove_redundant_parens_in_case_guard.py | 2 +- tests/data/cases/typed_params_trailing_comma.py | 1 - tests/data/cases/walrus_in_dict.py | 1 - 14 files changed, 4 insertions(+), 14 deletions(-) rename tests/data/cases/{preview_cantfit.py => cantfit.py} (99%) rename tests/data/cases/{docstring_newline_preview.py => docstring_newline.py} (83%) rename tests/data/cases/{preview_format_unicode_escape_seq.py => format_unicode_escape_seq.py} (96%) rename tests/data/cases/{preview_long_strings__type_annotations.py => long_strings__type_annotations.py} (98%) rename tests/data/cases/{preview_pep646_typed_star_arg_type_var_tuple.py => pep646_typed_star_arg_type_var_tuple.py} (62%) rename tests/data/cases/{preview_remove_lone_list_item_parens.py => remove_lone_list_item_parens.py} (99%) diff --git a/tests/data/cases/preview_cantfit.py b/tests/data/cases/cantfit.py similarity index 99% rename from tests/data/cases/preview_cantfit.py rename to tests/data/cases/cantfit.py index 29789c7e653..f002326947d 100644 --- a/tests/data/cases/preview_cantfit.py +++ b/tests/data/cases/cantfit.py @@ -1,4 +1,3 @@ -# flags: --preview # long variable name this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = 0 this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = 1 # with a comment diff --git a/tests/data/cases/docstring_newline_preview.py b/tests/data/cases/docstring_newline.py similarity index 83% rename from tests/data/cases/docstring_newline_preview.py rename to tests/data/cases/docstring_newline.py index 5c129ca5f80..75b8db48175 100644 --- a/tests/data/cases/docstring_newline_preview.py +++ b/tests/data/cases/docstring_newline.py @@ -1,4 +1,3 @@ -# flags: --preview """ 87 characters ............................................................................ """ diff --git a/tests/data/cases/fmtskip9.py b/tests/data/cases/fmtskip9.py index 30085bdd973..d070a7f17eb 100644 --- a/tests/data/cases/fmtskip9.py +++ b/tests/data/cases/fmtskip9.py @@ -1,4 +1,3 @@ -# flags: --preview print () # fmt: skip print () # fmt:skip diff --git a/tests/data/cases/preview_format_unicode_escape_seq.py b/tests/data/cases/format_unicode_escape_seq.py similarity index 96% rename from tests/data/cases/preview_format_unicode_escape_seq.py rename to tests/data/cases/format_unicode_escape_seq.py index 65c3d8d166e..3440696c303 100644 --- a/tests/data/cases/preview_format_unicode_escape_seq.py +++ b/tests/data/cases/format_unicode_escape_seq.py @@ -1,4 +1,3 @@ -# flags: --preview x = "\x1F" x = "\\x1B" x = "\\\x1B" diff --git a/tests/data/cases/funcdef_return_type_trailing_comma.py b/tests/data/cases/funcdef_return_type_trailing_comma.py index 14fd763d9d1..6335cf73396 100644 --- a/tests/data/cases/funcdef_return_type_trailing_comma.py +++ b/tests/data/cases/funcdef_return_type_trailing_comma.py @@ -1,4 +1,4 @@ -# flags: --preview --minimum-version=3.10 +# flags: --minimum-version=3.10 # normal, short, function definition def foo(a, b) -> tuple[int, float]: ... diff --git a/tests/data/cases/is_simple_lookup_for_doublestar_expression.py b/tests/data/cases/is_simple_lookup_for_doublestar_expression.py index a0d2e2ba842..ae3643ba4e8 100644 --- a/tests/data/cases/is_simple_lookup_for_doublestar_expression.py +++ b/tests/data/cases/is_simple_lookup_for_doublestar_expression.py @@ -1,4 +1,3 @@ -# flags: --preview m2 = None if not isinstance(dist, Normal) else m** 2 + s * 2 m3 = None if not isinstance(dist, Normal) else m ** 2 + s * 2 m4 = None if not isinstance(dist, Normal) else m**2 + s * 2 diff --git a/tests/data/cases/preview_long_strings__type_annotations.py b/tests/data/cases/long_strings__type_annotations.py similarity index 98% rename from tests/data/cases/preview_long_strings__type_annotations.py rename to tests/data/cases/long_strings__type_annotations.py index 8beb877bdd1..45de882d02c 100644 --- a/tests/data/cases/preview_long_strings__type_annotations.py +++ b/tests/data/cases/long_strings__type_annotations.py @@ -1,4 +1,3 @@ -# flags: --preview def func( arg1, arg2, diff --git a/tests/data/cases/no_blank_line_before_docstring.py b/tests/data/cases/no_blank_line_before_docstring.py index ced125fef78..74d43cd7eaf 100644 --- a/tests/data/cases/no_blank_line_before_docstring.py +++ b/tests/data/cases/no_blank_line_before_docstring.py @@ -62,5 +62,4 @@ class MultilineDocstringsAsWell: class SingleQuotedDocstring: - "I'm a docstring but I don't even get triple quotes." diff --git a/tests/data/cases/pattern_matching_with_if_stmt.py b/tests/data/cases/pattern_matching_with_if_stmt.py index ff54af91771..1c5d58f16f2 100644 --- a/tests/data/cases/pattern_matching_with_if_stmt.py +++ b/tests/data/cases/pattern_matching_with_if_stmt.py @@ -1,4 +1,4 @@ -# flags: --preview --minimum-version=3.10 +# flags: --minimum-version=3.10 match match: case "test" if case != "not very loooooooooooooog condition": # comment pass diff --git a/tests/data/cases/preview_pep646_typed_star_arg_type_var_tuple.py b/tests/data/cases/pep646_typed_star_arg_type_var_tuple.py similarity index 62% rename from tests/data/cases/preview_pep646_typed_star_arg_type_var_tuple.py rename to tests/data/cases/pep646_typed_star_arg_type_var_tuple.py index fb79e9983b1..6dfb5445efe 100644 --- a/tests/data/cases/preview_pep646_typed_star_arg_type_var_tuple.py +++ b/tests/data/cases/pep646_typed_star_arg_type_var_tuple.py @@ -1,4 +1,4 @@ -# flags: --minimum-version=3.11 --preview +# flags: --minimum-version=3.11 def fn(*args: *tuple[*A, B]) -> None: diff --git a/tests/data/cases/preview_remove_lone_list_item_parens.py b/tests/data/cases/remove_lone_list_item_parens.py similarity index 99% rename from tests/data/cases/preview_remove_lone_list_item_parens.py rename to tests/data/cases/remove_lone_list_item_parens.py index ad7058ba179..8127e038e1c 100644 --- a/tests/data/cases/preview_remove_lone_list_item_parens.py +++ b/tests/data/cases/remove_lone_list_item_parens.py @@ -1,4 +1,3 @@ -# flags: --preview items = [(123)] items = [(True)] items = [(((((True)))))] diff --git a/tests/data/cases/remove_redundant_parens_in_case_guard.py b/tests/data/cases/remove_redundant_parens_in_case_guard.py index bec4a3c3fcd..4739fc5478e 100644 --- a/tests/data/cases/remove_redundant_parens_in_case_guard.py +++ b/tests/data/cases/remove_redundant_parens_in_case_guard.py @@ -1,4 +1,4 @@ -# flags: --minimum-version=3.10 --preview --line-length=79 +# flags: --minimum-version=3.10 --line-length=79 match 1: case _ if (True): diff --git a/tests/data/cases/typed_params_trailing_comma.py b/tests/data/cases/typed_params_trailing_comma.py index a53b908b18b..5bcdb941966 100644 --- a/tests/data/cases/typed_params_trailing_comma.py +++ b/tests/data/cases/typed_params_trailing_comma.py @@ -1,4 +1,3 @@ -# flags: --preview def long_function_name_goes_here( x: Callable[List[int]] ) -> Union[List[int], float, str, bytes, Tuple[int]]: diff --git a/tests/data/cases/walrus_in_dict.py b/tests/data/cases/walrus_in_dict.py index c91ad9e8611..0a2ff80426a 100644 --- a/tests/data/cases/walrus_in_dict.py +++ b/tests/data/cases/walrus_in_dict.py @@ -1,4 +1,3 @@ -# flags: --preview # This is testing an issue that is specific to the preview style { "is_update": (up := commit.hash in update_hashes) From 164b1ff570a8c189ada577734790e4b54ccfd9cc Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 23 Jan 2025 19:33:10 -0800 Subject: [PATCH 6/8] walrus_in_dict is still unstable --- tests/data/cases/walrus_in_dict.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/data/cases/walrus_in_dict.py b/tests/data/cases/walrus_in_dict.py index 0a2ff80426a..d231b45f4ef 100644 --- a/tests/data/cases/walrus_in_dict.py +++ b/tests/data/cases/walrus_in_dict.py @@ -1,8 +1,9 @@ -# This is testing an issue that is specific to the preview style +# flags: --unstable +# This is testing an issue that is specific to the unstable 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 +# This is testing an issue that is specific to the unstable style (wrap_long_dict_values_in_parens) {"is_update": (up := commit.hash in update_hashes)} From 1d150cc0ef5f169b9a01e768c2adb611a21ab70f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 23 Jan 2025 19:35:48 -0800 Subject: [PATCH 7/8] unused import --- src/black/nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/black/nodes.py b/src/black/nodes.py index 4a9ba5f5565..3b74e2db0be 100644 --- a/src/black/nodes.py +++ b/src/black/nodes.py @@ -14,7 +14,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 From 5f07d4b797fbd02acf07057ecc1bd9cca55a198b Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 23 Jan 2025 20:36:09 -0800 Subject: [PATCH 8/8] Fix masking strategy for ipynb --- src/black/handle_ipynb_magics.py | 25 +++++++++++++++++-------- tests/test_ipynb.py | 4 ++-- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/black/handle_ipynb_magics.py b/src/black/handle_ipynb_magics.py index 6b3367ca9db..dd680bffffb 100644 --- a/src/black/handle_ipynb_magics.py +++ b/src/black/handle_ipynb_magics.py @@ -43,7 +43,6 @@ "time", "timeit", )) -TOKEN_HEX = secrets.token_hex @dataclasses.dataclass(frozen=True) @@ -160,7 +159,7 @@ def mask_cell(src: str) -> tuple[str, list[Replacement]]: becomes - "25716f358c32750e" + b"25716f358c32750" 'foo' The replacements are returned, along with the transformed code. @@ -192,6 +191,18 @@ def mask_cell(src: str) -> tuple[str, list[Replacement]]: return transformed, replacements +def create_token(n_chars: int) -> str: + """Create a randomly generated token that is n_chars characters long.""" + assert n_chars > 0 + n_bytes = max(n_chars // 2 - 1, 1) + token = secrets.token_hex(n_bytes) + if len(token) + 3 > n_chars: + token = token[:-1] + # We use a bytestring so that the string does not get interpreted + # as a docstring. + return f'b"{token}"' + + def get_token(src: str, magic: str) -> str: """Return randomly generated token to mask IPython magic with. @@ -201,11 +212,11 @@ def get_token(src: str, magic: str) -> str: not already present anywhere else in the cell. """ assert magic - nbytes = max(len(magic) // 2 - 1, 1) - token = TOKEN_HEX(nbytes) + n_chars = len(magic) + token = create_token(n_chars) counter = 0 while token in src: - token = TOKEN_HEX(nbytes) + token = create_token(n_chars) counter += 1 if counter > 100: raise AssertionError( @@ -213,9 +224,7 @@ def get_token(src: str, magic: str) -> str: "Please report a bug on https://github.com/psf/black/issues. " f"The magic might be helpful: {magic}" ) from None - if len(token) + 2 < len(magic): - token = f"{token}." - return f'"{token}"' + return token def replace_cell_magics(src: str) -> tuple[str, list[Replacement]]: diff --git a/tests/test_ipynb.py b/tests/test_ipynb.py index 46b1c9a9903..12afb971e2c 100644 --- a/tests/test_ipynb.py +++ b/tests/test_ipynb.py @@ -541,8 +541,8 @@ def test_ipynb_and_pyi_flags() -> None: def test_unable_to_replace_magics(monkeypatch: MonkeyPatch) -> None: - src = "%%time\na = 'foo'" - monkeypatch.setattr("black.handle_ipynb_magics.TOKEN_HEX", lambda _: "foo") + src = '%%time\na = b"foo"' + monkeypatch.setattr("secrets.token_hex", lambda _: "foo") with pytest.raises( AssertionError, match="Black was not able to replace IPython magic" ):