]> git.madduck.net Git - etc/vim.git/commitdiff

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Improve long values in dict literals (#3440)
authorYilei "Dolee" Yang <hi@mangoumbrella.com>
Thu, 15 Dec 2022 16:25:28 +0000 (08:25 -0800)
committerGitHub <noreply@github.com>
Thu, 15 Dec 2022 16:25:28 +0000 (08:25 -0800)
CHANGES.md
src/black/linegen.py
src/black/mode.py
src/black/trans.py
tests/data/preview/long_dict_values.py [new file with mode: 0644]
tests/data/preview/long_strings.py
tests/data/preview/long_strings__regression.py

index 1a7c320baf8d79271a208734fad9e2569a746073..03c7a28677172c594b38f2ae62622c71c551d039 100644 (file)
@@ -19,6 +19,9 @@
 - Fix a crash in preview style with assert + parenthesized string (#3415)
 - Do not put the closing quotes in a docstring on a separate line, even if the line is
   too long (#3430)
+- Long values in dict literals are now wrapped in parentheses; correspondingly
+  unnecessary parentheses around short values in dict literals are now removed; long
+  string lambda values are now wrapped in parentheses (#3440)
 
 ### Configuration
 
index 644824a3c861c34d45ce7befc73a783ea7d6dc2b..244dbe77eb5d2fe7150b2a9a9b4cabf8ebf6e7dd 100644 (file)
@@ -179,6 +179,23 @@ class LineGenerator(Visitor[Line]):
 
             yield from self.visit(child)
 
+    def visit_dictsetmaker(self, node: Node) -> Iterator[Line]:
+        if Preview.wrap_long_dict_values_in_parens in self.mode:
+            for i, child in enumerate(node.children):
+                if i == 0:
+                    continue
+                if node.children[i - 1].type == token.COLON:
+                    if child.type == syms.atom and child.children[0].type == token.LPAR:
+                        if maybe_make_parens_invisible_in_atom(
+                            child,
+                            parent=node,
+                            remove_brackets_around_comma=False,
+                        ):
+                            wrap_in_parentheses(node, child, visible=False)
+                    else:
+                        wrap_in_parentheses(node, child, visible=False)
+        yield from self.visit_default(node)
+
     def visit_funcdef(self, node: Node) -> Iterator[Line]:
         """Visit function definition."""
         if Preview.annotation_parens not in self.mode:
index a3ce20b8619494cfba2684e25034224eb36d2d07..bcd35b4d4be7eefdf8e2a173acb8b757372f50a3 100644 (file)
@@ -157,8 +157,11 @@ class Preview(Enum):
     one_element_subscript = auto()
     remove_block_trailing_newline = auto()
     remove_redundant_parens = 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()
     skip_magic_trailing_comma_in_subscript = auto()
+    wrap_long_dict_values_in_parens = auto()
 
 
 class Deprecated(UserWarning):
index 8893ab02aab8df61d670dcb6d148bd99647b43d6..b08a6d243d8981c90b8c9a38f396163c4a00f5b8 100644 (file)
@@ -1638,6 +1638,8 @@ class StringParenWrapper(BaseStringSplitter, CustomSplitMapMixin):
         * The line is a dictionary key assignment where some valid key is being
         assigned the value of some string.
             OR
+        * The line is an lambda expression and the value is a string.
+            OR
         * The line starts with an "atom" string that prefers to be wrapped in
         parens. It's preferred to be wrapped when the string is surrounded by
         commas (or is the first/last child).
@@ -1683,7 +1685,7 @@ class StringParenWrapper(BaseStringSplitter, CustomSplitMapMixin):
             or self._else_match(LL)
             or self._assert_match(LL)
             or self._assign_match(LL)
-            or self._dict_match(LL)
+            or self._dict_or_lambda_match(LL)
             or self._prefer_paren_wrap_match(LL)
         )
 
@@ -1841,22 +1843,23 @@ class StringParenWrapper(BaseStringSplitter, CustomSplitMapMixin):
         return None
 
     @staticmethod
-    def _dict_match(LL: List[Leaf]) -> Optional[int]:
+    def _dict_or_lambda_match(LL: List[Leaf]) -> Optional[int]:
         """
         Returns:
             string_idx such that @LL[string_idx] is equal to our target (i.e.
             matched) string, if this line matches the dictionary key assignment
-            statement requirements listed in the 'Requirements' section of this
-            classes' docstring.
+            statement or lambda expression requirements listed in the
+            'Requirements' section of this classes' docstring.
                 OR
             None, otherwise.
         """
-        # If this line is apart of a dictionary key assignment...
-        if syms.dictsetmaker in [parent_type(LL[0]), parent_type(LL[0].parent)]:
+        # If this line is a part of a dictionary key assignment or lambda expression...
+        parent_types = [parent_type(LL[0]), parent_type(LL[0].parent)]
+        if syms.dictsetmaker in parent_types or syms.lambdef in parent_types:
             is_valid_index = is_valid_index_factory(LL)
 
             for i, leaf in enumerate(LL):
-                # We MUST find a colon...
+                # We MUST find a colon, it can either be dict's or lambda's colon...
                 if leaf.type == token.COLON:
                     idx = i + 2 if is_empty_par(LL[i + 1]) else i + 1
 
@@ -1951,6 +1954,25 @@ class StringParenWrapper(BaseStringSplitter, CustomSplitMapMixin):
                     f" (left_leaves={left_leaves}, right_leaves={right_leaves})"
                 )
                 old_rpar_leaf = right_leaves.pop()
+            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},
+                #     }
+                # 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}),
+                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)
+                    if (
+                        index > 0
+                        and index < len(left_leaves) - 1
+                        and left_leaves[index - 1].type == token.COLON
+                        and left_leaves[index + 1].value == "lambda"
+                    ):
+                        right_leaves.pop()
 
             append_leaves(string_line, line, right_leaves)
 
