Skip to content

Commit

Permalink
Always explode data structure literals
Browse files Browse the repository at this point in the history
Fixes #152
  • Loading branch information
ambv committed May 18, 2018
1 parent dafa12f commit dd4477b
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 52 deletions.
30 changes: 18 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,13 @@ brackets and put that in a separate indented line.
```py3
# in:

l = [[n for n in list_bosses()], [n for n in list_employees()]]
TracebackException.from_exception(exc, limit, lookup_lines, capture_locals)

# out:

l = [
[n for n in list_bosses()], [n for n in list_employees()]
]
TracebackException.from_exception(
exc, limit, lookup_lines, capture_locals
)
```

If that still doesn't fit the bill, it will decompose the internal
Expand Down Expand Up @@ -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
example above).

If a line of "from" imports cannot fit in the allotted length, it's always split
into one per line. Imports tend to change often and this minimizes diffs, as well
as enables readers of code to easily find which commit introduced a particular
import. This exception also makes *Black* compatible with
[isort](https://pypi.org/p/isort/). Use `multi_line_output=3`,
`include_trailing_comma=True`, `force_grid_wrap=0`, and `line_length=88` in your
isort config.
If a data structure literal (tuple, list, set, dict) or a line of "from"
imports cannot fit in the allotted length, it's always split into one
per line. This minimizes diffs as well as enables readers of code to
find which commit introduced a particular entry. This also makes
*Black* compatible with [isort](https://pypi.org/p/isort/). Use
`multi_line_output=3`, `include_trailing_comma=True`,
`force_grid_wrap=0`, and `line_length=88` in your isort config.


### Line length
Expand Down Expand Up @@ -630,7 +630,13 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md).

### 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)

Expand Down
61 changes: 34 additions & 27 deletions black.py
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,7 @@ class Line:
comments: List[Tuple[Index, Leaf]] = Factory(list)
bracket_tracker: BracketTracker = Factory(BracketTracker)
inside_brackets: bool = False
should_explode: bool = False

def append(self, leaf: Leaf, preformatted: bool = False) -> None:
"""Add a new `leaf` to the end of the line.
Expand Down Expand Up @@ -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}"
if t == token.COLON and p.type not in {
syms.subscript, syms.subscriptlist, syms.sliceop
syms.subscript,
syms.subscriptlist,
syms.sliceop,
}:
return NO

Expand All @@ -1495,7 +1498,10 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa C901
if prevp.type == token.EQUAL:
if prevp.parent:
if prevp.parent.type in {
syms.arglist, syms.argument, syms.parameters, syms.varargslist
syms.arglist,
syms.argument,
syms.parameters,
syms.varargslist,
}:
return NO

Expand Down Expand Up @@ -1649,7 +1655,8 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa C901
prevp_parent = prevp.parent
assert prevp_parent is not None
if prevp.type == token.COLON and prevp_parent.type in {
syms.subscript, syms.sliceop
syms.subscript,
syms.sliceop,
}:
return NO

Expand Down Expand Up @@ -1902,15 +1909,15 @@ def split_line(
return

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
return

split_funcs: List[SplitFunc]
if line.is_def:
split_funcs = [left_hand_split]
elif line.is_import:
split_funcs = [explode_split]
else:

def rhs(line: Line, py36: bool = False) -> Iterator[Line]:
Expand Down Expand Up @@ -2073,6 +2080,7 @@ def right_hand_split(

ensure_visible(opening_bracket)
ensure_visible(closing_bracket)
body.should_explode = should_explode(body, opening_bracket)
for result in (head, body, tail):
if result:
yield result
Expand Down Expand Up @@ -2212,26 +2220,6 @@ def append_to_line(leaf: Leaf) -> Iterator[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:
"""Return True if the given leaf starts an import statement."""
p = leaf.parent
Expand Down Expand Up @@ -2547,6 +2535,17 @@ def ensure_visible(leaf: Leaf) -> None:
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:
"""Return True if the current file is using Python 3.6+ features.
Expand Down Expand Up @@ -2675,7 +2674,15 @@ def get_future_imports(node: Node) -> Set[str]:

PYTHON_EXTENSIONS = {".py", ".pyi"}
BLACKLISTED_DIRECTORIES = {
"build", "buck-out", "dist", "_build", ".git", ".hg", ".mypy_cache", ".tox", ".venv"
"build",
"buck-out",
"dist",
"_build",
".git",
".hg",
".mypy_cache",
".tox",
".venv",
}


Expand Down
2 changes: 0 additions & 2 deletions docs/reference/reference_functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,6 @@ Split functions

.. autofunction:: black.delimiter_split

.. autofunction:: black.explode_split

.. autofunction:: black.left_hand_split

.. autofunction:: black.right_hand_split
Expand Down
4 changes: 3 additions & 1 deletion tests/cantfit.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
1
) # with a comment
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 = (
function()
Expand Down
8 changes: 3 additions & 5 deletions tests/comments2.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@
def inline_comments_in_brackets_ruin_everything():
if typedargslist:
parameters.children = [
parameters.children[0], # (1
children[0], # (1
body,
parameters.children[-1], # )1
children[-1], # )1
]
else:
parameters.children = [
Expand Down Expand Up @@ -163,9 +163,7 @@ def inline_comments_in_brackets_ruin_everything():
# Comment before function.
def inline_comments_in_brackets_ruin_everything():
if typedargslist:
parameters.children = [
parameters.children[0], body, parameters.children[-1] # (1 # )1
]
parameters.children = [children[0], body, children[-1]] # (1 # )1
else:
parameters.children = [
parameters.children[0], # (2 what if this was actually long
Expand Down
9 changes: 5 additions & 4 deletions tests/expression.diff
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
True
False
1
@@ -29,62 +29,82 @@
@@ -29,62 +29,83 @@
~great
+value
-1
Expand All @@ -29,7 +29,8 @@
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
+ "port1": port1_resource,
+ "port2": port2_resource,
+}[port_id]
1 if True else 2
str or None if True else str or bytes or None
Expand Down Expand Up @@ -115,7 +116,7 @@
call(**self.screen_kwargs)
call(b, **self.screen_kwargs)
lukasz.langa.pl
@@ -93,11 +113,11 @@
@@ -93,11 +114,11 @@
1.0 .real
....__class__
list[str]
Expand All @@ -128,7 +129,7 @@
]
slice[0]
slice[0:1]
@@ -124,107 +144,159 @@
@@ -124,107 +145,159 @@
numpy[-(c + 1) :, d]
numpy[:, l[-2]]
numpy[:, ::-1]
Expand Down
3 changes: 2 additions & 1 deletion tests/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,8 @@ async def f():
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()
foo = lambda port_id, ignore_missing: {
"port1": port1_resource, "port2": port2_resource
"port1": port1_resource,
"port2": port2_resource,
}[port_id]
1 if True else 2
str or None if True else str or bytes or None
Expand Down

0 comments on commit dd4477b

Please sign in to comment.