Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrap concatenated strings used as function args in parens. #3307

Merged
merged 7 commits into from Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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