Make parentheses invisible recursively in atoms
This fixes non-deterministic formatting when multiple pairs of removable parentheses are used. Fixes #183
This commit is contained in:
parent
dc0c14240e
commit
cfb003f51c
@ -538,9 +538,14 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md).
|
|||||||
|
|
||||||
### 18.5a0 (unreleased)
|
### 18.5a0 (unreleased)
|
||||||
|
|
||||||
* Slices are now formatted according to PEP 8 (#178)
|
* slices are now formatted according to PEP 8 (#178)
|
||||||
|
|
||||||
* Empty parentheses in a class definition are removed (#145, #180)
|
* empty parentheses in a class definition are now removed (#145, #180)
|
||||||
|
|
||||||
|
* fixed an invalid trailing comma sometimes left in imports (#185)
|
||||||
|
|
||||||
|
* fixed non-deterministic formatting when multiple pairs of removable parentheses
|
||||||
|
were used (#183)
|
||||||
|
|
||||||
|
|
||||||
### 18.4a4
|
### 18.4a4
|
||||||
|
35
black.py
35
black.py
@ -2187,17 +2187,7 @@ def normalize_invisible_parens(node: Node, parens_after: Set[str]) -> None:
|
|||||||
for child in list(node.children):
|
for child in list(node.children):
|
||||||
if check_lpar:
|
if check_lpar:
|
||||||
if child.type == syms.atom:
|
if child.type == syms.atom:
|
||||||
if not (
|
maybe_make_parens_invisible_in_atom(child)
|
||||||
is_empty_tuple(child)
|
|
||||||
or is_one_tuple(child)
|
|
||||||
or max_delimiter_priority_in_atom(child) >= COMMA_PRIORITY
|
|
||||||
):
|
|
||||||
first = child.children[0]
|
|
||||||
last = child.children[-1]
|
|
||||||
if first.type == token.LPAR and last.type == token.RPAR:
|
|
||||||
# make parentheses invisible
|
|
||||||
first.value = "" # type: ignore
|
|
||||||
last.value = "" # type: ignore
|
|
||||||
elif is_one_tuple(child):
|
elif is_one_tuple(child):
|
||||||
# wrap child in visible parentheses
|
# wrap child in visible parentheses
|
||||||
lpar = Leaf(token.LPAR, "(")
|
lpar = Leaf(token.LPAR, "(")
|
||||||
@ -2214,6 +2204,29 @@ def normalize_invisible_parens(node: Node, parens_after: Set[str]) -> None:
|
|||||||
check_lpar = isinstance(child, Leaf) and child.value in parens_after
|
check_lpar = isinstance(child, Leaf) and child.value in parens_after
|
||||||
|
|
||||||
|
|
||||||
|
def maybe_make_parens_invisible_in_atom(node: LN) -> bool:
|
||||||
|
"""If it's safe, make the parens in the atom `node` invisible, recusively."""
|
||||||
|
if (
|
||||||
|
node.type != syms.atom
|
||||||
|
or is_empty_tuple(node)
|
||||||
|
or is_one_tuple(node)
|
||||||
|
or max_delimiter_priority_in_atom(node) >= COMMA_PRIORITY
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
first = node.children[0]
|
||||||
|
last = node.children[-1]
|
||||||
|
if first.type == token.LPAR and last.type == token.RPAR:
|
||||||
|
# make parentheses invisible
|
||||||
|
first.value = "" # type: ignore
|
||||||
|
last.value = "" # type: ignore
|
||||||
|
if len(node.children) > 1:
|
||||||
|
maybe_make_parens_invisible_in_atom(node.children[1])
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def is_empty_tuple(node: LN) -> bool:
|
def is_empty_tuple(node: LN) -> bool:
|
||||||
"""Return True if `node` holds an empty tuple."""
|
"""Return True if `node` holds an empty tuple."""
|
||||||
return (
|
return (
|
||||||
|
@ -98,6 +98,8 @@ Utilities
|
|||||||
|
|
||||||
.. autofunction:: black.make_comment
|
.. autofunction:: black.make_comment
|
||||||
|
|
||||||
|
.. autofunction:: black.maybe_make_parens_invisible_in_atom
|
||||||
|
|
||||||
.. autofunction:: black.max_delimiter_priority_in_atom
|
.. autofunction:: black.max_delimiter_priority_in_atom
|
||||||
|
|
||||||
.. autofunction:: black.normalize_prefix
|
.. autofunction:: black.normalize_prefix
|
||||||
|
@ -129,7 +129,7 @@
|
|||||||
]
|
]
|
||||||
slice[0]
|
slice[0]
|
||||||
slice[0:1]
|
slice[0:1]
|
||||||
@@ -123,88 +145,114 @@
|
@@ -123,91 +145,119 @@
|
||||||
numpy[-(c + 1) :, d]
|
numpy[-(c + 1) :, d]
|
||||||
numpy[:, l[-2]]
|
numpy[:, l[-2]]
|
||||||
numpy[:, ::-1]
|
numpy[:, ::-1]
|
||||||
@ -201,6 +201,9 @@
|
|||||||
+print(*[] or [1])
|
+print(*[] or [1])
|
||||||
print(**{1: 3} if False else {x: x for x in range(3)})
|
print(**{1: 3} if False else {x: x for x in range(3)})
|
||||||
-print(* lambda x: x)
|
-print(* lambda x: x)
|
||||||
|
-assert(not Test),("Short message")
|
||||||
|
-assert this is ComplexTest and not requirements.fit_in_a_single_line(force=False), "Short message"
|
||||||
|
-assert(((parens is TooMany)))
|
||||||
-for x, in (1,), (2,), (3,): ...
|
-for x, in (1,), (2,), (3,): ...
|
||||||
-for y in (): ...
|
-for y in (): ...
|
||||||
-for z in (i for i in (1, 2, 3)): ...
|
-for z in (i for i in (1, 2, 3)): ...
|
||||||
@ -242,6 +245,11 @@
|
|||||||
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa /
|
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa /
|
||||||
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
+print(*lambda x: x)
|
+print(*lambda x: x)
|
||||||
|
+assert not Test, "Short message"
|
||||||
|
+assert (
|
||||||
|
+ this is ComplexTest and not requirements.fit_in_a_single_line(force=False)
|
||||||
|
+), "Short message"
|
||||||
|
+assert parens is TooMany
|
||||||
+for (x,) in (1,), (2,), (3,):
|
+for (x,) in (1,), (2,), (3,):
|
||||||
+ ...
|
+ ...
|
||||||
+for y in ():
|
+for y in ():
|
||||||
|
@ -163,6 +163,9 @@ async def f():
|
|||||||
print(* [] or [1])
|
print(* [] or [1])
|
||||||
print(**{1: 3} if False else {x: x for x in range(3)})
|
print(**{1: 3} if False else {x: x for x in range(3)})
|
||||||
print(* lambda x: x)
|
print(* lambda x: x)
|
||||||
|
assert(not Test),("Short message")
|
||||||
|
assert this is ComplexTest and not requirements.fit_in_a_single_line(force=False), "Short message"
|
||||||
|
assert(((parens is TooMany)))
|
||||||
for x, in (1,), (2,), (3,): ...
|
for x, in (1,), (2,), (3,): ...
|
||||||
for y in (): ...
|
for y in (): ...
|
||||||
for z in (i for i in (1, 2, 3)): ...
|
for z in (i for i in (1, 2, 3)): ...
|
||||||
@ -419,6 +422,11 @@ async def f():
|
|||||||
print(*[] or [1])
|
print(*[] or [1])
|
||||||
print(**{1: 3} if False else {x: x for x in range(3)})
|
print(**{1: 3} if False else {x: x for x in range(3)})
|
||||||
print(*lambda x: x)
|
print(*lambda x: x)
|
||||||
|
assert not Test, "Short message"
|
||||||
|
assert (
|
||||||
|
this is ComplexTest and not requirements.fit_in_a_single_line(force=False)
|
||||||
|
), "Short message"
|
||||||
|
assert parens is TooMany
|
||||||
for (x,) in (1,), (2,), (3,):
|
for (x,) in (1,), (2,), (3,):
|
||||||
...
|
...
|
||||||
for y in ():
|
for y in ():
|
||||||
|
Loading…
Reference in New Issue
Block a user