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 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):