Skip to content

Commit 882d879

Browse files
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
1 parent 9edba85 commit 882d879

File tree

7 files changed

+125
-5
lines changed

7 files changed

+125
-5
lines changed

CHANGES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
### Preview style
1414

15-
<!-- Changes that affect Black's preview style -->
15+
- Fix merging implicit multiline strings that have inline comments (#3956)
1616

1717
### Configuration
1818

docs/the_black_code_style/future_style.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,67 @@ MULTILINE = """
160160
foobar
161161
""".replace("\n", "")
162162
```
163+
164+
Implicit multiline strings are special, because they can have inline comments. Strings
165+
without comments are merged, for example
166+
167+
```python
168+
s = (
169+
"An "
170+
"implicit "
171+
"multiline "
172+
"string"
173+
)
174+
```
175+
176+
becomes
177+
178+
```python
179+
s = "An implicit multiline string"
180+
```
181+
182+
A comment on any line of the string (or between two string lines) will block the
183+
merging, so
184+
185+
```python
186+
s = (
187+
"An " # Important comment concerning just this line
188+
"implicit "
189+
"multiline "
190+
"string"
191+
)
192+
```
193+
194+
and
195+
196+
```python
197+
s = (
198+
"An "
199+
"implicit "
200+
# Comment in between
201+
"multiline "
202+
"string"
203+
)
204+
```
205+
206+
will not be merged. Having the comment after or before the string lines (but still
207+
inside the parens) will merge the string. For example
208+
209+
```python
210+
s = ( # Top comment
211+
"An "
212+
"implicit "
213+
"multiline "
214+
"string"
215+
# Bottom comment
216+
)
217+
```
218+
219+
becomes
220+
221+
```python
222+
s = ( # Top comment
223+
"An implicit multiline string"
224+
# Bottom comment
225+
)
226+
```

src/black/linegen.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,7 @@ def transform_line(
587587
or line.contains_unsplittable_type_ignore()
588588
)
589589
and not (line.inside_brackets and line.contains_standalone_comments())
590+
and not line.contains_implicit_multiline_string_with_comments()
590591
):
591592
# Only apply basic string preprocessing, since lines shouldn't be split here.
592593
if Preview.string_processing in mode:

src/black/lines.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,21 @@ def contains_standalone_comments(self, depth_limit: int = sys.maxsize) -> bool:
239239

240240
return False
241241

242+
def contains_implicit_multiline_string_with_comments(self) -> bool:
243+
"""Chck if we have an implicit multiline string with comments on the line"""
244+
for leaf_type, leaf_group_iterator in itertools.groupby(
245+
self.leaves, lambda leaf: leaf.type
246+
):
247+
if leaf_type != token.STRING:
248+
continue
249+
leaf_list = list(leaf_group_iterator)
250+
if len(leaf_list) == 1:
251+
continue
252+
for leaf in leaf_list:
253+
if self.comments_after(leaf):
254+
return True
255+
return False
256+
242257
def contains_uncollapsable_type_comments(self) -> bool:
243258
ignored_ids = set()
244259
try:

src/black/trans.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,19 @@ def do_match(self, line: Line) -> TMatchResult:
390390
and is_valid_index(idx + 1)
391391
and LL[idx + 1].type == token.STRING
392392
):
393-
if not is_part_of_annotation(leaf):
393+
# Let's check if the string group contains an inline comment
394+
# If we have a comment inline, we don't merge the strings
395+
contains_comment = False
396+
i = idx
397+
while is_valid_index(i):
398+
if LL[i].type != token.STRING:
399+
break
400+
if line.comments_after(LL[i]):
401+
contains_comment = True
402+
break
403+
i += 1
404+
405+
if not is_part_of_annotation(leaf) and not contains_comment:
394406
string_indices.append(idx)
395407

396408
# Advance to the next non-STRING leaf.

tests/data/cases/preview_long_strings__regression.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,8 @@ def foo():
210210

211211
some_tuple = ("some string", "some string" " which should be joined")
212212

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

835835
some_tuple = ("some string", "some string which should be joined")
836836

837-
some_commented_string = ( # This comment gets thrown to the top.
837+
some_commented_string = ( # This comment stays at the top.
838838
"This string is long but not so long that it needs hahahah toooooo be so greatttt"
839839
" {} that I just can't think of any more good words to say about it at"
840840
" allllllllllll".format("ha") # comments here are fine

tests/data/cases/preview_multiline_strings.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,24 @@ def dastardly_default_value(
157157
`--global-option` is reserved to flags like `--verbose` or `--quiet`.
158158
"""
159159

160+
this_will_become_one_line = (
161+
"a"
162+
"b"
163+
"c"
164+
)
165+
166+
this_will_stay_on_three_lines = (
167+
"a" # comment
168+
"b"
169+
"c"
170+
)
171+
172+
this_will_also_become_one_line = ( # comment
173+
"a"
174+
"b"
175+
"c"
176+
)
177+
160178
# output
161179
"""cow
162180
say""",
@@ -357,3 +375,13 @@ def dastardly_default_value(
357375
Please use `--build-option` instead,
358376
`--global-option` is reserved to flags like `--verbose` or `--quiet`.
359377
"""
378+
379+
this_will_become_one_line = "abc"
380+
381+
this_will_stay_on_three_lines = (
382+
"a" # comment
383+
"b"
384+
"c"
385+
)
386+
387+
this_will_also_become_one_line = "abc" # comment

0 commit comments

Comments
 (0)