diff --git a/tests/data/preview/long_dict_values.py b/tests/data/preview/long_dict_values.py
new file mode 100644 (file)
index 0000000..f23c5d3
--- /dev/null
@@ -0,0 +1,53 @@
+my_dict = {
+    "something_something":
+        r"Lorem ipsum dolor sit amet, an sed convenire eloquentiam \t"
+        r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t"
+        r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t",
+}
+
+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")
+}
+
+
+# output
+
+
+my_dict = {
+    "something_something": (
+        r"Lorem ipsum dolor sit amet, an sed convenire eloquentiam \t"
+        r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t"
+        r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t"
+    ),
+}
+
+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")
+    )
+}
index 9288b253b60e4c1950af3b0368a18861745bc281..9c78f675b8f89382307778411df41df0646326d6 100644 (file)
@@ -278,6 +278,15 @@ string_with_escaped_nameescape = (
     "........................................................................... \\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"
+
+dict_with_lambda_values = {
+    "join": lambda j: (
+        f"{j.__class__.__name__}({some_function_call(j.left)}, "
+        f"{some_function_call(j.right)})"
+    ),
+}
+
 
 # output
 
@@ -362,9 +371,8 @@ D4 = {
     "A %s %s"
     % ("formatted", "string"): (
         "This is a really really really long string that has to go inside of a"
-        " dictionary. It is %s bad (#%d)."
-    )
-    % ("soooo", 2),
+        " dictionary. It is %s bad (#%d)." % ("soooo", 2)
+    ),
 }
 
 D5 = {  # Test for https://github.com/psf/black/issues/3261
@@ -806,3 +814,17 @@ string_with_escaped_nameescape = (
     "..........................................................................."
     " \\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"
+    )
+)
+
+dict_with_lambda_values = {
+    "join": lambda j: (
+        f"{j.__class__.__name__}({some_function_call(j.left)}, "
+        f"{some_function_call(j.right)})"
+    ),
+}
index 8b00e76f40e5c7f9e8248a519c2aae3a9c2a48a6..6d56dcc635d63395de48821c3ec6f0bf4e0ec6a3 100644 (file)
@@ -524,6 +524,13 @@ xxxxxx_xxx_xxxx_xx_xxxxx_xxxxxxxx_xxxxxxxx_xxxxxxxxxx_xxxx_xxxx_xxxxx = xxxx.xxx
     },
 )
 
+# Regression test for https://github.com/psf/black/issues/3117.
+some_dict = {
+    "something_something":
+        r"Lorem ipsum dolor sit amet, an sed convenire eloquentiam \t"
+        r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t",
+}
+
 
 # output
 
@@ -1178,3 +1185,11 @@ xxxxxx_xxx_xxxx_xx_xxxxx_xxxxxxxx_xxxxxxxx_xxxxxxxxxx_xxxx_xxxx_xxxxx = xxxx.xxx
         ),
     },
 )
+
+# Regression test for https://github.com/psf/black/issues/3117.
+some_dict = {
+    "something_something": (
+        r"Lorem ipsum dolor sit amet, an sed convenire eloquentiam \t"
+        r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t"
+    ),
+}