Fix merging implicit multiline strings that have inline comments (#3956)

* Fix test behaviour

* Add new test cases

* Skip merging strings that have inline comments

* Don't merge lines with multiline strings with inline comments

* Changelog entry

* Document implicit multiline string merging rules

* Fix PR number
This commit is contained in:
Henri Holopainen 2023-10-20 06:09:33 +03:00 committed by GitHub
parent 9edba85f71
commit 882d8795c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 125 additions and 5 deletions

View File

@ -12,7 +12,7 @@
### Preview style ### Preview style
<!-- Changes that affect Black's preview style --> - Fix merging implicit multiline strings that have inline comments (#3956)
### Configuration ### Configuration

View File

@ -160,3 +160,67 @@ MULTILINE = """
foobar foobar
""".replace("\n", "") """.replace("\n", "")
``` ```
Implicit multiline strings are special, because they can have inline comments. Strings
without comments are merged, for example
```python
s = (
"An "
"implicit "
"multiline "
"string"
)
```
becomes
```python
s = "An implicit multiline string"
```
A comment on any line of the string (or between two string lines) will block the
merging, so
```python
s = (
"An " # Important comment concerning just this line
"implicit "
"multiline "
"string"
)
```
and
```python
s = (
"An "
"implicit "
# Comment in between
"multiline "
"string"
)
```
will not be merged. Having the comment after or before the string lines (but still
inside the parens) will merge the string. For example
```python
s = ( # Top comment
"An "
"implicit "
"multiline "
"string"
# Bottom comment
)
```
becomes
```python
s = ( # Top comment
"An implicit multiline string"
# Bottom comment
)
```

View File

@ -587,6 +587,7 @@ def transform_line(
or line.contains_unsplittable_type_ignore() or line.contains_unsplittable_type_ignore()
) )
and not (line.inside_brackets and line.contains_standalone_comments()) and not (line.inside_brackets and line.contains_standalone_comments())
and not line.contains_implicit_multiline_string_with_comments()
): ):
# Only apply basic string preprocessing, since lines shouldn't be split here. # Only apply basic string preprocessing, since lines shouldn't be split here.
if Preview.string_processing in mode: if Preview.string_processing in mode:

View File

@ -239,6 +239,21 @@ def contains_standalone_comments(self, depth_limit: int = sys.maxsize) -> bool:
return False return False
def contains_implicit_multiline_string_with_comments(self) -> bool:
"""Chck if we have an implicit multiline string with comments on the line"""
for leaf_type, leaf_group_iterator in itertools.groupby(
self.leaves, lambda leaf: leaf.type
):
if leaf_type != token.STRING:
continue
leaf_list = list(leaf_group_iterator)
if len(leaf_list) == 1:
continue
for leaf in leaf_list:
if self.comments_after(leaf):
return True
return False
def contains_uncollapsable_type_comments(self) -> bool: def contains_uncollapsable_type_comments(self) -> bool:
ignored_ids = set() ignored_ids = set()
try: try:

View File

@ -390,7 +390,19 @@ def do_match(self, line: Line) -> TMatchResult:
and is_valid_index(idx + 1) and is_valid_index(idx + 1)
and LL[idx + 1].type == token.STRING and LL[idx + 1].type == token.STRING
): ):
if not is_part_of_annotation(leaf): # Let's check if the string group contains an inline comment
# If we have a comment inline, we don't merge the strings
contains_comment = False
i = idx
while is_valid_index(i):
if LL[i].type != token.STRING:
break
if line.comments_after(LL[i]):
contains_comment = True
break
i += 1
if not is_part_of_annotation(leaf) and not contains_comment:
string_indices.append(idx) string_indices.append(idx)
# Advance to the next non-STRING leaf. # Advance to the next non-STRING leaf.

View File

@ -210,8 +210,8 @@ def foo():
some_tuple = ("some string", "some string" " which should be joined") some_tuple = ("some string", "some string" " which should be joined")
some_commented_string = ( some_commented_string = ( # This comment stays at the top.
"This string is long but not so long that it needs hahahah toooooo be so greatttt" # This comment gets thrown to the top. "This string is long but not so long that it needs hahahah toooooo be so greatttt"
" {} that I just can't think of any more good words to say about it at" " {} that I just can't think of any more good words to say about it at"
" allllllllllll".format("ha") # comments here are fine " allllllllllll".format("ha") # comments here are fine
) )
@ -834,7 +834,7 @@ def foo():
some_tuple = ("some string", "some string which should be joined") some_tuple = ("some string", "some string which should be joined")
some_commented_string = ( # This comment gets thrown to the top. some_commented_string = ( # This comment stays at the top.
"This string is long but not so long that it needs hahahah toooooo be so greatttt" "This string is long but not so long that it needs hahahah toooooo be so greatttt"
" {} that I just can't think of any more good words to say about it at" " {} that I just can't think of any more good words to say about it at"
" allllllllllll".format("ha") # comments here are fine " allllllllllll".format("ha") # comments here are fine

View File

@ -157,6 +157,24 @@ def dastardly_default_value(
`--global-option` is reserved to flags like `--verbose` or `--quiet`. `--global-option` is reserved to flags like `--verbose` or `--quiet`.
""" """
this_will_become_one_line = (
"a"
"b"
"c"
)
this_will_stay_on_three_lines = (
"a" # comment
"b"
"c"
)
this_will_also_become_one_line = ( # comment
"a"
"b"
"c"
)
# output # output
"""cow """cow
say""", say""",
@ -357,3 +375,13 @@ def dastardly_default_value(
Please use `--build-option` instead, Please use `--build-option` instead,
`--global-option` is reserved to flags like `--verbose` or `--quiet`. `--global-option` is reserved to flags like `--verbose` or `--quiet`.
""" """
this_will_become_one_line = "abc"
this_will_stay_on_three_lines = (
"a" # comment
"b"
"c"
)
this_will_also_become_one_line = "abc" # comment