Join implicit concatenated strings when they fit on a line (#13663)

This commit is contained in:
Micha Reiser 2024-10-24 11:52:22 +02:00 committed by GitHub
parent e402e27a09
commit 73ee72b665
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 3907 additions and 363 deletions

View file

@ -0,0 +1,5 @@
[
{
"preview": "enabled"
}
]

View file

@ -0,0 +1,321 @@
"aaaaaaaaa" "bbbbbbbbbbbbbbbbbbbb" # Join
(
"aaaaaaaaaaa" "bbbbbbbbbbbbbbbb"
) # join
(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
) # too long to join
"different '" 'quote "are fine"' # join
# More single quotes
"one single'" "two 'single'" ' two "double"'
# More double quotes
'one double"' 'two "double"' " two 'single'"
# Equal number of single and double quotes
'two "double"' " two 'single'"
f"{'Hy \"User\"'}" 'more'
b"aaaaaaaaa" b"bbbbbbbbbbbbbbbbbbbb" # Join
(
b"aaaaaaaaaaa" b"bbbbbbbbbbbbbbbb"
) # join
(
b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" b"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
) # too long to join
# Skip joining if there is a trailing comment
(
"fffffffffffff"
"bbbbbbbbbbbbb" # comment
"cccccccccccccc"
)
# Skip joining if there is a leading comment
(
"fffffffffffff"
# comment
"bbbbbbbbbbbbb"
"cccccccccccccc"
)
##############################################################################
# F-strings
##############################################################################
# Escape `{` and `}` when marging an f-string with a string
"a {not_a_variable}" f"b {10}" "c"
# Join, and break expressions
f"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{
expression
}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" f"cccccccccccccccccccc {20999}" "more"
# Join, but don't break the expressions
f"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{expression}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" f"cccccccccccccccccccc {20999}" "more"
f"test{
expression
}flat" f"can be {
joined
} together"
aaaaaaaaaaa = f"test{
expression
}flat" f"cean beeeeeeee {
joined
} eeeeeeeeeeeeeeeeeeeeeeeeeeeee" # inline
f"single quoted '{x}'" f'double quoted "{x}"' # Same number of quotes => use preferred quote style
f"single quote ' {x}" f'double quoted "{x}"' # More double quotes => use single quotes
f"single quoted '{x}'" f'double quote " {x}"' # More single quotes => use double quotes
# Different triple quoted strings
f"{'''test'''}" f'{"""other"""}'
# Now with inner quotes
f"{'''test ' '''}" f'{"""other " """}'
f"{some_where_nested('''test ' ''')}" f'{"""other " """ + "more"}'
f"{b'''test ' '''}" f'{b"""other " """}'
f"{f'''test ' '''}" f'{f"""other " """}'
# debug expressions containing quotes
f"{10 + len('bar')=}" f"{10 + len('bar')=}"
f"{10 + len('bar')=}" f'no debug{10}' f"{10 + len('bar')=}"
# We can't savely merge this pre Python 3.12 without altering the debug expression.
f"{10 + len('bar')=}" f'{10 + len("bar")=}'
##############################################################################
# Don't join raw strings
##############################################################################
r"a" "normal"
R"a" "normal"
f"test" fr"test"
f"test" fR"test"
##############################################################################
# Don't join triple quoted strings
##############################################################################
"single" """triple"""
"single" f""""single"""
b"single" b"""triple"""
##############################################################################
# Join strings in with statements
##############################################################################
# Fits
with "aa" "bbb" "cccccccccccccccccccccccccccccccccccccccccccccc":
pass
# Parenthesize single-line
with "aa" "bbb" "ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc":
pass
# Multiline
with "aa" "bbb" "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc":
pass
with f"aaaaaaa{expression}bbbb" f"ccc {20999}" "more":
pass
##############################################################################
# For loops
##############################################################################
# Flat
for a in "aaaaaaaaa" "bbbbbbbbb" "ccccccccc" "dddddddddd":
pass
# Parenthesize single-line
for a in "aaaaaaaaa" "bbbbbbbbb" "ccccccccc" "dddddddddd" "eeeeeeeeeeeeeee" "fffffffffffff" "ggggggggggggggg" "hh":
pass
# Multiline
for a in "aaaaaaaaa" "bbbbbbbbb" "ccccccccc" "dddddddddd" "eeeeeeeeeeeeeee" "fffffffffffff" "ggggggggggggggg" "hhhh":
pass
##############################################################################
# Assert statement
##############################################################################
# Fits
assert "aaaaaaaaa" "bbbbbbbbbbbb", "cccccccccccccccc" "dddddddddddddddd"
# Wrap right
assert "aaaaaaaaa" "bbbbbbbbbbbb", "cccccccccccccccc" "dddddddddddddddd" "eeeeeeeeeeeee" "fffffffffff"
# Right multiline
assert "aaaaaaaaa" "bbbbbbbbbbbb", "cccccccccccccccc" "dddddddddddddddd" "eeeeeeeeeeeee" "fffffffffffffff" "ggggggggggggg" "hhhhhhhhhhh"
# Wrap left
assert "aaaaaaaaa" "bbbbbbbbbbbb" "cccccccccccccccc" "dddddddddddddddd" "eeeeeeeeeeeee" "fffffffffffffff", "ggggggggggggg" "hhhhhhhhhhh"
# Left multiline
assert "aaaaaaaaa" "bbbbbbbbbbbb" "cccccccccccccccc" "dddddddddddddddd" "eeeeeeeeeeeee" "fffffffffffffff" "ggggggggggggg", "hhhhhhhhhhh"
# wrap both
assert "aaaaaaaaa" "bbbbbbbbbbbb" "cccccccccccccccc" "dddddddddddddddd" "eeeeeeeeeeeee" "fffffffffffffff", "ggggggggggggg" "hhhhhhhhhhh" "iiiiiiiiiiiiiiiiii" "jjjjjjjjjjjjj" "kkkkkkkkkkkkkkkkk" "llllllllllll"
# both multiline
assert "aaaaaaaaa" "bbbbbbbbbbbb" "cccccccccccccccc" "dddddddddddddddd" "eeeeeeeeeeeee" "fffffffffffffff" "ggggggggggggg", "hhhhhhhhhhh" "iiiiiiiiiiiiiiiiii" "jjjjjjjjjjjjj" "kkkkkkkkkkkkkkkkk" "llllllllllll" "mmmmmmmmmmmmmm"
##############################################################################
# In clause headers (can_omit_optional_parentheses)
##############################################################################
# Use can_omit_optional_parentheses layout to avoid an instability where the formatter
# picks the can_omit_optional_parentheses layout when the strings are joined.
if (
f"implicit"
"concatenated"
"string" + f"implicit"
"concaddddddddddded"
"ring"
* len([aaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccccc, ddddddddddddddddddddddddddd])
):
pass
# Keep parenthesizing multiline - implicit concatenated strings
if (
f"implicit"
"""concatenate
d"""
"string" + f"implicit"
"concaddddddddddded"
"ring"
* len([aaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccccc, ddddddddddddddddddddddddddd])
):
pass
if (
[
aaaaaa,
bbbbbbbbbbbbbbbb,
cccccccccccccccccc,
ddddddddddddddddddddddddddd,
]
+ "implicitconcat"
"enatedstriiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiing"
):
pass
# In match statements
match x:
case "implicitconcat" "enatedstring" | [
aaaaaa,
bbbbbbbbbbbbbbbb,
cccccccccccccccccc,
ddddddddddddddddddddddddddd,
]:
pass
case [
aaaaaa,
bbbbbbbbbbbbbbbb,
cccccccccccccccccc,
ddddddddddddddddddddddddddd,
] | "implicitconcat" "enatedstring" :
pass
case "implicitconcat" "enatedstriiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiing" | [
aaaaaa,
bbbbbbbbbbbbbbbb,
cccccccccccccccccc,
ddddddddddddddddddddddddddd,
]:
pass
##############################################################################
# In docstring positions
##############################################################################
def short_docstring():
"Implicit" "concatenated" "docstring"
def long_docstring():
"Loooooooooooooooooooooong" "doooooooooooooooooooocstriiiiiiiiiiiiiiiiiiiiiiiiiiiiiiing" "exceding the line width" "but it should be concatenated anyways because it is single line"
def docstring_with_leading_whitespace():
" This is a " "implicit" "concatenated" "docstring"
def docstring_with_trailing_whitespace():
"This is a " "implicit" "concatenated" "docstring "
def docstring_with_leading_empty_parts():
" " " " "" "This is a " "implicit" "concatenated" "docstring"
def docstring_with_trailing_empty_parts():
"This is a " "implicit" "concatenated" "docstring" "" " " " "
def all_empty():
" " " " " "
def byte_string_in_docstring_position():
b" don't trim the" b"bytes literal "
def f_string_in_docstring_position():
f" don't trim the" "f-string literal "
def single_quoted():
' content\ ' ' '
return
def implicit_with_comment():
(
"a"
# leading
"the comment above"
)
##############################################################################
# Regressions
##############################################################################
LEEEEEEEEEEEEEEEEEEEEEEFT = RRRRRRRRIIIIIIIIIIIIGGGGGHHHT | {
"entityNameeeeeeeeeeeeeeeeee", # comment must be long enough to
"some long implicit concatenated string" "that should join"
}
# Ensure that flipping between Multiline and BestFit layout results in stable formatting
# when using IfBreaksParenthesized layout.
assert False, "Implicit concatenated string" "uses {} layout on {} format".format(
"Multiline", "first"
)
assert False, await "Implicit concatenated string" "uses {} layout on {} format".format(
"Multiline", "first"
)
assert False, "Implicit concatenated stringuses {} layout on {} format"[
aaaaaaaaa, bbbbbb
]
assert False, +"Implicit concatenated string" "uses {} layout on {} format".format(
"Multiline", "first"
)

