Fix instability due to trailing comma logic (#2572)
It was causing stability issues because the first pass could cause a "magic trailing comma" to appear, meaning that the second pass might get a different result. It's not critical. Some things format differently (with extra parens)
This commit is contained in:
parent
95e77cb559
commit
a24e1f7959
@ -139,6 +139,7 @@ and the first release covered by our new stability policy.
|
|||||||
when `--target-version py310` is explicitly specified (#2586)
|
when `--target-version py310` is explicitly specified (#2586)
|
||||||
- Add support for parenthesized with (#2586)
|
- Add support for parenthesized with (#2586)
|
||||||
- Declare support for Python 3.10 for running Black (#2562)
|
- Declare support for Python 3.10 for running Black (#2562)
|
||||||
|
- Fix unstable black runs around magic trailing comma (#2572)
|
||||||
|
|
||||||
### Integrations
|
### Integrations
|
||||||
|
|
||||||
|
@ -1332,7 +1332,7 @@ def get_imports_from_children(children: List[LN]) -> Generator[str, None, None]:
|
|||||||
return imports
|
return imports
|
||||||
|
|
||||||
|
|
||||||
def assert_equivalent(src: str, dst: str, *, pass_num: int = 1) -> None:
|
def assert_equivalent(src: str, dst: str) -> None:
|
||||||
"""Raise AssertionError if `src` and `dst` aren't equivalent."""
|
"""Raise AssertionError if `src` and `dst` aren't equivalent."""
|
||||||
try:
|
try:
|
||||||
src_ast = parse_ast(src)
|
src_ast = parse_ast(src)
|
||||||
@ -1349,7 +1349,7 @@ def assert_equivalent(src: str, dst: str, *, pass_num: int = 1) -> None:
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log = dump_to_file("".join(traceback.format_tb(exc.__traceback__)), dst)
|
log = dump_to_file("".join(traceback.format_tb(exc.__traceback__)), dst)
|
||||||
raise AssertionError(
|
raise AssertionError(
|
||||||
f"INTERNAL ERROR: Black produced invalid code on pass {pass_num}: {exc}. "
|
f"INTERNAL ERROR: Black produced invalid code: {exc}. "
|
||||||
"Please report a bug on https://github.com/psf/black/issues. "
|
"Please report a bug on https://github.com/psf/black/issues. "
|
||||||
f"This invalid output might be helpful: {log}"
|
f"This invalid output might be helpful: {log}"
|
||||||
) from None
|
) from None
|
||||||
@ -1360,7 +1360,7 @@ def assert_equivalent(src: str, dst: str, *, pass_num: int = 1) -> None:
|
|||||||
log = dump_to_file(diff(src_ast_str, dst_ast_str, "src", "dst"))
|
log = dump_to_file(diff(src_ast_str, dst_ast_str, "src", "dst"))
|
||||||
raise AssertionError(
|
raise AssertionError(
|
||||||
"INTERNAL ERROR: Black produced code that is not equivalent to the"
|
"INTERNAL ERROR: Black produced code that is not equivalent to the"
|
||||||
f" source on pass {pass_num}. Please report a bug on "
|
f" source. Please report a bug on "
|
||||||
f"https://github.com/psf/black/issues. This diff might be helpful: {log}"
|
f"https://github.com/psf/black/issues. This diff might be helpful: {log}"
|
||||||
) from None
|
) from None
|
||||||
|
|
||||||
|
@ -543,7 +543,7 @@ def right_hand_split(
|
|||||||
# there are no standalone comments in the body
|
# there are no standalone comments in the body
|
||||||
and not body.contains_standalone_comments(0)
|
and not body.contains_standalone_comments(0)
|
||||||
# and we can actually remove the parens
|
# and we can actually remove the parens
|
||||||
and can_omit_invisible_parens(body, line_length, omit_on_explode=omit)
|
and can_omit_invisible_parens(body, line_length)
|
||||||
):
|
):
|
||||||
omit = {id(closing_bracket), *omit}
|
omit = {id(closing_bracket), *omit}
|
||||||
try:
|
try:
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
import sys
|
import sys
|
||||||
from typing import (
|
from typing import (
|
||||||
Callable,
|
Callable,
|
||||||
Collection,
|
|
||||||
Dict,
|
Dict,
|
||||||
Iterator,
|
Iterator,
|
||||||
List,
|
List,
|
||||||
@ -22,7 +21,7 @@
|
|||||||
from black.nodes import STANDALONE_COMMENT, TEST_DESCENDANTS
|
from black.nodes import STANDALONE_COMMENT, TEST_DESCENDANTS
|
||||||
from black.nodes import BRACKETS, OPENING_BRACKETS, CLOSING_BRACKETS
|
from black.nodes import BRACKETS, OPENING_BRACKETS, CLOSING_BRACKETS
|
||||||
from black.nodes import syms, whitespace, replace_child, child_towards
|
from black.nodes import syms, whitespace, replace_child, child_towards
|
||||||
from black.nodes import is_multiline_string, is_import, is_type_comment, last_two_except
|
from black.nodes import is_multiline_string, is_import, is_type_comment
|
||||||
from black.nodes import is_one_tuple_between
|
from black.nodes import is_one_tuple_between
|
||||||
|
|
||||||
# types
|
# types
|
||||||
@ -645,7 +644,6 @@ def can_be_split(line: Line) -> bool:
|
|||||||
def can_omit_invisible_parens(
|
def can_omit_invisible_parens(
|
||||||
line: Line,
|
line: Line,
|
||||||
line_length: int,
|
line_length: int,
|
||||||
omit_on_explode: Collection[LeafID] = (),
|
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Does `line` have a shape safe to reformat without optional parens around it?
|
"""Does `line` have a shape safe to reformat without optional parens around it?
|
||||||
|
|
||||||
@ -683,12 +681,6 @@ def can_omit_invisible_parens(
|
|||||||
|
|
||||||
penultimate = line.leaves[-2]
|
penultimate = line.leaves[-2]
|
||||||
last = line.leaves[-1]
|
last = line.leaves[-1]
|
||||||
if line.magic_trailing_comma:
|
|
||||||
try:
|
|
||||||
penultimate, last = last_two_except(line.leaves, omit=omit_on_explode)
|
|
||||||
except LookupError:
|
|
||||||
# Turns out we'd omit everything. We cannot skip the optional parentheses.
|
|
||||||
return False
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
last.type == token.RPAR
|
last.type == token.RPAR
|
||||||
@ -710,10 +702,6 @@ def can_omit_invisible_parens(
|
|||||||
# unnecessary.
|
# unnecessary.
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if line.magic_trailing_comma and penultimate.type == token.COMMA:
|
|
||||||
# The rightmost non-omitted bracket pair is the one we want to explode on.
|
|
||||||
return True
|
|
||||||
|
|
||||||
if _can_omit_closing_paren(line, last=last, line_length=line_length):
|
if _can_omit_closing_paren(line, last=last, line_length=line_length):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -4,13 +4,11 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
from typing import (
|
from typing import (
|
||||||
Collection,
|
|
||||||
Generic,
|
Generic,
|
||||||
Iterator,
|
Iterator,
|
||||||
List,
|
List,
|
||||||
Optional,
|
Optional,
|
||||||
Set,
|
Set,
|
||||||
Tuple,
|
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Union,
|
Union,
|
||||||
)
|
)
|
||||||
@ -439,27 +437,6 @@ def prev_siblings_are(node: Optional[LN], tokens: List[Optional[NodeType]]) -> b
|
|||||||
return prev_siblings_are(node.prev_sibling, tokens[:-1])
|
return prev_siblings_are(node.prev_sibling, tokens[:-1])
|
||||||
|
|
||||||
|
|
||||||
def last_two_except(leaves: List[Leaf], omit: Collection[LeafID]) -> Tuple[Leaf, Leaf]:
|
|
||||||
"""Return (penultimate, last) leaves skipping brackets in `omit` and contents."""
|
|
||||||
stop_after: Optional[Leaf] = None
|
|
||||||
last: Optional[Leaf] = None
|
|
||||||
for leaf in reversed(leaves):
|
|
||||||
if stop_after:
|
|
||||||
if leaf is stop_after:
|
|
||||||
stop_after = None
|
|
||||||
continue
|
|
||||||
|
|
||||||
if last:
|
|
||||||
return leaf, last
|
|
||||||
|
|
||||||
if id(leaf) in omit:
|
|
||||||
stop_after = leaf.opening_bracket
|
|
||||||
else:
|
|
||||||
last = leaf
|
|
||||||
else:
|
|
||||||
raise LookupError("Last two leaves were also skipped")
|
|
||||||
|
|
||||||
|
|
||||||
def parent_type(node: Optional[LN]) -> Optional[NodeType]:
|
def parent_type(node: Optional[LN]) -> Optional[NodeType]:
|
||||||
"""
|
"""
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -89,16 +89,19 @@ def f(
|
|||||||
"a": 1,
|
"a": 1,
|
||||||
"b": 2,
|
"b": 2,
|
||||||
}["a"]
|
}["a"]
|
||||||
if a == {
|
if (
|
||||||
"a": 1,
|
a
|
||||||
"b": 2,
|
== {
|
||||||
"c": 3,
|
"a": 1,
|
||||||
"d": 4,
|
"b": 2,
|
||||||
"e": 5,
|
"c": 3,
|
||||||
"f": 6,
|
"d": 4,
|
||||||
"g": 7,
|
"e": 5,
|
||||||
"h": 8,
|
"f": 6,
|
||||||
}["a"]:
|
"g": 7,
|
||||||
|
"h": 8,
|
||||||
|
}["a"]
|
||||||
|
):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -133,11 +133,14 @@
|
|||||||
"Use f-strings instead!",
|
"Use f-strings instead!",
|
||||||
)
|
)
|
||||||
|
|
||||||
old_fmt_string3 = "Whereas only the strings after the percent sign were long in the last example, this example uses a long initial string as well. This is another %s %s %s %s" % (
|
old_fmt_string3 = (
|
||||||
"really really really really really",
|
"Whereas only the strings after the percent sign were long in the last example, this example uses a long initial string as well. This is another %s %s %s %s"
|
||||||
"old",
|
% (
|
||||||
"way to format strings!",
|
"really really really really really",
|
||||||
"Use f-strings instead!",
|
"old",
|
||||||
|
"way to format strings!",
|
||||||
|
"Use f-strings instead!",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
fstring = f"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one."
|
fstring = f"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one."
|
||||||
|
@ -31,20 +31,17 @@ def test(self, othr):
|
|||||||
** 101234234242352525425252352352525234890264906820496920680926538059059209922523523525
|
** 101234234242352525425252352352525234890264906820496920680926538059059209922523523525
|
||||||
) #
|
) #
|
||||||
|
|
||||||
assert (
|
assert sort_by_dependency(
|
||||||
sort_by_dependency(
|
{
|
||||||
{
|
"1": {"2", "3"},
|
||||||
"1": {"2", "3"},
|
"2": {"2a", "2b"},
|
||||||
"2": {"2a", "2b"},
|
"3": {"3a", "3b"},
|
||||||
"3": {"3a", "3b"},
|
"2a": set(),
|
||||||
"2a": set(),
|
"2b": set(),
|
||||||
"2b": set(),
|
"3a": set(),
|
||||||
"3a": set(),
|
"3b": set(),
|
||||||
"3b": set(),
|
}
|
||||||
}
|
) == ["2a", "2b", "2", "3a", "3b", "3", "1"]
|
||||||
)
|
|
||||||
== ["2a", "2b", "2", "3a", "3b", "3", "1"]
|
|
||||||
)
|
|
||||||
|
|
||||||
importA
|
importA
|
||||||
0
|
0
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
_winapi.ERROR_PIPE_BUSY) or _check_timeout(t):
|
_winapi.ERROR_PIPE_BUSY) or _check_timeout(t):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if x:
|
||||||
|
if y:
|
||||||
|
new_id = max(Vegetable.objects.order_by('-id')[0].id,
|
||||||
|
Mineral.objects.order_by('-id')[0].id) + 1
|
||||||
|
|
||||||
class X:
|
class X:
|
||||||
def get_help_text(self):
|
def get_help_text(self):
|
||||||
@ -23,39 +27,37 @@ def b(self):
|
|||||||
|
|
||||||
# output
|
# output
|
||||||
|
|
||||||
if (
|
if e1234123412341234.winerror not in (
|
||||||
e1234123412341234.winerror
|
_winapi.ERROR_SEM_TIMEOUT,
|
||||||
not in (
|
_winapi.ERROR_PIPE_BUSY,
|
||||||
_winapi.ERROR_SEM_TIMEOUT,
|
) or _check_timeout(t):
|
||||||
_winapi.ERROR_PIPE_BUSY,
|
|
||||||
)
|
|
||||||
or _check_timeout(t)
|
|
||||||
):
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if x:
|
||||||
|
if y:
|
||||||
|
new_id = (
|
||||||
|
max(
|
||||||
|
Vegetable.objects.order_by("-id")[0].id,
|
||||||
|
Mineral.objects.order_by("-id")[0].id,
|
||||||
|
)
|
||||||
|
+ 1
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class X:
|
class X:
|
||||||
def get_help_text(self):
|
def get_help_text(self):
|
||||||
return (
|
return ngettext(
|
||||||
ngettext(
|
"Your password must contain at least %(min_length)d character.",
|
||||||
"Your password must contain at least %(min_length)d character.",
|
"Your password must contain at least %(min_length)d characters.",
|
||||||
"Your password must contain at least %(min_length)d characters.",
|
self.min_length,
|
||||||
self.min_length,
|
) % {"min_length": self.min_length}
|
||||||
)
|
|
||||||
% {"min_length": self.min_length}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class A:
|
class A:
|
||||||
def b(self):
|
def b(self):
|
||||||
if (
|
if self.connection.mysql_is_mariadb and (
|
||||||
self.connection.mysql_is_mariadb
|
10,
|
||||||
and (
|
4,
|
||||||
10,
|
3,
|
||||||
4,
|
) < self.connection.mysql_version < (10, 5, 2):
|
||||||
3,
|
|
||||||
)
|
|
||||||
< self.connection.mysql_version
|
|
||||||
< (10, 5, 2)
|
|
||||||
):
|
|
||||||
pass
|
pass
|
||||||
|
@ -4,14 +4,9 @@
|
|||||||
|
|
||||||
# output
|
# output
|
||||||
|
|
||||||
if (
|
if e123456.get_tk_patchlevel() >= (8, 6, 0, "final") or (
|
||||||
e123456.get_tk_patchlevel() >= (8, 6, 0, "final")
|
8,
|
||||||
or (
|
5,
|
||||||
8,
|
8,
|
||||||
5,
|
) <= get_tk_patchlevel() < (8, 6):
|
||||||
8,
|
|
||||||
)
|
|
||||||
<= get_tk_patchlevel()
|
|
||||||
< (8, 6)
|
|
||||||
):
|
|
||||||
pass
|
pass
|
||||||
|
@ -18,7 +18,4 @@
|
|||||||
"qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweas "
|
"qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweas "
|
||||||
+ "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwegqweasdzxcqweasdzxc.",
|
+ "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwegqweasdzxcqweasdzxc.",
|
||||||
"qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwe",
|
"qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwe",
|
||||||
) % {
|
) % {"reported_username": reported_username, "report_reason": report_reason}
|
||||||
"reported_username": reported_username,
|
|
||||||
"report_reason": report_reason,
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user