Discover whether a file is Python 3.6+ also by stars in calls

Fixes a pathological situation where if a function signature used a trailing
comma but was later reformatted to a single line (with the trailing comma
removed), Black would change its mind whether a file is Python
3.6-compatible between runs.
This commit is contained in:
Łukasz Langa 2018-05-08 15:44:44 -07:00
parent e196180a0d
commit 1747c388bb
5 changed files with 55 additions and 2 deletions

View File

@ -549,6 +549,11 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md).
* fixed not splitting long from-imports with only a single name * fixed not splitting long from-imports with only a single name
* fixed Python 3.6+ file discovery by also looking at function calls with
unpacking. This fixed non-deterministic formatting if trailing commas
where used both in function signatures with stars and function calls
with stars but the former would be reformatted to a single line.
### 18.4a4 ### 18.4a4

View File

@ -2337,7 +2337,7 @@ def is_python36(node: Node) -> bool:
Currently looking for: Currently looking for:
- f-strings; and - f-strings; and
- trailing commas after * or ** in function signatures. - trailing commas after * or ** in function signatures and calls.
""" """
for n in node.pre_order(): for n in node.pre_order():
if n.type == token.STRING: if n.type == token.STRING:
@ -2346,7 +2346,7 @@ def is_python36(node: Node) -> bool:
return True return True
elif ( elif (
n.type == syms.typedargslist n.type in {syms.typedargslist, syms.arglist}
and n.children and n.children
and n.children[-1].type == token.COMMA and n.children[-1].type == token.COMMA
): ):
@ -2354,6 +2354,11 @@ def is_python36(node: Node) -> bool:
if ch.type in STARS: if ch.type in STARS:
return True return True
if ch.type == syms.argument:
for argch in ch.children:
if argch.type in STARS:
return True
return False return False

View File

@ -81,6 +81,15 @@ def trailing_comma():
C: 0.1 * (10.0 / 12), C: 0.1 * (10.0 / 12),
D: 0.1 * (10.0 / 12), D: 0.1 * (10.0 / 12),
} }
def f(
a,
**kwargs,
) -> A:
return A(
very_long_argument_name1=very_long_value_for_the_argument,
very_long_argument_name2=very_long_value_for_the_argument,
**kwargs,
)
# output # output
@ -212,3 +221,11 @@ def trailing_comma():
C: 0.1 * (10.0 / 12), C: 0.1 * (10.0 / 12),
D: 0.1 * (10.0 / 12), D: 0.1 * (10.0 / 12),
} }
def f(a, **kwargs) -> A:
return A(
very_long_argument_name1=very_long_value_for_the_argument,
very_long_argument_name2=very_long_value_for_the_argument,
**kwargs,
)

18
tests/function2.py Normal file
View File

@ -0,0 +1,18 @@
def f(
a,
**kwargs,
) -> A:
return A(
very_long_argument_name1=very_long_value_for_the_argument,
very_long_argument_name2=very_long_value_for_the_argument,
**kwargs,
)
# output
def f(a, **kwargs) -> A:
return A(
very_long_argument_name1=very_long_value_for_the_argument,
very_long_argument_name2=very_long_value_for_the_argument,
**kwargs,
)

View File

@ -167,6 +167,14 @@ def test_function(self) -> None:
black.assert_equivalent(source, actual) black.assert_equivalent(source, actual)
black.assert_stable(source, actual, line_length=ll) black.assert_stable(source, actual, line_length=ll)
@patch("black.dump_to_file", dump_to_stderr)
def test_function2(self) -> None:
source, expected = read_data("function2")
actual = fs(source)
self.assertFormatEqual(expected, actual)
black.assert_equivalent(source, actual)
black.assert_stable(source, actual, line_length=ll)
@patch("black.dump_to_file", dump_to_stderr) @patch("black.dump_to_file", dump_to_stderr)
def test_expression(self) -> None: def test_expression(self) -> None:
source, expected = read_data("expression") source, expected = read_data("expression")