From a58d11225c1be59df18afe7a62bc1b1080516b94 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+RedGuy12@users.noreply.github.com> Date: Thu, 6 Jun 2024 15:50:38 -0500 Subject: [PATCH 01/18] fix(preview): Don't remove parenthesis around long dictionary values --- src/black/lines.py | 14 ++ src/black/trans.py | 16 +- .../data/cases/preview_long_dict_values_2.py | 147 ++++++++++++++++++ 3 files changed, 171 insertions(+), 6 deletions(-) create mode 100644 tests/data/cases/preview_long_dict_values_2.py diff --git a/src/black/lines.py b/src/black/lines.py index 6b65372fb3f..1cf4d739e24 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -954,6 +954,20 @@ def can_omit_invisible_parens( ): closing_bracket = leaf + if ( + # Keep parenthesized dictionary values + len(rhs.head.leaves) >= 2 + and rhs.head.leaves[-1].type == token.LPAR + and rhs.head.leaves[-2].type == token.COLON + # Unless key is a function call with trailing comma + and not( + len(rhs.head.leaves) >= 4 + and rhs.head.leaves[-3].type == token.RPAR + and rhs.head.leaves[-4].type == token.COMMA + ) + ): + return False + bt = line.bracket_tracker if not bt.delimiters: # Without delimiters the optional parentheses are useless. diff --git a/src/black/trans.py b/src/black/trans.py index 29a978c6b71..f2c679bdc83 100644 --- a/src/black/trans.py +++ b/src/black/trans.py @@ -886,6 +886,7 @@ class StringParenStripper(StringTransformer): The line contains a string which is surrounded by parentheses and: - The target string is NOT the only argument to a function call. - The target string is NOT a "pointless" string. + - The target string is NOT a dictionary value. - If the target string contains a PERCENT, the brackets are not preceded or followed by an operator with higher precedence than PERCENT. @@ -933,11 +934,14 @@ def do_match(self, line: Line) -> TMatchResult: ): continue - # That LPAR should NOT be preceded by a function name or a closing - # bracket (which could be a function which returns a function or a - # list/dictionary that contains a function)... + # That LPAR should NOT be preceded by a colon (which could be a + # dictionary value), function name, or a closing bracket (which + # could be a function returning a function or a list/dictionary + # containing a function)... if is_valid_index(idx - 2) and ( - LL[idx - 2].type == token.NAME or LL[idx - 2].type in CLOSING_BRACKETS + LL[idx - 2].type == token.COLON + or LL[idx - 2].type == token.NAME + or LL[idx - 2].type in CLOSING_BRACKETS ): continue @@ -2263,12 +2267,12 @@ def do_transform( elif right_leaves and right_leaves[-1].type == token.RPAR: # Special case for lambda expressions as dict's value, e.g.: # my_dict = { - # "key": lambda x: f"formatted: {x}, + # "key": lambda x: f"formatted: {x}", # } # After wrapping the dict's value with parentheses, the string is # followed by a RPAR but its opening bracket is lambda's, not # the string's: - # "key": (lambda x: f"formatted: {x}), + # "key": (lambda x: f"formatted: {x}"), opening_bracket = right_leaves[-1].opening_bracket if opening_bracket is not None and opening_bracket in left_leaves: index = left_leaves.index(opening_bracket) diff --git a/tests/data/cases/preview_long_dict_values_2.py b/tests/data/cases/preview_long_dict_values_2.py new file mode 100644 index 00000000000..299d57c095b --- /dev/null +++ b/tests/data/cases/preview_long_dict_values_2.py @@ -0,0 +1,147 @@ +# flags: --unstable + +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( + "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx" + ) +} +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx", +} +x = { + "foo": (bar), + "foo": bar, + "foo": xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx, +} +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( + "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxx" + ) +} + +# Function calls as keys +tasks = { + get_key_name(foo, bar, baz,): src, + loop.run_in_executor(): src, + loop.run_in_executor(xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx): src, + loop.run_in_executor(xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxx): src, + loop.run_in_executor(): xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx +} + +# Dictionary comprehensions +tasks = { + key_name: xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx + for src in sources +} +tasks = {key_name: foobar for src in sources} +tasks = {get_key_name(foo, bar, baz,): src for src in sources} +tasks = { + get_key_name(): xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx + for src in sources +} +tasks = { + get_key_name(): foobar + for src in sources +} + +# Delimiters inside the value +def foo(): + def bar(): + x = { + common.models.DateTimeField: ( + datetime(2020, 1, 31, tzinfo=utc) + timedelta(days=i) + ), + } + x = { + common.models.DateTimeField: datetime(2020, 1, 31, tzinfo=utc) + timedelta( + days=i + ), + } + x = { + "foobar": ( + 123 + 456 + ), + } + + +# output + +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( + "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx" + ) +} +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( + "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx" + ), +} +x = { + "foo": bar, + "foo": bar, + "foo": ( + xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx + ), +} +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxx" +} + +# Function calls as keys +tasks = { + get_key_name( + foo, + bar, + baz, + ): src, + loop.run_in_executor(): src, + loop.run_in_executor(xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx): src, + loop.run_in_executor( + xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxx + ): src, + loop.run_in_executor(): ( + xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx + ), +} + +# Dictionary comprehensions +tasks = { + key_name: ( + xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx + ) + for src in sources +} +tasks = {key_name: foobar for src in sources} +tasks = { + get_key_name( + foo, + bar, + baz, + ): src + for src in sources +} +tasks = { + get_key_name(): ( + xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx + ) + for src in sources +} +tasks = {get_key_name(): foobar for src in sources} + + +# Delimiters inside the value +def foo(): + def bar(): + x = { + common.models.DateTimeField: ( + datetime(2020, 1, 31, tzinfo=utc) + timedelta(days=i) + ), + } + x = { + common.models.DateTimeField: ( + datetime(2020, 1, 31, tzinfo=utc) + timedelta(days=i) + ), + } + x = { + "foobar": 123 + 456, + } From 43a2bd63b1b42db44ef5bf57699b1628bab49b96 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+RedGuy12@users.noreply.github.com> Date: Wed, 12 Jun 2024 15:45:13 -0500 Subject: [PATCH 02/18] fix: Use node type instead of leaf type Signed-off-by: cobalt <61329810+RedGuy12@users.noreply.github.com> --- src/black/lines.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/black/lines.py b/src/black/lines.py index 1cf4d739e24..29030dc308c 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -954,13 +954,14 @@ def can_omit_invisible_parens( ): closing_bracket = leaf + bracket = rhs.opening_bracket if ( # Keep parenthesized dictionary values - len(rhs.head.leaves) >= 2 - and rhs.head.leaves[-1].type == token.LPAR - and rhs.head.leaves[-2].type == token.COLON - # Unless key is a function call with trailing comma - and not( + bracket.parent + and bracket.parent.parent + and bracket.parent.parent.type == syms.dictsetmaker + # Unless key is a multiline function call (aka, with trailing comma) + and not ( len(rhs.head.leaves) >= 4 and rhs.head.leaves[-3].type == token.RPAR and rhs.head.leaves[-4].type == token.COMMA From 9140aa2dce538f49ceccece7e999c8226ed2c820 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 12 Jun 2024 20:51:50 +0000 Subject: [PATCH 03/18] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/contributing/release_process.md | 9 ++++----- docs/usage_and_configuration/black_docker_image.md | 14 +++++++------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/contributing/release_process.md b/docs/contributing/release_process.md index c66ffae8ace..2c904fb95c4 100644 --- a/docs/contributing/release_process.md +++ b/docs/contributing/release_process.md @@ -29,8 +29,8 @@ frequently than monthly nets rapidly diminishing returns. **You must have `write` permissions for the _Black_ repository to cut a release.** The 10,000 foot view of the release process is that you prepare a release PR and then -publish a [GitHub Release]. This triggers [release automation](#release-workflows) that -builds all release artifacts and publishes them to the various platforms we publish to. +publish a [GitHub Release]. This triggers [release automation](#release-workflows) that builds +all release artifacts and publishes them to the various platforms we publish to. We now have a `scripts/release.py` script to help with cutting the release PRs. @@ -96,9 +96,8 @@ In the end, use your best judgement and ask other maintainers for their thoughts ## Release workflows -All of _Black_'s release automation uses [GitHub Actions]. All workflows are therefore -configured using YAML files in the `.github/workflows` directory of the _Black_ -repository. +All of _Black_'s release automation uses [GitHub Actions]. All workflows are therefore configured +using YAML files in the `.github/workflows` directory of the _Black_ repository. They are triggered by the publication of a [GitHub Release]. diff --git a/docs/usage_and_configuration/black_docker_image.md b/docs/usage_and_configuration/black_docker_image.md index c97c25af328..72969b7b68a 100644 --- a/docs/usage_and_configuration/black_docker_image.md +++ b/docs/usage_and_configuration/black_docker_image.md @@ -8,16 +8,16 @@ _Black_ images with the following tags are available: - release numbers, e.g. `21.5b2`, `21.6b0`, `21.7b0` etc.\ ℹ Recommended for users who want to use a particular version of _Black_. - `latest_release` - tag created when a new version of _Black_ is released.\ - ℹ Recommended for users who want to use released versions of _Black_. It maps to [the latest release](https://github.com/psf/black/releases/latest) - of _Black_. + ℹ Recommended for users who want to use released versions of _Black_. It maps to + [the latest release](https://github.com/psf/black/releases/latest) of _Black_. - `latest_prerelease` - tag created when a new alpha (prerelease) version of _Black_ is released.\ - ℹ Recommended for users who want to preview or test alpha versions of _Black_. Note that - the most recent release may be newer than any prerelease, because no prereleases are created - before most releases. + ℹ Recommended for users who want to preview or test alpha versions of _Black_. Note + that the most recent release may be newer than any prerelease, because no prereleases + are created before most releases. - `latest` - tag used for the newest image of _Black_.\ - ℹ Recommended for users who always want to use the latest version of _Black_, even before - it is released. + ℹ Recommended for users who always want to use the latest version of _Black_, even + before it is released. There is one more tag used for _Black_ Docker images - `latest_non_release`. It is created for all unreleased From a54503d28d8acf9816f8d12a6b971789fe83e95a Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Fri, 8 Nov 2024 16:54:00 -0600 Subject: [PATCH 04/18] add more tests Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- tests/data/cases/preview_long_dict_values_2.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/data/cases/preview_long_dict_values_2.py b/tests/data/cases/preview_long_dict_values_2.py index 299d57c095b..4305461192f 100644 --- a/tests/data/cases/preview_long_dict_values_2.py +++ b/tests/data/cases/preview_long_dict_values_2.py @@ -34,6 +34,7 @@ for src in sources } tasks = {key_name: foobar for src in sources} +tasks = {get_key_name(src,): "foo" for src in sources} tasks = {get_key_name(foo, bar, baz,): src for src in sources} tasks = { get_key_name(): xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx @@ -62,6 +63,11 @@ def bar(): 123 + 456 ), } + x = { + "foobar": ( + 123 + ) + 456, + } # output @@ -97,7 +103,7 @@ def bar(): loop.run_in_executor(): src, loop.run_in_executor(xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx): src, loop.run_in_executor( - xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxx + xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxx, ): src, loop.run_in_executor(): ( xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx @@ -112,6 +118,12 @@ def bar(): for src in sources } tasks = {key_name: foobar for src in sources} +tasks = { + get_key_name( + src, + ): "foo" + for src in sources +} tasks = { get_key_name( foo, @@ -145,3 +157,6 @@ def bar(): x = { "foobar": 123 + 456, } + x = { + "foobar": (123) + 456, + } From 689075372978aff92a524f49788ecba3f6c83360 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 15 Dec 2024 21:09:42 -0600 Subject: [PATCH 05/18] make progress i think Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- src/black/const.py | 4 +- src/black/linegen.py | 51 ++++++++----------- src/black/lines.py | 15 ------ src/black/trans.py | 2 +- .../data/cases/preview_long_dict_values_2.py | 2 +- 5 files changed, 27 insertions(+), 47 deletions(-) diff --git a/src/black/const.py b/src/black/const.py index ee466679c70..04048f30c65 100644 --- a/src/black/const.py +++ b/src/black/const.py @@ -1,4 +1,6 @@ DEFAULT_LINE_LENGTH = 88 -DEFAULT_EXCLUDES = r"/(\.direnv|\.eggs|\.git|\.hg|\.ipynb_checkpoints|\.mypy_cache|\.nox|\.pytest_cache|\.ruff_cache|\.tox|\.svn|\.venv|\.vscode|__pypackages__|_build|buck-out|build|dist|venv)/" # noqa: B950 +DEFAULT_EXCLUDES = ( + r"/(\.direnv|\.eggs|\.git|\.hg|\.ipynb_checkpoints|\.mypy_cache|\.nox|\.pytest_cache|\.ruff_cache|\.tox|\.svn|\.venv|\.vscode|__pypackages__|_build|buck-out|build|dist|venv)/" # noqa: B950 +) DEFAULT_INCLUDES = r"(\.pyi?|\.ipynb)$" STDIN_PLACEHOLDER = "__BLACK_STDIN_FILENAME__" diff --git a/src/black/linegen.py b/src/black/linegen.py index 7bd018d31af..8ff7a1dc624 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -971,29 +971,7 @@ def _maybe_split_omitting_optional_parens( try: # The RHSResult Omitting Optional Parens. rhs_oop = _first_right_hand_split(line, omit=omit) - is_split_right_after_equal = ( - len(rhs.head.leaves) >= 2 and rhs.head.leaves[-2].type == token.EQUAL - ) - rhs_head_contains_brackets = any( - leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1] - ) - # the -1 is for the ending optional paren - rhs_head_short_enough = is_line_short_enough( - rhs.head, mode=replace(mode, line_length=mode.line_length - 1) - ) - rhs_head_explode_blocked_by_magic_trailing_comma = ( - rhs.head.magic_trailing_comma is None - ) - if ( - not ( - is_split_right_after_equal - and rhs_head_contains_brackets - and rhs_head_short_enough - and rhs_head_explode_blocked_by_magic_trailing_comma - ) - # the omit optional parens split is preferred by some other reason - or _prefer_split_rhs_oop_over_rhs(rhs_oop, rhs, mode) - ): + if _prefer_split_rhs_oop_over_rhs(rhs_oop, rhs, mode): yield from _maybe_split_omitting_optional_parens( rhs_oop, line, mode, features=features, omit=omit ) @@ -1004,12 +982,12 @@ def _maybe_split_omitting_optional_parens( if line.is_chained_assignment: pass - elif not can_be_split(rhs.body) and not is_line_short_enough( - rhs.body, mode=mode - ): - raise CannotSplit( - "Splitting failed, body is still too long and can't be split." - ) from e + # elif not can_be_split(rhs.body) and not is_line_short_enough( + # rhs.body, mode=mode + # ): + # raise CannotSplit( + # "Splitting failed, body is still too long and can't be split." + # ) from e elif ( rhs.head.contains_multiline_strings() @@ -1036,6 +1014,18 @@ def _prefer_split_rhs_oop_over_rhs( Returns whether we should prefer the result from a split omitting optional parens (rhs_oop) over the original (rhs). """ + # the -1 is for the ending optional paren + if is_line_short_enough( + rhs.head, mode=replace(mode, line_length=mode.line_length - 1) + ): + return True + + # Remove optional parens when the head contains brackets + if any(leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1]): + return True + if rhs.head.magic_trailing_comma is not None: + return False + # If we have multiple targets, we prefer more `=`s on the head vs pushing them to # the body rhs_head_equal_count = [leaf.type for leaf in rhs.head.leaves].count(token.EQUAL) @@ -1044,6 +1034,9 @@ def _prefer_split_rhs_oop_over_rhs( ) if rhs_head_equal_count > 1 and rhs_head_equal_count > rhs_oop_head_equal_count: return False + # Remove optional parens right after an equal sign + if len(rhs.head.leaves) >= 2 and rhs.head.leaves[-2].type == token.EQUAL: + return True has_closing_bracket_after_assign = False for leaf in reversed(rhs_oop.head.leaves): diff --git a/src/black/lines.py b/src/black/lines.py index 34962396f9b..dd91bf898f2 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -958,21 +958,6 @@ def can_omit_invisible_parens( ): closing_bracket = leaf - bracket = rhs.opening_bracket - if ( - # Keep parenthesized dictionary values - bracket.parent - and bracket.parent.parent - and bracket.parent.parent.type == syms.dictsetmaker - # Unless key is a multiline function call (aka, with trailing comma) - and not ( - len(rhs.head.leaves) >= 4 - and rhs.head.leaves[-3].type == token.RPAR - and rhs.head.leaves[-4].type == token.COMMA - ) - ): - return False - bt = line.bracket_tracker if not bt.delimiters: # Without delimiters the optional parentheses are useless. diff --git a/src/black/trans.py b/src/black/trans.py index e544d36c77a..9cc00e74636 100644 --- a/src/black/trans.py +++ b/src/black/trans.py @@ -856,7 +856,7 @@ def _validate_msg(line: Line, string_idx: int) -> TResult[None]: ): return TErr( "StringMerger does NOT merge f-strings with different quote types" - "and internal quotes." + " and internal quotes." ) if id(leaf) in line.comments: diff --git a/tests/data/cases/preview_long_dict_values_2.py b/tests/data/cases/preview_long_dict_values_2.py index 4305461192f..3fd4a452d31 100644 --- a/tests/data/cases/preview_long_dict_values_2.py +++ b/tests/data/cases/preview_long_dict_values_2.py @@ -103,7 +103,7 @@ def bar(): loop.run_in_executor(): src, loop.run_in_executor(xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx): src, loop.run_in_executor( - xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxx, + xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxx ): src, loop.run_in_executor(): ( xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx From 7e80b09d8584f825b74179adfc26e091082d7f77 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 15 Dec 2024 21:21:52 -0600 Subject: [PATCH 06/18] dont wrap simple assignments Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- src/black/const.py | 4 +--- src/black/linegen.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/black/const.py b/src/black/const.py index 04048f30c65..ee466679c70 100644 --- a/src/black/const.py +++ b/src/black/const.py @@ -1,6 +1,4 @@ DEFAULT_LINE_LENGTH = 88 -DEFAULT_EXCLUDES = ( - r"/(\.direnv|\.eggs|\.git|\.hg|\.ipynb_checkpoints|\.mypy_cache|\.nox|\.pytest_cache|\.ruff_cache|\.tox|\.svn|\.venv|\.vscode|__pypackages__|_build|buck-out|build|dist|venv)/" # noqa: B950 -) +DEFAULT_EXCLUDES = r"/(\.direnv|\.eggs|\.git|\.hg|\.ipynb_checkpoints|\.mypy_cache|\.nox|\.pytest_cache|\.ruff_cache|\.tox|\.svn|\.venv|\.vscode|__pypackages__|_build|buck-out|build|dist|venv)/" # noqa: B950 DEFAULT_INCLUDES = r"(\.pyi?|\.ipynb)$" STDIN_PLACEHOLDER = "__BLACK_STDIN_FILENAME__" diff --git a/src/black/linegen.py b/src/black/linegen.py index 8ff7a1dc624..411e65cd558 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -982,12 +982,18 @@ def _maybe_split_omitting_optional_parens( if line.is_chained_assignment: pass - # elif not can_be_split(rhs.body) and not is_line_short_enough( - # rhs.body, mode=mode - # ): - # raise CannotSplit( - # "Splitting failed, body is still too long and can't be split." - # ) from e + elif ( + not can_be_split(rhs.body) + and not is_line_short_enough(rhs.body, mode=mode) + and not ( + rhs.opening_bracket.parent + and rhs.opening_bracket.parent.parent + and rhs.opening_bracket.parent.parent.type == syms.dictsetmaker + ) + ): + raise CannotSplit( + "Splitting failed, body is still too long and can't be split." + ) from e elif ( rhs.head.contains_multiline_strings() From a13dcd7b3c0469edeaf3fbb2744aa568c2ac3b0b Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 22 Dec 2024 17:06:36 -0600 Subject: [PATCH 07/18] fix a few regressions Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- src/black/linegen.py | 57 +++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index 411e65cd558..4fa1095f5c8 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -1020,18 +1020,14 @@ def _prefer_split_rhs_oop_over_rhs( Returns whether we should prefer the result from a split omitting optional parens (rhs_oop) over the original (rhs). """ - # the -1 is for the ending optional paren - if is_line_short_enough( - rhs.head, mode=replace(mode, line_length=mode.line_length - 1) + # contains unsplittable type ignore + if ( + rhs_oop.head.contains_unsplittable_type_ignore() + or rhs_oop.body.contains_unsplittable_type_ignore() + or rhs_oop.tail.contains_unsplittable_type_ignore() ): return True - # Remove optional parens when the head contains brackets - if any(leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1]): - return True - if rhs.head.magic_trailing_comma is not None: - return False - # If we have multiple targets, we prefer more `=`s on the head vs pushing them to # the body rhs_head_equal_count = [leaf.type for leaf in rhs.head.leaves].count(token.EQUAL) @@ -1040,10 +1036,21 @@ def _prefer_split_rhs_oop_over_rhs( ) if rhs_head_equal_count > 1 and rhs_head_equal_count > rhs_oop_head_equal_count: return False - # Remove optional parens right after an equal sign - if len(rhs.head.leaves) >= 2 and rhs.head.leaves[-2].type == token.EQUAL: + + if not is_line_short_enough( + # the -1 is for the ending optional paren + rhs.head, + mode=replace(mode, line_length=mode.line_length - 1), + ): + return False + + # Remove optional parens when the head contains brackets + if any(leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1]): return True + if rhs.head.magic_trailing_comma is not None: + return False + # contains matching brackets after the `=` (done by checking there is a closing bracket) has_closing_bracket_after_assign = False for leaf in reversed(rhs_oop.head.leaves): if leaf.type == token.EQUAL: @@ -1051,22 +1058,18 @@ def _prefer_split_rhs_oop_over_rhs( if leaf.type in CLOSING_BRACKETS: has_closing_bracket_after_assign = True break - return ( - # contains matching brackets after the `=` (done by checking there is a - # closing bracket) - has_closing_bracket_after_assign - or ( - # the split is actually from inside the optional parens (done by checking - # the first line still contains the `=`) - any(leaf.type == token.EQUAL for leaf in rhs_oop.head.leaves) - # the first line is short enough - and is_line_short_enough(rhs_oop.head, mode=mode) - ) - # contains unsplittable type ignore - or rhs_oop.head.contains_unsplittable_type_ignore() - or rhs_oop.body.contains_unsplittable_type_ignore() - or rhs_oop.tail.contains_unsplittable_type_ignore() - ) + if has_closing_bracket_after_assign: + return True + + # the split is actually from inside the optional parens (done by checking the first line still contains the `=`) + if ( + any(leaf.type == token.EQUAL for leaf in rhs_oop.head.leaves) + # the first line is short enough + and is_line_short_enough(rhs_oop.head, mode=mode) + ): + return True + + return False def bracket_split_succeeded_or_raise(head: Line, body: Line, tail: Line) -> None: From 71fed4986fc4dc41138dfc4e7524257deec6cb41 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 22 Dec 2024 18:40:54 -0600 Subject: [PATCH 08/18] hmmm Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- src/black/linegen.py | 47 ++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index 4fa1095f5c8..5ef9b3dd78e 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -1028,6 +1028,25 @@ def _prefer_split_rhs_oop_over_rhs( ): return True + # the split is right after `=` + if not (len(rhs.head.leaves) >= 2 and rhs.head.leaves[-2].type == token.EQUAL): + return True + + # the left side of assignment contains brackets + if not any(leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1]): + return True + + # the left side of assignment is short enough (the -1 is for the ending optional + # paren) + if not is_line_short_enough( + rhs.head, mode=replace(mode, line_length=mode.line_length - 1) + ): + return True + + # the left side of assignment won't explode further because of magic trailing comma + if rhs.head.magic_trailing_comma is not None: + return True + # If we have multiple targets, we prefer more `=`s on the head vs pushing them to # the body rhs_head_equal_count = [leaf.type for leaf in rhs.head.leaves].count(token.EQUAL) @@ -1037,20 +1056,18 @@ def _prefer_split_rhs_oop_over_rhs( if rhs_head_equal_count > 1 and rhs_head_equal_count > rhs_oop_head_equal_count: return False - if not is_line_short_enough( - # the -1 is for the ending optional paren - rhs.head, - mode=replace(mode, line_length=mode.line_length - 1), + # the split is actually from inside the optional parens (done by checking the first + # line still contains the `=`) + if ( + rhs_head_equal_count > 1 + and rhs_oop_head_equal_count > 1 + # and the first line is short enough + and is_line_short_enough(rhs_oop.head, mode=mode) ): - return False - - # Remove optional parens when the head contains brackets - if any(leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1]): return True - if rhs.head.magic_trailing_comma is not None: - return False - # contains matching brackets after the `=` (done by checking there is a closing bracket) + # contains matching brackets after the `=` (done by checking there is a closing + # bracket) has_closing_bracket_after_assign = False for leaf in reversed(rhs_oop.head.leaves): if leaf.type == token.EQUAL: @@ -1061,14 +1078,6 @@ def _prefer_split_rhs_oop_over_rhs( if has_closing_bracket_after_assign: return True - # the split is actually from inside the optional parens (done by checking the first line still contains the `=`) - if ( - any(leaf.type == token.EQUAL for leaf in rhs_oop.head.leaves) - # the first line is short enough - and is_line_short_enough(rhs_oop.head, mode=mode) - ): - return True - return False From 1fb452a477369527507c7967eea4e020b1549836 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 22 Dec 2024 19:01:07 -0600 Subject: [PATCH 09/18] oop Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- src/black/linegen.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index 5ef9b3dd78e..65e5db5aad2 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -1029,22 +1029,22 @@ def _prefer_split_rhs_oop_over_rhs( return True # the split is right after `=` - if not (len(rhs.head.leaves) >= 2 and rhs.head.leaves[-2].type == token.EQUAL): + if len(rhs.head.leaves) >= 2 and rhs.head.leaves[-2].type == token.EQUAL: return True # the left side of assignment contains brackets - if not any(leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1]): + if any(leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1]): return True # the left side of assignment is short enough (the -1 is for the ending optional # paren) - if not is_line_short_enough( + if is_line_short_enough( rhs.head, mode=replace(mode, line_length=mode.line_length - 1) ): return True # the left side of assignment won't explode further because of magic trailing comma - if rhs.head.magic_trailing_comma is not None: + if rhs.head.magic_trailing_comma is None: return True # If we have multiple targets, we prefer more `=`s on the head vs pushing them to From 4102ffe2d98934741b3674e51e0ed4a538d73cdc Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 22 Dec 2024 19:44:02 -0600 Subject: [PATCH 10/18] no at this point it mostly works so go back here Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- src/black/linegen.py | 36 +++++++++++------------- tests/data/cases/preview_long_strings.py | 10 +++---- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index 65e5db5aad2..8359a7a80ff 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -1029,22 +1029,22 @@ def _prefer_split_rhs_oop_over_rhs( return True # the split is right after `=` - if len(rhs.head.leaves) >= 2 and rhs.head.leaves[-2].type == token.EQUAL: + if not (len(rhs.head.leaves) >= 2 and rhs.head.leaves[-2].type == token.EQUAL): return True # the left side of assignment contains brackets - if any(leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1]): + if not any(leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1]): return True # the left side of assignment is short enough (the -1 is for the ending optional # paren) - if is_line_short_enough( + if not is_line_short_enough( rhs.head, mode=replace(mode, line_length=mode.line_length - 1) ): return True # the left side of assignment won't explode further because of magic trailing comma - if rhs.head.magic_trailing_comma is None: + if rhs.head.magic_trailing_comma is not None: return True # If we have multiple targets, we prefer more `=`s on the head vs pushing them to @@ -1056,18 +1056,6 @@ def _prefer_split_rhs_oop_over_rhs( if rhs_head_equal_count > 1 and rhs_head_equal_count > rhs_oop_head_equal_count: return False - # the split is actually from inside the optional parens (done by checking the first - # line still contains the `=`) - if ( - rhs_head_equal_count > 1 - and rhs_oop_head_equal_count > 1 - # and the first line is short enough - and is_line_short_enough(rhs_oop.head, mode=mode) - ): - return True - - # contains matching brackets after the `=` (done by checking there is a closing - # bracket) has_closing_bracket_after_assign = False for leaf in reversed(rhs_oop.head.leaves): if leaf.type == token.EQUAL: @@ -1075,10 +1063,18 @@ def _prefer_split_rhs_oop_over_rhs( if leaf.type in CLOSING_BRACKETS: has_closing_bracket_after_assign = True break - if has_closing_bracket_after_assign: - return True - - return False + return ( + # contains matching brackets after the `=` (done by checking there is a + # closing bracket) + has_closing_bracket_after_assign + or ( + # the split is actually from inside the optional parens (done by checking + # the first line still contains the `=`) + any(leaf.type == token.EQUAL for leaf in rhs_oop.head.leaves) + # the first line is short enough + and is_line_short_enough(rhs_oop.head, mode=mode) + ) + ) def bracket_split_succeeded_or_raise(head: Line, body: Line, tail: Line) -> None: diff --git a/tests/data/cases/preview_long_strings.py b/tests/data/cases/preview_long_strings.py index ba48c19d542..db1499010fb 100644 --- a/tests/data/cases/preview_long_strings.py +++ b/tests/data/cases/preview_long_strings.py @@ -842,11 +842,9 @@ def foo(): " \\N{LAO KO LA}" ) -msg = ( - lambda x: ( - f"this is a very very very long lambda value {x} that doesn't fit on a single" - " line" - ) +msg = lambda x: ( + f"this is a very very very very long lambda value {x} that doesn't fit on a single" + " line" ) dict_with_lambda_values = { @@ -925,4 +923,4 @@ def foo(): log.info( f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']} {desc['exposure_max']}""" -) \ No newline at end of file +) From 22a18b8ca5f988bb15a5c449eb9dc1c605166eac Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 22 Dec 2024 19:48:42 -0600 Subject: [PATCH 11/18] fix preview_long_strings Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- tests/data/cases/preview_long_strings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/data/cases/preview_long_strings.py b/tests/data/cases/preview_long_strings.py index db1499010fb..ce35d4477b6 100644 --- a/tests/data/cases/preview_long_strings.py +++ b/tests/data/cases/preview_long_strings.py @@ -279,7 +279,7 @@ def foo(): "........................................................................... \\N{LAO KO LA}" ) -msg = lambda x: f"this is a very very very long lambda value {x} that doesn't fit on a single line" +msg = lambda x: f"this is a very very very very long lambda value {x} that doesn't fit on a single line" dict_with_lambda_values = { "join": lambda j: ( From d39e79dfa06482f8c8e1b88f1ec21896e06dacf1 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 22 Dec 2024 20:25:54 -0600 Subject: [PATCH 12/18] maybe finish it! Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- src/black/linegen.py | 17 ++++++++++------- tests/data/cases/preview_long_dict_values_2.py | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index 8359a7a80ff..e397a74bfbb 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -1627,17 +1627,20 @@ def maybe_make_parens_invisible_in_atom( or is_empty_tuple(node) or is_one_tuple(node) or (is_yield(node) and parent.type != syms.expr_stmt) - or ( - # This condition tries to prevent removing non-optional brackets - # around a tuple, however, can be a bit overzealous so we provide - # 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 - ) or is_tuple_containing_walrus(node) ): return False + max_delimiter_priority = max_delimiter_priority_in_atom(node) + # This condition tries to prevent removing non-optional brackets + # around a tuple, however, can be a bit overzealous so we provide + # and option to skip this check for `for` and `with` statements. + if not remove_brackets_around_comma and max_delimiter_priority >= COMMA_PRIORITY: + return False + + if parent.type == syms.dictsetmaker and max_delimiter_priority != 0: + return False + if is_walrus_assignment(node): if parent.type in [ syms.annassign, diff --git a/tests/data/cases/preview_long_dict_values_2.py b/tests/data/cases/preview_long_dict_values_2.py index 3fd4a452d31..bfe46374697 100644 --- a/tests/data/cases/preview_long_dict_values_2.py +++ b/tests/data/cases/preview_long_dict_values_2.py @@ -155,7 +155,7 @@ def bar(): ), } x = { - "foobar": 123 + 456, + "foobar": (123 + 456), } x = { "foobar": (123) + 456, From 1dc7fa5879519a731286333e88b64d90e4bfcc30 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 22 Dec 2024 20:32:04 -0600 Subject: [PATCH 13/18] fix tests Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- tests/data/cases/preview_long_dict_values_2.py | 4 ++-- tests/data/cases/preview_long_strings.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/data/cases/preview_long_dict_values_2.py b/tests/data/cases/preview_long_dict_values_2.py index bfe46374697..c56b5396496 100644 --- a/tests/data/cases/preview_long_dict_values_2.py +++ b/tests/data/cases/preview_long_dict_values_2.py @@ -150,8 +150,8 @@ def bar(): ), } x = { - common.models.DateTimeField: ( - datetime(2020, 1, 31, tzinfo=utc) + timedelta(days=i) + common.models.DateTimeField: datetime(2020, 1, 31, tzinfo=utc) + timedelta( + days=i ), } x = { diff --git a/tests/data/cases/preview_long_strings.py b/tests/data/cases/preview_long_strings.py index ce35d4477b6..8c092e61b07 100644 --- a/tests/data/cases/preview_long_strings.py +++ b/tests/data/cases/preview_long_strings.py @@ -843,8 +843,8 @@ def foo(): ) msg = lambda x: ( - f"this is a very very very very long lambda value {x} that doesn't fit on a single" - " line" + f"this is a very very very very long lambda value {x} that doesn't fit on a" + " single line" ) dict_with_lambda_values = { From daa9431a600eb06688c418ea2c24e9e80319f6a3 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 22 Dec 2024 20:43:43 -0600 Subject: [PATCH 14/18] Fix type error Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- src/blib2to3/pgen2/pgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blib2to3/pgen2/pgen.py b/src/blib2to3/pgen2/pgen.py index 17f7533995f..a41c1aa06aa 100644 --- a/src/blib2to3/pgen2/pgen.py +++ b/src/blib2to3/pgen2/pgen.py @@ -357,7 +357,7 @@ def raise_error(self, msg: str, *args: Any) -> NoReturn: msg = msg % args except Exception: msg = " ".join([msg] + list(map(str, args))) - raise SyntaxError(msg, (self.filename, self.end[0], self.end[1], self.line)) + raise SyntaxError(msg, (str(self.filename), self.end[0], self.end[1], self.line)) class NFAState: From a060a2ff2822c0021a0eeee056cf0c098049976c Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 12 Jan 2025 18:46:57 -0600 Subject: [PATCH 15/18] hmm update tests Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- tests/data/cases/preview_long_dict_values.py | 184 +++++++++++++++++- .../data/cases/preview_long_dict_values_2.py | 162 --------------- tests/data/cases/preview_long_strings.py | 28 +++ 3 files changed, 207 insertions(+), 167 deletions(-) delete mode 100644 tests/data/cases/preview_long_dict_values_2.py diff --git a/tests/data/cases/preview_long_dict_values.py b/tests/data/cases/preview_long_dict_values.py index a19210605f6..a58eccf4068 100644 --- a/tests/data/cases/preview_long_dict_values.py +++ b/tests/data/cases/preview_long_dict_values.py @@ -1,4 +1,25 @@ # flags: --unstable +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( + "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx" + ) +} +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( + "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx" + ), +} +x = { + "foo": bar, + "foo": bar, + "foo": ( + xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx + ), +} +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxx" +} + my_dict = { "something_something": r"Lorem ipsum dolor sit amet, an sed convenire eloquentiam \t" @@ -6,14 +27,81 @@ r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t", } +# Function calls as keys +tasks = { + get_key_name( + foo, + bar, + baz, + ): src, + loop.run_in_executor(): src, + loop.run_in_executor(xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx): src, + loop.run_in_executor( + xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxx + ): src, + loop.run_in_executor(): ( + xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx + ), +} + +# Dictionary comprehensions +tasks = { + key_name: ( + xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx + ) + for src in sources +} +tasks = {key_name: foobar for src in sources} +tasks = { + get_key_name( + src, + ): "foo" + for src in sources +} +tasks = { + get_key_name( + foo, + bar, + baz, + ): src + for src in sources +} +tasks = { + get_key_name(): ( + xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx + ) + for src in sources +} +tasks = {get_key_name(): foobar for src in sources} + + +# Delimiters inside the value +def foo(): + def bar(): + x = { + common.models.DateTimeField: datetime(2020, 1, 31, tzinfo=utc) + timedelta( + days=i + ), + } + x = { + common.models.DateTimeField: datetime(2020, 1, 31, tzinfo=utc) + timedelta( + days=i + ), + } + x = { + "foobar": (123 + 456), + } + x = { + "foobar": (123) + 456, + } + + my_dict = { "a key in my dict": a_very_long_variable * and_a_very_long_function_call() / 100000.0 } - my_dict = { "a key in my dict": a_very_long_variable * and_a_very_long_function_call() * and_another_long_func() / 100000.0 } - my_dict = { "a key in my dict": MyClass.some_attribute.first_call().second_call().third_call(some_args="some value") } @@ -58,7 +146,26 @@ def func(): # output - +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( + "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx" + ) +} +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( + "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx" + ), +} +x = { + "foo": bar, + "foo": bar, + "foo": ( + xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx + ), +} +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxx" +} my_dict = { "something_something": ( @@ -68,12 +175,80 @@ def func(): ), } +# Function calls as keys +tasks = { + get_key_name( + foo, + bar, + baz, + ): src, + loop.run_in_executor(): src, + loop.run_in_executor(xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx): src, + loop.run_in_executor( + xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxx + ): src, + loop.run_in_executor(): ( + xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx + ), +} + +# Dictionary comprehensions +tasks = { + key_name: ( + xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx + ) + for src in sources +} +tasks = {key_name: foobar for src in sources} +tasks = { + get_key_name( + src, + ): "foo" + for src in sources +} +tasks = { + get_key_name( + foo, + bar, + baz, + ): src + for src in sources +} +tasks = { + get_key_name(): ( + xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx + ) + for src in sources +} +tasks = {get_key_name(): foobar for src in sources} + + +# Delimiters inside the value +def foo(): + def bar(): + x = { + common.models.DateTimeField: ( + datetime(2020, 1, 31, tzinfo=utc) + timedelta(days=i) + ), + } + x = { + common.models.DateTimeField: ( + datetime(2020, 1, 31, tzinfo=utc) + timedelta(days=i) + ), + } + x = { + "foobar": 123 + 456, + } + x = { + "foobar": 123 + 456, + } + + my_dict = { "a key in my dict": ( a_very_long_variable * and_a_very_long_function_call() / 100000.0 ) } - my_dict = { "a key in my dict": ( a_very_long_variable @@ -82,7 +257,6 @@ def func(): / 100000.0 ) } - my_dict = { "a key in my dict": ( MyClass.some_attribute.first_call() diff --git a/tests/data/cases/preview_long_dict_values_2.py b/tests/data/cases/preview_long_dict_values_2.py deleted file mode 100644 index c56b5396496..00000000000 --- a/tests/data/cases/preview_long_dict_values_2.py +++ /dev/null @@ -1,162 +0,0 @@ -# flags: --unstable - -x = { - "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( - "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx" - ) -} -x = { - "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx", -} -x = { - "foo": (bar), - "foo": bar, - "foo": xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx, -} -x = { - "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( - "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxx" - ) -} - -# Function calls as keys -tasks = { - get_key_name(foo, bar, baz,): src, - loop.run_in_executor(): src, - loop.run_in_executor(xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx): src, - loop.run_in_executor(xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxx): src, - loop.run_in_executor(): xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx -} - -# Dictionary comprehensions -tasks = { - key_name: xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx - for src in sources -} -tasks = {key_name: foobar for src in sources} -tasks = {get_key_name(src,): "foo" for src in sources} -tasks = {get_key_name(foo, bar, baz,): src for src in sources} -tasks = { - get_key_name(): xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx - for src in sources -} -tasks = { - get_key_name(): foobar - for src in sources -} - -# Delimiters inside the value -def foo(): - def bar(): - x = { - common.models.DateTimeField: ( - datetime(2020, 1, 31, tzinfo=utc) + timedelta(days=i) - ), - } - x = { - common.models.DateTimeField: datetime(2020, 1, 31, tzinfo=utc) + timedelta( - days=i - ), - } - x = { - "foobar": ( - 123 + 456 - ), - } - x = { - "foobar": ( - 123 - ) + 456, - } - - -# output - -x = { - "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( - "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx" - ) -} -x = { - "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( - "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx" - ), -} -x = { - "foo": bar, - "foo": bar, - "foo": ( - xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx - ), -} -x = { - "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxx" -} - -# Function calls as keys -tasks = { - get_key_name( - foo, - bar, - baz, - ): src, - loop.run_in_executor(): src, - loop.run_in_executor(xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx): src, - loop.run_in_executor( - xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxx - ): src, - loop.run_in_executor(): ( - xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx - ), -} - -# Dictionary comprehensions -tasks = { - key_name: ( - xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx - ) - for src in sources -} -tasks = {key_name: foobar for src in sources} -tasks = { - get_key_name( - src, - ): "foo" - for src in sources -} -tasks = { - get_key_name( - foo, - bar, - baz, - ): src - for src in sources -} -tasks = { - get_key_name(): ( - xx_xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxxxxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx - ) - for src in sources -} -tasks = {get_key_name(): foobar for src in sources} - - -# Delimiters inside the value -def foo(): - def bar(): - x = { - common.models.DateTimeField: ( - datetime(2020, 1, 31, tzinfo=utc) + timedelta(days=i) - ), - } - x = { - common.models.DateTimeField: datetime(2020, 1, 31, tzinfo=utc) + timedelta( - days=i - ), - } - x = { - "foobar": (123 + 456), - } - x = { - "foobar": (123) + 456, - } diff --git a/tests/data/cases/preview_long_strings.py b/tests/data/cases/preview_long_strings.py index 8c092e61b07..cf1d12b6e3e 100644 --- a/tests/data/cases/preview_long_strings.py +++ b/tests/data/cases/preview_long_strings.py @@ -329,6 +329,20 @@ def foo(): log.info(f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']} {desc['exposure_max']}""") +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( + "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx" + ) +} +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx", +} +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( + "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxx" + ) +} + # output @@ -924,3 +938,17 @@ def foo(): log.info( f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']} {desc['exposure_max']}""" ) + +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( + "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx" + ) +} +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( + "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx" + ), +} +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxx" +} From 8629d3659829c4ee232af3a94d48f74d82401c88 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Sun, 12 Jan 2025 21:55:15 -0600 Subject: [PATCH 16/18] it's done now i think Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- src/black/linegen.py | 14 ++++++- tests/data/cases/preview_long_dict_values.py | 40 ++++++++++---------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index e397a74bfbb..3cd6a403017 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -1028,6 +1028,16 @@ def _prefer_split_rhs_oop_over_rhs( ): return True + # Retain optional parens around dictionary values + if ( + rhs.opening_bracket.parent + and rhs.opening_bracket.parent.parent + and rhs.opening_bracket.parent.parent.type == syms.dictsetmaker + and rhs.body.bracket_tracker.delimiters + ): + # Unless the split is inside the key + return any(leaf.type == token.COLON for leaf in rhs_oop.tail.leaves) + # the split is right after `=` if not (len(rhs.head.leaves) >= 2 and rhs.head.leaves[-2].type == token.EQUAL): return True @@ -1638,8 +1648,8 @@ def maybe_make_parens_invisible_in_atom( if not remove_brackets_around_comma and max_delimiter_priority >= COMMA_PRIORITY: return False - if parent.type == syms.dictsetmaker and max_delimiter_priority != 0: - return False + # if parent.type == syms.dictsetmaker and max_delimiter_priority != 0: + # return False if is_walrus_assignment(node): if parent.type in [ diff --git a/tests/data/cases/preview_long_dict_values.py b/tests/data/cases/preview_long_dict_values.py index a58eccf4068..7c65d67496d 100644 --- a/tests/data/cases/preview_long_dict_values.py +++ b/tests/data/cases/preview_long_dict_values.py @@ -84,8 +84,8 @@ def bar(): ), } x = { - common.models.DateTimeField: datetime(2020, 1, 31, tzinfo=utc) + timedelta( - days=i + common.models.DateTimeField: ( + datetime(2020, 1, 31, tzinfo=utc) + timedelta(days=i) ), } x = { @@ -107,10 +107,10 @@ def bar(): } { - 'xxxxxx': + "xxxxxx": xxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxx( xxxxxxxxxxxxxx={ - 'x': + "x": xxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxx( xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=( xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx @@ -118,8 +118,8 @@ def bar(): xxxxxxxxxxxxx=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx .xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx( xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx={ - 'x': x.xx, - 'x': x.x, + "x": x.xx, + "x": x.x, })))) }), } @@ -240,7 +240,7 @@ def bar(): "foobar": 123 + 456, } x = { - "foobar": 123 + 456, + "foobar": (123) + 456, } @@ -287,17 +287,15 @@ def bar(): class Random: def func(): - random_service.status.active_states.inactive = ( - make_new_top_level_state_from_dict({ - "topLevelBase": { - "secondaryBase": { - "timestamp": 1234, - "latitude": 1, - "longitude": 2, - "actionTimestamp": ( - Timestamp(seconds=1530584000, nanos=0).ToJsonString() - ), - } - }, - }) - ) + random_service.status.active_states.inactive = make_new_top_level_state_from_dict({ + "topLevelBase": { + "secondaryBase": { + "timestamp": 1234, + "latitude": 1, + "longitude": 2, + "actionTimestamp": ( + Timestamp(seconds=1530584000, nanos=0).ToJsonString() + ), + } + }, + }) From e4230b660c662acda285e5a5cf6292b5eba21403 Mon Sep 17 00:00:00 2001 From: cobalt <61329810+cobaltt7@users.noreply.github.com> Date: Tue, 14 Jan 2025 21:26:53 -0600 Subject: [PATCH 17/18] Move to preview style, remove cruft, and add to changelog Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com> --- CHANGES.md | 3 +++ src/black/linegen.py | 23 +++++++++++------------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 51374fbe7f8..fa6c963221b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,6 +24,8 @@ (#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 + parenthesis around long dictionary values (#4377) ### Configuration @@ -43,6 +45,7 @@ ### Performance + - Speed up the `is_fstring_start` function in Black's tokenizer (#4541) ### Output diff --git a/src/black/linegen.py b/src/black/linegen.py index e7b9751b401..90ca5da8587 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -988,7 +988,8 @@ def _maybe_split_omitting_optional_parens( not can_be_split(rhs.body) and not is_line_short_enough(rhs.body, mode=mode) and not ( - rhs.opening_bracket.parent + Preview.wrap_long_dict_values_in_parens + and rhs.opening_bracket.parent and rhs.opening_bracket.parent.parent and rhs.opening_bracket.parent.parent.type == syms.dictsetmaker ) @@ -1032,7 +1033,8 @@ def _prefer_split_rhs_oop_over_rhs( # Retain optional parens around dictionary values if ( - rhs.opening_bracket.parent + Preview.wrap_long_dict_values_in_parens + and rhs.opening_bracket.parent and rhs.opening_bracket.parent.parent and rhs.opening_bracket.parent.parent.type == syms.dictsetmaker and rhs.body.bracket_tracker.delimiters @@ -1639,22 +1641,19 @@ def maybe_make_parens_invisible_in_atom( or is_empty_tuple(node) or is_one_tuple(node) or (is_yield(node) and parent.type != syms.expr_stmt) + or ( + # This condition tries to prevent removing non-optional brackets + # around a tuple, however, can be a bit overzealous so we provide + # 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 + ) or is_tuple_containing_walrus(node) or is_tuple_containing_star(node) or is_generator(node) ): return False - max_delimiter_priority = max_delimiter_priority_in_atom(node) - # This condition tries to prevent removing non-optional brackets - # around a tuple, however, can be a bit overzealous so we provide - # and option to skip this check for `for` and `with` statements. - if not remove_brackets_around_comma and max_delimiter_priority >= COMMA_PRIORITY: - return False - - # if parent.type == syms.dictsetmaker and max_delimiter_priority != 0: - # return False - if is_walrus_assignment(node): if parent.type in [ syms.annassign, From 4053acc5785230a0174cfc0d00be12dec9ecfe24 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 16 Jan 2025 20:58:37 -0800 Subject: [PATCH 18/18] Update CHANGES.md --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index fa6c963221b..416aabcdf13 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -25,7 +25,7 @@ - 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 - parenthesis around long dictionary values (#4377) + parentheses around long dictionary values (#4377) ### Configuration