Skip to content

Commit d638d56

Browse files
committed
Fix unnecessary parentheses when a line contains multiline strings
Fixes psf#232
1 parent 23a00f0 commit d638d56

File tree

3 files changed

+85
-15
lines changed

3 files changed

+85
-15
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,8 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md).
721721

722722
* fixed long trivial assignments being wrapped in unnecessary parentheses (#273)
723723

724+
* fixed unnecessary parentheses when a line contained multiline strings (#232)
725+
724726
* fixed stdin handling not working correctly if an old version of Click was
725727
used (#276)
726728

black.py

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,13 @@ def contains_standalone_comments(self, depth_limit: int = sys.maxsize) -> bool:
10941094

10951095
return False
10961096

1097+
def contains_multiline_strings(self) -> bool:
1098+
for leaf in self.leaves:
1099+
if is_multiline_string(leaf):
1100+
return True
1101+
1102+
return False
1103+
10971104
def maybe_remove_trailing_comma(self, closing: Leaf) -> bool:
10981105
"""Remove trailing comma if there is one and it's safe."""
10991106
if not (
@@ -2225,24 +2232,35 @@ def right_hand_split(
22252232
# the closing bracket is an optional paren
22262233
and closing_bracket.type == token.RPAR
22272234
and not closing_bracket.value
2228-
# there are no standalone comments in the body
2229-
and not line.contains_standalone_comments(0)
2230-
# and it's not an import (optional parens are the only thing we can split
2231-
# on in this case; attempting a split without them is a waste of time)
2235+
# it's not an import (optional parens are the only thing we can split on
2236+
# in this case; attempting a split without them is a waste of time)
22322237
and not line.is_import
2238+
# there are no standalone comments in the body
2239+
and not body.contains_standalone_comments(0)
2240+
# and we can actually remove the parens
2241+
and can_omit_invisible_parens(body, line_length)
22332242
):
22342243
omit = {id(closing_bracket), *omit}
2235-
if can_omit_invisible_parens(body, line_length):
2236-
try:
2237-
yield from right_hand_split(line, line_length, py36=py36, omit=omit)
2238-
return
2239-
except CannotSplit:
2240-
if len(body.leaves) == 1 and not is_line_short_enough(
2241-
body, line_length=line_length
2242-
):
2243-
raise CannotSplit(
2244-
"Splitting failed, body is still too long and can't be split."
2245-
)
2244+
try:
2245+
yield from right_hand_split(line, line_length, py36=py36, omit=omit)
2246+
return
2247+
2248+
except CannotSplit:
2249+
if not (
2250+
can_be_split(body)
2251+
or is_line_short_enough(body, line_length=line_length)
2252+
):
2253+
raise CannotSplit(
2254+
"Splitting failed, body is still too long and can't be split."
2255+
)
2256+
2257+
elif head.contains_multiline_strings() or tail.contains_multiline_strings():
2258+
raise CannotSplit(
2259+
"The current optional pair of parentheses is bound to fail to "
2260+
"satisfy the splitting algorithm becase the head or the tail "
2261+
"contains multiline strings which by definition never fit one "
2262+
"line."
2263+
)
22462264

22472265
ensure_visible(opening_bracket)
22482266
ensure_visible(closing_bracket)
@@ -3190,6 +3208,42 @@ def is_line_short_enough(line: Line, *, line_length: int, line_str: str = "") ->
31903208
)
31913209

31923210

3211+
def can_be_split(line: Line) -> bool:
3212+
"""Return False if the line cannot be split *for sure*.
3213+
3214+
This is not an exhaustive search but a cheap heuristic that we can use to
3215+
avoid some unfortunate formattings (mostly around wrapping unsplittable code
3216+
in unnecessary parentheses).
3217+
"""
3218+
leaves = line.leaves
3219+
if len(leaves) < 2:
3220+
return False
3221+
3222+
if leaves[0].type == token.STRING and leaves[1].type == token.DOT:
3223+
call_count = 0
3224+
dot_count = 0
3225+
next = leaves[-1]
3226+
for leaf in leaves[-2::-1]:
3227+
if leaf.type in OPENING_BRACKETS:
3228+
if next.type not in CLOSING_BRACKETS:
3229+
return False
3230+
3231+
call_count += 1
3232+
elif leaf.type == token.DOT:
3233+
dot_count += 1
3234+
elif leaf.type == token.NAME:
3235+
if not (next.type == token.DOT or next.type in OPENING_BRACKETS):
3236+
return False
3237+
3238+
elif leaf.type not in CLOSING_BRACKETS:
3239+
return False
3240+
3241+
if dot_count > 1 and call_count > 1:
3242+
return False
3243+
3244+
return True
3245+
3246+
31933247
def can_omit_invisible_parens(line: Line, line_length: int) -> bool:
31943248
"""Does `line` have a shape safe to reformat without optional parens around it?
31953249

tests/cantfit.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@
2828
string_variable_name = (
2929
"a string that is waaaaaaaayyyyyyyy too long, even in parens, there's nothing you can do" # noqa
3030
)
31+
for key in """
32+
hostname
33+
port
34+
username
35+
""".split():
36+
if key in self.connect_kwargs:
37+
raise ValueError(err.format(key))
3138

3239

3340
# output
@@ -71,3 +78,10 @@
7178
this_is_a_ridiculously_long_name_and_nobody_in_their_right_mind_would_use_one_like_it=0,
7279
)
7380
string_variable_name = "a string that is waaaaaaaayyyyyyyy too long, even in parens, there's nothing you can do" # noqa
81+
for key in """
82+
hostname
83+
port
84+
username
85+
""".split():
86+
if key in self.connect_kwargs:
87+
raise ValueError(err.format(key))

0 commit comments

Comments
 (0)