Skip to content

Commit

Permalink
Wrap concatenated strings used as function args in parens (#3307)
Browse files Browse the repository at this point in the history
Fixes #3292
  • Loading branch information
yilei committed Oct 27, 2022
1 parent 09d4acd commit b73b77a
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 110 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -15,6 +15,8 @@
<!-- Changes that affect Black's preview style -->

- Enforce empty lines before classes and functions with sticky leading comments (#3302)
- Implicitly concatenated strings used as function args are now wrapped inside
parentheses (#3307)

### Configuration

Expand Down
6 changes: 4 additions & 2 deletions src/black/__init__.py
Expand Up @@ -497,8 +497,10 @@ def main( # noqa: C901
user_level_config = str(find_user_pyproject_toml())
if config == user_level_config:
out(
"Using configuration from user-level config at "
f"'{user_level_config}'.",
(
"Using configuration from user-level config at "
f"'{user_level_config}'."
),
fg="blue",
)
elif config_source in (
Expand Down
6 changes: 4 additions & 2 deletions src/black/mode.py
Expand Up @@ -180,8 +180,10 @@ class Mode:
def __post_init__(self) -> None:
if self.experimental_string_processing:
warn(
"`experimental string processing` has been included in `preview`"
" and deprecated. Use `preview` instead.",
(
"`experimental string processing` has been included in `preview`"
" and deprecated. Use `preview` instead."
),
Deprecated,
)

Expand Down
8 changes: 5 additions & 3 deletions src/black/parsing.py
Expand Up @@ -29,9 +29,11 @@
except ImportError:
if sys.version_info < (3, 8) and not _IS_PYPY:
print(
"The typed_ast package is required but not installed.\n"
"You can upgrade to Python 3.8+ or install typed_ast with\n"
"`python3 -m pip install typed-ast`.",
(
"The typed_ast package is required but not installed.\n"
"You can upgrade to Python 3.8+ or install typed_ast with\n"
"`python3 -m pip install typed-ast`."
),
file=sys.stderr,
)
sys.exit(1)
Expand Down
43 changes: 14 additions & 29 deletions src/black/trans.py
Expand Up @@ -1058,33 +1058,19 @@ def _prefer_paren_wrap_match(LL: List[Leaf]) -> Optional[int]:
if LL[0].type != token.STRING:
return None

matching_nodes = [
syms.listmaker,
syms.dictsetmaker,
syms.testlist_gexp,
]
# If the string is an immediate child of a list/set/tuple literal...
if (
parent_type(LL[0]) in matching_nodes
or parent_type(LL[0].parent) in matching_nodes
# If the string is surrounded by commas (or is the first/last child)...
prev_sibling = LL[0].prev_sibling
next_sibling = LL[0].next_sibling
if not prev_sibling and not next_sibling and parent_type(LL[0]) == syms.atom:
# If it's an atom string, we need to check the parent atom's siblings.
parent = LL[0].parent
assert parent is not None # For type checkers.
prev_sibling = parent.prev_sibling
next_sibling = parent.next_sibling
if (not prev_sibling or prev_sibling.type == token.COMMA) and (
not next_sibling or next_sibling.type == token.COMMA
):
# And the string is surrounded by commas (or is the first/last child)...
prev_sibling = LL[0].prev_sibling
next_sibling = LL[0].next_sibling
if (
not prev_sibling
and not next_sibling
and parent_type(LL[0]) == syms.atom
):
# If it's an atom string, we need to check the parent atom's siblings.
parent = LL[0].parent
assert parent is not None # For type checkers.
prev_sibling = parent.prev_sibling
next_sibling = parent.next_sibling
if (not prev_sibling or prev_sibling.type == token.COMMA) and (
not next_sibling or next_sibling.type == token.COMMA
):
return 0
return 0

return None

Expand Down Expand Up @@ -1653,9 +1639,8 @@ class StringParenWrapper(BaseStringSplitter, CustomSplitMapMixin):
assigned the value of some string.
OR
* The line starts with an "atom" string that prefers to be wrapped in
parens. It's preferred to be wrapped when it's is an immediate child of
a list/set/tuple literal, AND the string is surrounded by commas (or is
the first/last child).
parens. It's preferred to be wrapped when the string is surrounded by
commas (or is the first/last child).
Transformations:
The chosen string is wrapped in parentheses and then split at the LPAR.
Expand Down
12 changes: 8 additions & 4 deletions tests/data/preview/cantfit.py
Expand Up @@ -79,10 +79,14 @@
)
# 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",
(
"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,
)
string_variable_name = "a string that is waaaaaaaayyyyyyyy too long, even in parens, there's nothing you can do" # noqa
Expand Down
54 changes: 34 additions & 20 deletions tests/data/preview/long_strings.py
Expand Up @@ -297,8 +297,10 @@ def foo():
y = "Short string"

print(
"This is a really long string inside of a print statement with extra arguments"
" attached at the end of it.",
(
"This is a really long string inside of a print statement with extra arguments"
" attached at the end of it."
),
x,
y,
z,
Expand Down Expand Up @@ -474,13 +476,15 @@ def foo():
)

bad_split_func1(
"But what should happen when code has already "
"been formatted but in the wrong way? Like "
"with a space at the end instead of the "
"beginning. Or what about when it is split too "
"soon? In the case of a split that is too "
"short, black will try to honer the custom "
"split.",
(
"But what should happen when code has already "
"been formatted but in the wrong way? Like "
"with a space at the end instead of the "
"beginning. Or what about when it is split too "
"soon? In the case of a split that is too "
"short, black will try to honer the custom "
"split."
),
xxx,
yyy,
zzz,
Expand Down Expand Up @@ -583,9 +587,11 @@ def foo():
)

arg_comment_string = print(
"Long lines with inline comments which are apart of (and not the only member of) an"
" argument list should have their comments appended to the reformatted string's"
" enclosing left parentheses.", # This comment gets thrown to the top.
( # This comment gets thrown to the top.
"Long lines with inline comments which are apart of (and not the only member"
" of) an argument list should have their comments appended to the reformatted"
" string's enclosing left parentheses."
),
"Arg #2",
"Arg #3",
"Arg #4",
Expand Down Expand Up @@ -645,23 +651,31 @@ def foo():
)

func_with_bad_comma(
"This is a really long string argument to a function that has a trailing comma"
" which should NOT be there.",
(
"This is a really long string argument to a function that has a trailing comma"
" which should NOT be there."
),
)

func_with_bad_comma(
"This is a really long string argument to a function that has a trailing comma"
" which should NOT be there.", # comment after comma
( # comment after comma
"This is a really long string argument to a function that has a trailing comma"
" which should NOT be there."
),
)

func_with_bad_comma(
"This is a really long string argument to a function that has a trailing comma"
" which should NOT be there.",
(
"This is a really long string argument to a function that has a trailing comma"
" which should NOT be there."
),
)

func_with_bad_comma(
"This is a really long string argument to a function that has a trailing comma"
" which should NOT be there.", # comment after comma
( # comment after comma
"This is a really long string argument to a function that has a trailing comma"
" which should NOT be there."
),
)

func_with_bad_parens_that_wont_fit_in_one_line(
Expand Down
22 changes: 14 additions & 8 deletions tests/data/preview/long_strings__regression.py
Expand Up @@ -679,9 +679,11 @@ class A:
def foo():
some_func_call(
"xxxxxxxxxx",
"xx {xxxxxxxxxxx}/xxxxxxxxxxx.xxx xxxx.xxx && xxxxxx -x "
'"xxxx xxxxxxx xxxxxx xxxx; xxxx xxxxxx_xxxxx xxxxxx xxxx; '
"xxxx.xxxx_xxxxxx(['xxxx.xxx'], xxxx.xxxxxxx().xxxxxxxxxx)\" ",
(
"xx {xxxxxxxxxxx}/xxxxxxxxxxx.xxx xxxx.xxx && xxxxxx -x "
'"xxxx xxxxxxx xxxxxx xxxx; xxxx xxxxxx_xxxxx xxxxxx xxxx; '
"xxxx.xxxx_xxxxxx(['xxxx.xxx'], xxxx.xxxxxxx().xxxxxxxxxx)\" "
),
None,
("xxxxxxxxxxx",),
),
Expand All @@ -690,9 +692,11 @@ def foo():
class A:
def foo():
some_func_call(
"xx {xxxxxxxxxxx}/xxxxxxxxxxx.xxx xxxx.xxx && xxxxxx -x "
"xxxx, ('xxxxxxx xxxxxx xxxx, xxxx') xxxxxx_xxxxx xxxxxx xxxx; "
"xxxx.xxxx_xxxxxx(['xxxx.xxx'], xxxx.xxxxxxx().xxxxxxxxxx)\" ",
(
"xx {xxxxxxxxxxx}/xxxxxxxxxxx.xxx xxxx.xxx && xxxxxx -x "
"xxxx, ('xxxxxxx xxxxxx xxxx, xxxx') xxxxxx_xxxxx xxxxxx xxxx; "
"xxxx.xxxx_xxxxxx(['xxxx.xxx'], xxxx.xxxxxxx().xxxxxxxxxx)\" "
),
None,
("xxxxxxxxxxx",),
),
Expand Down Expand Up @@ -810,8 +814,10 @@ def foo():
)

lpar_and_rpar_have_comments = func_call( # LPAR Comment
"Long really ridiculous type of string that shouldn't really even exist at all. I"
" mean commmme onnn!!!", # Comma Comment
( # Comma Comment
"Long really ridiculous type of string that shouldn't really even exist at all."
" I mean commmme onnn!!!"
),
) # RPAR Comment

cmd_fstring = (
Expand Down

0 comments on commit b73b77a

Please sign in to comment.