View file

@ -0,0 +1,293 @@
## Implicit concatenated strings with a trailing comment but a non splittable target.
# Don't join the string because the joined string with the inlined comment exceeds the line length limit.
____aaa = (
"aaaaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvv"
) # c
# This is the same string as above and should lead to the same formatting. The only difference is that we start
# with an unparenthesized string.
____aaa = "aaaaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvv" # c
# Again the same string as above but this time as non-implicit concatenated string.
# It's okay if the formatting differs because it's an explicit choice to use implicit concatenation.
____aaa = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvvvvvv" # c
# Join the string because it's exactly in the line length limit when the comment is inlined.
____aaa = (
"aaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv"
) # c
# This is the same string as above and should lead to the same formatting. The only difference is that we start
# with an unparenthesized string.
____aaa = "aaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv" # c
# Again the same string as above but as a non-implicit concatenated string. It should result in the same formatting
# (for consistency).
____aaa = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvvvvvvvvvvv" # c
# It should collapse the parentheses if the joined string and the comment fit on the same line.
# This is required for stability.
____aaa = (
"aaaaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbvv" # c
)
#############################################################
# Assignments where the target or annotations are splittable
#############################################################
# The target splits because of a magic trailing comma
# The string is joined and not parenthesized because it just fits into the line length (including comment).
a[
aaaaaaa,
b,
] = "ccccccccccccccccccccccccccccc" "cccccccccccccccccccccccccccccccccccccccccc" # comment
# Same but starting with a joined string. They should both result in the same formatting.
[
aaaaaaa,
b,
] = "ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" # comment
# The target splits because of the magic trailing comma
# The string is **not** joined because it with the inlined comment exceeds the line length limit.
a[
aaaaaaa,
b,
] = "ccccccccccccccccccccccccccccc" "ccccccccccccccccccccccccccccccccccccccccccc" # comment
# The target should be flat
# The string should be joined because it fits into the line length
a[
aaaaaaa,
b
] = (
"ccccccccccccccccccccccccccccccccccc" "cccccccccccccccccccccccc" # comment
)
# Same but starting with a joined string. They should both result in the same formatting.
a[
aaaaaaa,
b
] = "ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" # comment
# The target should be flat
# The string gets parenthesized because it, with the inlined comment, exceeds the line length limit.
a[
aaaaaaa,
b
] = "ccccccccccccccccccccccccccccc" "ccccccccccccccccccccccccccccccccccccccccccc" # comment
# Split an overlong target, but join the string if it fits
a[
aaaaaaa,
b
].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
"ccccccccccccccccccccccccccccccccccccccccc" "cccccccccccccccccccccccccccccc" # comment
)
# Split both if necessary and keep multiline
a[
aaaaaaa,
b
].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
"ccccccccccccccccccccccccccccccccccccccccc" "ccccccccccccccccccccccccccccccc" # comment
)
#########################################################
# Leading or trailing own line comments:
# Preserve the parentheses
########################################################
a[
aaaaaaa,
b
] = (
# test
"ccccccccccccccccccccccccccccc" "ccccccccccccccccccccccccccccccccccccccccccc"
)
a[
aaaaaaa,
b
] = (
"ccccccccccccccccccccccccccccc" "ccccccccccccccccccccccccccccccccccccccccccc"
# test
)
a[
aaaaaaa,
b
] = (
"ccccccccccccccccccccccccccccccccccccccccc" "ccccccccccccccccccccccccccccccccccccccccccc"
# test
)
#############################################################
# Type alias statements
#############################################################
# First break the right, join the string
type A[str, int, number] = "Literal[string, int] | None | " "CustomType" "| OtherCustomTypeExcee" # comment
# Keep multiline if overlong
type A[str, int, number] = "Literal[string, int] | None | " "CustomTypeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" # comment
# Break the left if it is over-long, join the string
type Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[stringgggggggggg, inttttttttttttttttttttttt, number] = "Literal[string, int] | None | " "CustomType" # comment
# Break both if necessary and keep multiline
type Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[stringgggggggggg, inttttttttttttttttttttttt, number] = "Literal[string, int] | None | " "CustomTypeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" # comment
#############################################################
# F-Strings
#############################################################
# Flatten and join the f-string
aaaaaaaaaaa = f"test{
expression}flat" f"cean beeeeeeee {joined} eeeeeeeeeeeeeeeee" # inline
# Parenthesize the value and join it, inline the comment
aaaaaaaaaaa = f"test{
expression}flat" f"cean beeeeeeee {joined} eeeeeeeeeeeeeeeeeeeeeeeeeee" # inline
# Parenthesize the f-string and keep it multiline because it doesn't fit on a single line including the comment
aaaaaaaaaaa = f"test{
expression
}flat" f"cean beeeeeeee {
joined
} eeeeeeeeeeeeeeeeeeeeeeeeeeeee" # inline
# The target splits because of a magic trailing comma
# The string is joined and not parenthesized because it just fits into the line length (including comment).
a[
aaaaaaa,
b,
] = f"ccccc{
expression}ccccccccccc" f"cccccccccccccccccccccccccccccccccccccccccc" # comment
# Same but starting with a joined string. They should both result in the same formatting.
[
aaaaaaa,
b,
] = f"ccccc{
expression}ccccccccccccccccccccccccccccccccccccccccccccccccccccc" # comment
# The target splits because of the magic trailing comma
# The string is **not** joined because it with the inlined comment exceeds the line length limit.
a[
aaaaaaa,
b,
] = f"ccccc{
expression}cccccccccccccccccccc" f"cccccccccccccccccccccccccccccccccccccccccc" # comment
# The target should be flat
# The string should be joined because it fits into the line length
a[
aaaaaaa,
b
] = (
f"ccccc{
expression}ccccccccccc" "cccccccccccccccccccccccc" # comment
)
# Same but starting with a joined string. They should both result in the same formatting.
a[
aaaaaaa,
b
] = f"ccccc{
expression}ccccccccccccccccccccccccccccccccccc" # comment
# The target should be flat
# The string gets parenthesized because it, with the inlined comment, exceeds the line length limit.
a[
aaaaaaa,
b
] = f"ccccc{
expression}ccccccccccc" "ccccccccccccccccccccccccccccccccccccccccccc" # comment
# Split an overlong target, but join the string if it fits
a[
aaaaaaa,
b
].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
f"ccccc{
expression}ccccccccccc" "cccccccccccccccccccccccccccccc" # comment
)
# Split both if necessary and keep multiline
a[
aaaaaaa,
b
].bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
f"ccccc{
expression}cccccccccccccccccccccccccccccccc" "ccccccccccccccccccccccccccccccc" # comment
)
# Don't inline f-strings that contain expressions that are guaranteed to split, e.b. because of a magic trailing comma
aaaaaaaaaaaaaaaaaa = f"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
[a,]
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
aaaaaaaaaaaaaaaaaa = (
f"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
[a,]
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
)
aaaaa[aaaaaaaaaaa] = f"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
[a,]
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
aaaaa[aaaaaaaaaaa] = (f"testeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{
[a,]
}" "moreeeeeeeeeeeeeeeeeeee" "test" # comment
)
# Don't inline f-strings that contain commented expressions
aaaaaaaaaaaaaaaaaa = (
f"testeeeeeeeeeeeeeeeeeeeeeeeee{[
a # comment
]}" "moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
f"testeeeeeeeeeeeeeeeeeeeeeeeee{[
a # comment
]}" "moreeeeeeeeeeeeeeeeeetest" # comment
)
# Don't inline f-strings with multiline debug expressions:
aaaaaaaaaaaaaaaaaa = (
f"testeeeeeeeeeeeeeeeeeeeeeeeee{
a=}" "moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaaaaaaaaaaaaaaa = (
f"testeeeeeeeeeeeeeeeeeeeeeeeee{a +
b=}" "moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaaaaaaaaaaaaaaa = (
f"testeeeeeeeeeeeeeeeeeeeeeeeee{a
=}" "moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
f"testeeeeeeeeeeeeeeeeeeeeeeeee{
a=}" "moreeeeeeeeeeeeeeeeeetest" # comment
)
aaaaa[aaaaaaaaaaa] = (
f"testeeeeeeeeeeeeeeeeeeeeeeeee{a
=}" "moreeeeeeeeeeeeeeeeeetest" # comment
)

View file

@ -0,0 +1,11 @@
[
{
"quote_style": "preserve",
"preview": "enabled"
},
{
"quote_style": "preserve",
"preview": "enabled",
"target_version": "py312"
}
]

View file

@ -0,0 +1,13 @@
a = "different '" 'quote "are fine"' # join
# More single quotes
"one single'" "two 'single'" ' two "double"'
# More double quotes
'one double"' 'two "double"' " two 'single'"
# Equal number of single and double quotes
'two "double"' " two 'single'"
# Already invalid Pre Python 312
f"{'Hy "User"'}" f'{"Hy 'User'"}'