Skip to content

Commit 6d27c00

Browse files
committed
fix: handle multiline string tokens in parameter formatting
1 parent d9ace1f commit 6d27c00

File tree

3 files changed

+59
-4
lines changed

3 files changed

+59
-4
lines changed

src/docformatter/format.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,32 @@ def _do_skip_newlines(
138138
return j
139139

140140

141+
def _is_multiline_parameter(tokens: list[tokenize.TokenInfo], index: int) -> bool:
142+
"""Determine if a token is a multiline string parameter.
143+
144+
A multiline string token spans multiple physical lines in the source code.
145+
This is determined by checking if the token's start and end line numbers
146+
are different.
147+
148+
Parameters
149+
----------
150+
tokens : list[tokenize.TokenInfo]
151+
The list of tokens.
152+
index : int
153+
The index of the token to check.
154+
155+
Returns
156+
-------
157+
bool
158+
True if the token is a multiline string parameter, False otherwise.
159+
"""
160+
if index >= len(tokens):
161+
return False
162+
163+
token = tokens[index]
164+
return token.type == tokenize.STRING and token.start[0] != token.end[0]
165+
166+
141167
def _do_update_token_indices(
142168
tokens: list[tokenize.TokenInfo],
143169
) -> list[tokenize.TokenInfo]:
@@ -166,9 +192,19 @@ def _do_update_token_indices(
166192
# If the current token line is the same as the preceding token line,
167193
# the starting row for the current token should be the same as the ending
168194
# line for the previous token unless both lines are NEWLINES.
169-
if tokens[i].line == tokens[i - 1].line and tokens[i - 1].type not in (
170-
tokenize.NEWLINE,
171-
tokenize.NL,
195+
# Also check if tokens are at the same position (handles multiline strings).
196+
is_multiline = _is_multiline_parameter(tokens, i - 1)
197+
is_same_line = tokens[i].line == tokens[i - 1].line
198+
is_same_position = tokens[i].start[0] == tokens[i - 1].end[0]
199+
200+
if (
201+
is_multiline
202+
or (is_same_line or is_same_position)
203+
and tokens[i - 1].type
204+
not in (
205+
tokenize.NEWLINE,
206+
tokenize.NL,
207+
)
172208
):
173209
_start_idx, _end_idx = _get_start_end_indices(
174210
tokens[i],
@@ -461,7 +497,12 @@ def _get_start_end_indices(
461497
_end_row = _start_row
462498
_end_col = token.end[1]
463499

464-
if num_rows > 1 and _end_row != prev_token.end[0]:
500+
# For multiline STRING tokens, update the end row but keep the original end column.
501+
# The num_cols calculation from the line is incorrect for multiline strings because
502+
# the line attribute contains more than just the string content.
503+
if num_rows > 1 and token.type == tokenize.STRING:
504+
_end_row = _start_row + num_rows - 1
505+
elif num_rows > 1 and _end_row != prev_token.end[0]:
465506
_end_row = _start_row + num_rows - 1
466507
_end_col = num_cols
467508

tests/_data/string_files/do_format_code.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,3 +1233,16 @@ expected="""foo = f'''
12331233
bar
12341234
'''
12351235
"""
1236+
1237+
1238+
[do_not_break_multiline_parameter]
1239+
source='''foo = textwrap.dedent("""\
1240+
bar
1241+
baz
1242+
""")
1243+
'''
1244+
expected='''foo = textwrap.dedent("""\
1245+
bar
1246+
baz
1247+
""")
1248+
'''

tests/formatter/test_do_format_code.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@
140140
("ellipses_is_code_line", NO_ARGS),
141141
("do_not_break_f_string_double_quotes", NO_ARGS),
142142
("do_not_break_f_string_single_quotes", NO_ARGS),
143+
("do_not_break_multiline_parameter", NO_ARGS),
143144
],
144145
)
145146
def test_do_format_code(test_key, test_args, args):

0 commit comments

Comments
 (0)