From 793450aeb00c8547ef5355c38dbf573ce4252bae Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Tue, 8 May 2018 17:28:40 -0700 Subject: [PATCH] Automatic management of parentheses in assignments Fixes #140 Note: this is an evolution but the end result needs to be different. See cantfit.py for some good examples on bad formatting caused by this change. --- README.md | 8 ++++++++ black.py | 25 ++++++++++++++++++++++--- tests/cantfit.py | 40 ++++++++++++++++++++++++++++++++++++++++ tests/expression.diff | 26 ++++++++++++++------------ tests/expression.py | 22 +++++++++++----------- 5 files changed, 95 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index cb92f57..aca4999 100644 --- a/README.md +++ b/README.md @@ -327,6 +327,11 @@ interesting cases: - `for (...) in (...):` - `assert (...), (...)` - `from X import (...)` +- assignments like: + - `target = (...)` + - `target: type = (...)` + - `some, *un, packing = (...)` + - `augmented += (...)` In those cases, parentheses are removed when the entire statement fits in one line, or if the inner expression doesn't have any delimiters to @@ -540,6 +545,9 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md). * slices are now formatted according to PEP 8 (#178) +* parentheses are now also managed automatically on the right-hand side + of assignments and return statements (#140) + * math operators now use their respective priorities for delimiting multiline expressions (#148) diff --git a/black.py b/black.py index eb99a41..00d5758 100644 --- a/black.py +++ b/black.py @@ -599,6 +599,22 @@ TEST_DESCENDANTS = { syms.term, syms.power, } +ASSIGNMENTS = { + "=", + "+=", + "-=", + "*=", + "@=", + "/=", + "%=", + "&=", + "|=", + "^=", + "<<=", + ">>=", + "**=", + "//=", +} COMPREHENSION_PRIORITY = 20 COMMA_PRIORITY = 18 TERNARY_PRIORITY = 16 @@ -994,8 +1010,9 @@ class Line: and subscript_start.type == syms.subscriptlist ): subscript_start = child_towards(subscript_start, leaf) - return subscript_start is not None and any( - n.type in TEST_DESCENDANTS for n in subscript_start.pre_order() + return ( + subscript_start is not None + and any(n.type in TEST_DESCENDANTS for n in subscript_start.pre_order()) ) def __str__(self) -> str: @@ -1252,7 +1269,7 @@ class LineGenerator(Visitor[Line]): """Visit a statement. This implementation is shared for `if`, `while`, `for`, `try`, `except`, - `def`, `with`, `class`, and `assert`. + `def`, `with`, `class`, `assert` and assignments. The relevant Python language `keywords` for a given statement will be NAME leaves within it. This methods puts those on a separate line. @@ -1368,6 +1385,8 @@ class LineGenerator(Visitor[Line]): self.visit_with_stmt = partial(v, keywords={"with"}, parens=Ø) self.visit_funcdef = partial(v, keywords={"def"}, parens=Ø) self.visit_classdef = partial(v, keywords={"class"}, parens=Ø) + self.visit_expr_stmt = partial(v, keywords=Ø, parens=ASSIGNMENTS) + self.visit_return_stmt = partial(v, keywords={"return"}, parens={"return"}) self.visit_async_funcdef = self.visit_async_stmt self.visit_decorated = self.visit_decorators diff --git a/tests/cantfit.py b/tests/cantfit.py index 99bcaa0..54f692c 100644 --- a/tests/cantfit.py +++ b/tests/cantfit.py @@ -25,3 +25,43 @@ normal_name = normal_function_name( "eggs with spam and eggs and spam with eggs with spam and eggs and spam with eggs with spam and eggs and spam with eggs", this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it=0, ) + + +# output + + +# long variable name +this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = ( + 0 +) +this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = ( + 1 +) # with a comment +this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = [ + 1, 2, 3 +] +this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = ( + function() +) +this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = function( + arg1, arg2, arg3 +) +this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it = function( + [1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3 +) +# long function name +normal_name = ( + but_the_function_name_is_now_ridiculously_long_and_it_is_still_super_annoying() +) +normal_name = but_the_function_name_is_now_ridiculously_long_and_it_is_still_super_annoying( + arg1, arg2, arg3 +) +normal_name = but_the_function_name_is_now_ridiculously_long_and_it_is_still_super_annoying( + [1, 2, 3], arg1, [1, 2, 3], arg2, [1, 2, 3], arg3 +) +# long arguments +normal_name = normal_function_name( + "but with super long string arguments that on their own exceed the line limit so there's no way it can ever fit", + "eggs with spam and eggs and spam with eggs with spam and eggs and spam with eggs with spam and eggs and spam with eggs", + this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it=0, +) diff --git a/tests/expression.diff b/tests/expression.diff index 7004a3e..2af955c 100644 --- a/tests/expression.diff +++ b/tests/expression.diff @@ -27,11 +27,11 @@ -foo = (lambda port_id, ignore_missing: {"port1": port1_resource, "port2": port2_resource}[port_id]) +lambda a, b, c=True, *, d=(1 << v2), e="str": a +lambda a, b, c=True, *vararg, d=(v1 << 2), e="str", **kwargs: a + b -+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 ++] 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) @@ -160,18 +160,19 @@ + **kwargs, +} a = (1,) - b = 1, +-b = 1, ++b = (1,) c = 1 d = (1,) + a + (2,) e = (1,).count(1) -what_is_up_with_those_new_coord_names = (coord_names + set(vars_to_create)) + set(vars_to_remove) -what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set(vars_to_remove) -result = session.query(models.Customer.id).filter(models.Customer.account_id == account_id, models.Customer.email == email_address).order_by(models.Customer.id.asc(),).all() -+what_is_up_with_those_new_coord_names = (coord_names + set(vars_to_create)) + set( -+ vars_to_remove ++what_is_up_with_those_new_coord_names = ( ++ (coord_names + set(vars_to_create)) + set(vars_to_remove) +) -+what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set( -+ vars_to_remove ++what_is_up_with_those_new_coord_names = ( ++ (coord_names | set(vars_to_create)) - set(vars_to_remove) +) +result = session.query(models.Customer.id).filter( + models.Customer.account_id == account_id, models.Customer.email == email_address @@ -190,9 +191,10 @@ + def gen(): yield from outside_of_generator - a = (yield) - +- a = (yield) ++ a = yield + + async def f(): await some.complicated[0].call(with_args=(True or (1 is not 1))) -print(* [] or [1]) diff --git a/tests/expression.py b/tests/expression.py index d3451c5..82d9dfa 100644 --- a/tests/expression.py +++ b/tests/expression.py @@ -266,11 +266,11 @@ lambda a=True: a lambda a, b, c=True: a lambda a, b, c=True, *, d=(1 << v2), e="str": a lambda a, b, c=True, *vararg, d=(v1 << 2), e="str", **kwargs: a + b -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 +] 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) @@ -397,15 +397,15 @@ SomeName **kwargs, } a = (1,) -b = 1, +b = (1,) c = 1 d = (1,) + a + (2,) e = (1,).count(1) -what_is_up_with_those_new_coord_names = (coord_names + set(vars_to_create)) + set( - vars_to_remove +what_is_up_with_those_new_coord_names = ( + (coord_names + set(vars_to_create)) + set(vars_to_remove) ) -what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set( - vars_to_remove +what_is_up_with_those_new_coord_names = ( + (coord_names | set(vars_to_create)) - set(vars_to_remove) ) result = session.query(models.Customer.id).filter( models.Customer.account_id == account_id, models.Customer.email == email_address @@ -424,7 +424,7 @@ mapping = { def gen(): yield from outside_of_generator - a = (yield) + a = yield async def f(): -- 2.39.5