From b519e8f282bf6c3700c48c86af1f2dd4fc6e1464 Mon Sep 17 00:00:00 2001 From: dataflow-solutions-sk Date: Sun, 16 Aug 2026 17:00:04 +0200 Subject: [PATCH 1/2] test: add regression test for issue #367 (f-string in parenthesized expr) --- tests/_data/string_files/do_format_code.toml | 14 ++++++++++++++ tests/formatter/test_do_format_code.py | 1 + 2 files changed, 15 insertions(+) diff --git a/tests/_data/string_files/do_format_code.toml b/tests/_data/string_files/do_format_code.toml index d652a15..a07fdcd 100644 --- a/tests/_data/string_files/do_format_code.toml +++ b/tests/_data/string_files/do_format_code.toml @@ -1260,3 +1260,17 @@ expected="def foo():\n \"\"\"Summary.\"\"\"\n x = 1\n # next line has 4 [issue_360_no_trailing_newline] source="def foo():\n \"\"\"\n Hello foo.\n \"\"\"\n x = 1" expected="def foo():\n \"\"\"Hello foo.\"\"\"\n x = 1" + +[issue_367] +source='''def build(x): + return ( + f"""a +{x}""", + ) +''' +expected='''def build(x): + return ( + f"""a +{x}""", + ) +''' diff --git a/tests/formatter/test_do_format_code.py b/tests/formatter/test_do_format_code.py index 037ace1..53b1dfd 100644 --- a/tests/formatter/test_do_format_code.py +++ b/tests/formatter/test_do_format_code.py @@ -143,6 +143,7 @@ ("issue_331_black_module_docstring", ["--black", ""]), ("issue_355", NO_ARGS), ("issue_360_no_trailing_newline", NO_ARGS), + ("issue_367", NO_ARGS), ], ) def test_do_format_code(test_key, test_args, args): From 2c498f21e048a99710ce5f2b2935d45c41f1ba83 Mon Sep 17 00:00:00 2001 From: dataflow-solutions-sk Date: Sun, 16 Aug 2026 17:11:28 +0200 Subject: [PATCH 2/2] fix: do not treat any f-string prefix as needing row continuation on Python <3.12 (#367) is_f_string()'s pre-3.12 fallback (from #330) matched any token whose string started with f"""/f''' as an f-string, regardless of bracket or assignment context. That flag was consumed generically by _get_unmatched_start_end_indices() to decide whether a token's start row should be pinned to the previous token's end row -- a fix-up that only makes sense for PEP 701's split FSTRING_START/MIDDLE/END token sequence (Python 3.12+). Before 3.12, an f-string is always a single STRING token, so there is nothing to stitch together and the check should never fire. Because it fired anyway, a multi-line f-string nested inside a parenthesized expression (e.g. `return (f"""...""",)`) had its row bookkeeping corrupted, causing tokenize.untokenize() to raise "ValueError: start precedes previous end" on Python 3.10/3.11 -- the crash reported in #367. (This is unrelated to the #364 bracket guard in is_attribute_docstring/_is_inside_brackets, which is never reached here since there is no preceding "=".) Verified the crash and the fix directly against Python 3.11.9 (embeddable distribution) since this environment only ships Python 3.12/3.14: - Pre-fix: tests/formatter/test_do_format_code.py::issue_367 raised ValueError: start (2,8) precedes previous end (3,4) on 3.11.9. - Post-fix: the same test passes on 3.11.9, and the full suite shows no new failures (the lone "preserve_line_ending" failure and the VIRTUAL_ENV-dependent end-to-end errors are pre-existing environment limitations, reproduced identically before this change). The regression test added in a prior commit (using the exact issue #367 reproducer) now actually exercises the buggy pre-3.12 code path. --- src/docformatter/classify.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/docformatter/classify.py b/src/docformatter/classify.py index 9b8b553..265076c 100644 --- a/src/docformatter/classify.py +++ b/src/docformatter/classify.py @@ -343,19 +343,23 @@ def is_f_string(token: tokenize.TokenInfo, prev_token: tokenize.TokenInfo) -> bo bool True if the token is an f-string, False otherwise. """ + # On Python 3.12+, PEP 701 tokenizes an f-string as a FSTRING_START / + # FSTRING_MIDDLE / FSTRING_END sequence, so adjacent tokens must be + # stitched back together onto the same row. if PY312: if tokenize.FSTRING_MIDDLE in [token.type, prev_token.type]: return True - elif any( - [ - token.string.startswith('f"""'), - prev_token.string.startswith('f"""'), - token.string.startswith("f'''"), - prev_token.string.startswith("f'''"), - ] - ): - return True + return False + + # Before Python 3.12, an f-string is always tokenized as a single STRING + # token, so there is nothing to stitch together and this function should + # never fire. Naively checking the string prefix here (regardless of + # bracket/assignment context) used to misclassify *any* f\"\"\"/f''' token + # -- e.g. one nested inside a parenthesized expression or tuple -- as + # needing row-continuation treatment, corrupting the row bookkeeping in + # ``_get_unmatched_start_end_indices`` and causing + # ``tokenize.untokenize`` to raise ``ValueError`` (see issue #367). return False