Always explode data structure literals

Fixes #152
This commit is contained in:
Łukasz Langa 2018-05-16 21:33:29 -07:00
parent dafa12f10b
commit dd4477b701
7 changed files with 65 additions and 52 deletions

View File

@ -131,13 +131,13 @@ brackets and put that in a separate indented line.
```py3 ```py3
# in: # in:
l = [[n for n in list_bosses()], [n for n in list_employees()]] TracebackException.from_exception(exc, limit, lookup_lines, capture_locals)
# out: # out:
l = [ TracebackException.from_exception(
[n for n in list_bosses()], [n for n in list_employees()] exc, limit, lookup_lines, capture_locals
] )
``` ```
If that still doesn't fit the bill, it will decompose the internal If that still doesn't fit the bill, it will decompose the internal
@ -176,13 +176,13 @@ between two distinct sections of the code that otherwise share the same
indentation level (like the arguments list and the docstring in the indentation level (like the arguments list and the docstring in the
example above). example above).
If a line of "from" imports cannot fit in the allotted length, it's always split If a data structure literal (tuple, list, set, dict) or a line of "from"
into one per line. Imports tend to change often and this minimizes diffs, as well imports cannot fit in the allotted length, it's always split into one
as enables readers of code to easily find which commit introduced a particular per line. This minimizes diffs as well as enables readers of code to
import. This exception also makes *Black* compatible with find which commit introduced a particular entry. This also makes
[isort](https://pypi.org/p/isort/). Use `multi_line_output=3`, *Black* compatible with [isort](https://pypi.org/p/isort/). Use
`include_trailing_comma=True`, `force_grid_wrap=0`, and `line_length=88` in your `multi_line_output=3`, `include_trailing_comma=True`,
isort config. `force_grid_wrap=0`, and `line_length=88` in your isort config.
### Line length ### Line length
@ -630,7 +630,13 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md).
### 18.5a0 (unreleased) ### 18.5a0 (unreleased)
* call chains are now formatted according to the [fluent interfaces](https://en.wikipedia.org/wiki/Fluent_interface) style (#67) * call chains are now formatted according to the
[fluent interfaces](https://en.wikipedia.org/wiki/Fluent_interface)
style (#67)
* data structure literals (tuples, lists, dictionaries, and sets) are
now also always exploded like imports when they don't fit in a single
line (#152)
* slices are now formatted according to PEP 8 (#178) * slices are now formatted according to PEP 8 (#178)

View File

@ -776,6 +776,7 @@ class Line:
comments: List[Tuple[Index, Leaf]] = Factory(list) comments: List[Tuple[Index, Leaf]] = Factory(list)
bracket_tracker: BracketTracker = Factory(BracketTracker) bracket_tracker: BracketTracker = Factory(BracketTracker)
inside_brackets: bool = False inside_brackets: bool = False
should_explode: bool = False
def append(self, leaf: Leaf, preformatted: bool = False) -> None: def append(self, leaf: Leaf, preformatted: bool = False) -> None:
"""Add a new `leaf` to the end of the line. """Add a new `leaf` to the end of the line.
@ -1473,7 +1474,9 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa C901
assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}" assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}"
if t == token.COLON and p.type not in { if t == token.COLON and p.type not in {
syms.subscript, syms.subscriptlist, syms.sliceop syms.subscript,
syms.subscriptlist,
syms.sliceop,
}: }:
return NO return NO
@ -1495,7 +1498,10 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa C901
if prevp.type == token.EQUAL: if prevp.type == token.EQUAL:
if prevp.parent: if prevp.parent:
if prevp.parent.type in { if prevp.parent.type in {
syms.arglist, syms.argument, syms.parameters, syms.varargslist syms.arglist,
syms.argument,
syms.parameters,
syms.varargslist,
}: }:
return NO return NO
@ -1649,7 +1655,8 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa C901
prevp_parent = prevp.parent prevp_parent = prevp.parent
assert prevp_parent is not None assert prevp_parent is not None
if prevp.type == token.COLON and prevp_parent.type in { if prevp.type == token.COLON and prevp_parent.type in {
syms.subscript, syms.sliceop syms.subscript,
syms.sliceop,
}: }:
return NO return NO
@ -1902,15 +1909,15 @@ def split_line(
return return
line_str = str(line).strip("\n") line_str = str(line).strip("\n")
if is_line_short_enough(line, line_length=line_length, line_str=line_str): if not line.should_explode and is_line_short_enough(
line, line_length=line_length, line_str=line_str
):
yield line yield line
return return
split_funcs: List[SplitFunc] split_funcs: List[SplitFunc]
if line.is_def: if line.is_def:
split_funcs = [left_hand_split] split_funcs = [left_hand_split]
elif line.is_import:
split_funcs = [explode_split]
else: else:
def rhs(line: Line, py36: bool = False) -> Iterator[Line]: def rhs(line: Line, py36: bool = False) -> Iterator[Line]:
@ -2073,6 +2080,7 @@ def right_hand_split(
ensure_visible(opening_bracket) ensure_visible(opening_bracket)
ensure_visible(closing_bracket) ensure_visible(closing_bracket)
body.should_explode = should_explode(body, opening_bracket)
for result in (head, body, tail): for result in (head, body, tail):
if result: if result:
yield result yield result
@ -2212,26 +2220,6 @@ def append_to_line(leaf: Leaf) -> Iterator[Line]:
yield current_line yield current_line
def explode_split(
line: Line, py36: bool = False, omit: Collection[LeafID] = ()
) -> Iterator[Line]:
"""Split by rightmost bracket and immediately split contents by a delimiter."""
new_lines = list(right_hand_split(line, py36, omit))
if len(new_lines) != 3:
yield from new_lines
return
yield new_lines[0]
try:
yield from delimiter_split(new_lines[1], py36)
except CannotSplit:
yield new_lines[1]
yield new_lines[2]
def is_import(leaf: Leaf) -> bool: def is_import(leaf: Leaf) -> bool:
"""Return True if the given leaf starts an import statement.""" """Return True if the given leaf starts an import statement."""
p = leaf.parent p = leaf.parent
@ -2547,6 +2535,17 @@ def ensure_visible(leaf: Leaf) -> None:
leaf.value = ")" leaf.value = ")"
def should_explode(line: Line, opening_bracket: Leaf) -> bool:
"""Should `line` immediately be split with `delimiter_split()` after RHS?"""
return bool(
opening_bracket.parent
and opening_bracket.parent.type in {syms.atom, syms.import_from}
and opening_bracket.value in "[{("
and line.bracket_tracker.delimiters
and line.bracket_tracker.max_delimiter_priority() == COMMA_PRIORITY
)
def is_python36(node: Node) -> bool: def is_python36(node: Node) -> bool:
"""Return True if the current file is using Python 3.6+ features. """Return True if the current file is using Python 3.6+ features.
@ -2675,7 +2674,15 @@ def get_future_imports(node: Node) -> Set[str]:
PYTHON_EXTENSIONS = {".py", ".pyi"} PYTHON_EXTENSIONS = {".py", ".pyi"}
BLACKLISTED_DIRECTORIES = { BLACKLISTED_DIRECTORIES = {
"build", "buck-out", "dist", "_build", ".git", ".hg", ".mypy_cache", ".tox", ".venv" "build",
"buck-out",
"dist",
"_build",
".git",
".hg",
".mypy_cache",
".tox",
".venv",
} }

View File

@ -66,8 +66,6 @@ Split functions
.. autofunction:: black.delimiter_split .. autofunction:: black.delimiter_split
.. autofunction:: black.explode_split
.. autofunction:: black.left_hand_split .. autofunction:: black.left_hand_split
.. autofunction:: black.right_hand_split .. autofunction:: black.right_hand_split

View File

@ -38,7 +38,9 @@
1 1
) # with a comment ) # with a comment
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = [ this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = [
1, 2, 3 1,
2,
3,
] ]
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = ( this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = (
function() function()

View File

@ -35,9 +35,9 @@
def inline_comments_in_brackets_ruin_everything(): def inline_comments_in_brackets_ruin_everything():
if typedargslist: if typedargslist:
parameters.children = [ parameters.children = [
parameters.children[0], # (1 children[0], # (1
body, body,
parameters.children[-1], # )1 children[-1], # )1
] ]
else: else:
parameters.children = [ parameters.children = [
@ -163,9 +163,7 @@ def inline_comments_in_brackets_ruin_everything():
# Comment before function. # Comment before function.
def inline_comments_in_brackets_ruin_everything(): def inline_comments_in_brackets_ruin_everything():
if typedargslist: if typedargslist:
parameters.children = [ parameters.children = [children[0], body, children[-1]] # (1 # )1
parameters.children[0], body, parameters.children[-1] # (1 # )1
]
else: else:
parameters.children = [ parameters.children = [
parameters.children[0], # (2 what if this was actually long parameters.children[0], # (2 what if this was actually long

View File

@ -11,7 +11,7 @@
True True
False False
1 1
@@ -29,62 +29,82 @@ @@ -29,62 +29,83 @@
~great ~great
+value +value
-1 -1
@ -29,7 +29,8 @@
manylambdas = lambda x=lambda y=lambda z=1: z: y(): x() manylambdas = lambda x=lambda y=lambda z=1: z: y(): x()
-foo = (lambda port_id, ignore_missing: {"port1": port1_resource, "port2": port2_resource}[port_id]) -foo = (lambda port_id, ignore_missing: {"port1": port1_resource, "port2": port2_resource}[port_id])
+foo = lambda port_id, ignore_missing: { +foo = lambda port_id, ignore_missing: {
+ "port1": port1_resource, "port2": port2_resource + "port1": port1_resource,
+ "port2": port2_resource,
+}[port_id] +}[port_id]
1 if True else 2 1 if True else 2
str or None if True else str or bytes or None str or None if True else str or bytes or None
@ -115,7 +116,7 @@
call(**self.screen_kwargs) call(**self.screen_kwargs)
call(b, **self.screen_kwargs) call(b, **self.screen_kwargs)
lukasz.langa.pl lukasz.langa.pl
@@ -93,11 +113,11 @@ @@ -93,11 +114,11 @@
1.0 .real 1.0 .real
....__class__ ....__class__
list[str] list[str]
@ -128,7 +129,7 @@
] ]
slice[0] slice[0]
slice[0:1] slice[0:1]
@@ -124,107 +144,159 @@ @@ -124,107 +145,159 @@
numpy[-(c + 1) :, d] numpy[-(c + 1) :, d]
numpy[:, l[-2]] numpy[:, l[-2]]
numpy[:, ::-1] numpy[:, ::-1]

View File

@ -273,7 +273,8 @@ async def f():
lambda a, b, c=True, *vararg, d=(v1 << 2), e="str", **kwargs: a + b lambda a, b, c=True, *vararg, d=(v1 << 2), e="str", **kwargs: a + b
manylambdas = lambda x=lambda y=lambda z=1: z: y(): x() manylambdas = lambda x=lambda y=lambda z=1: z: y(): x()
foo = lambda port_id, ignore_missing: { foo = lambda port_id, ignore_missing: {
"port1": port1_resource, "port2": port2_resource "port1": port1_resource,
"port2": port2_resource,
}[port_id] }[port_id]
1 if True else 2 1 if True else 2
str or None if True else str or bytes or None str or None if True else str or bytes or None