diff --git a/pre_commit_hooks/string_fixer.py b/pre_commit_hooks/string_fixer.py index 0ef9bc7a..d1b1c4ae 100644 --- a/pre_commit_hooks/string_fixer.py +++ b/pre_commit_hooks/string_fixer.py @@ -3,9 +3,16 @@ import argparse import io import re +import sys import tokenize from typing import Sequence +if sys.version_info >= (3, 12): # pragma: >=3.12 cover + FSTRING_START = tokenize.FSTRING_START + FSTRING_END = tokenize.FSTRING_END +else: # pragma: <3.12 cover + FSTRING_START = FSTRING_END = -1 + START_QUOTE_RE = re.compile('^[a-zA-Z]*"') @@ -40,11 +47,17 @@ def fix_strings(filename: str) -> int: # Basically a mutable string splitcontents = list(contents) + fstring_depth = 0 + # Iterate in reverse so the offsets are always correct tokens_l = list(tokenize.generate_tokens(io.StringIO(contents).readline)) tokens = reversed(tokens_l) for token_type, token_text, (srow, scol), (erow, ecol), _ in tokens: - if token_type == tokenize.STRING: + if token_type == FSTRING_START: # pragma: >=3.12 cover + fstring_depth += 1 + elif token_type == FSTRING_END: # pragma: >=3.12 cover + fstring_depth -= 1 + elif fstring_depth == 0 and token_type == tokenize.STRING: new_text = handle_match(token_text) splitcontents[ line_offsets[srow] + scol: diff --git a/tests/string_fixer_test.py b/tests/string_fixer_test.py index 9dd73152..8eb164c5 100644 --- a/tests/string_fixer_test.py +++ b/tests/string_fixer_test.py @@ -37,6 +37,12 @@ 1, ), ('"foo""bar"', "'foo''bar'", 1), + pytest.param( + "f'hello{\"world\"}'", + "f'hello{\"world\"}'", + 0, + id='ignore nested fstrings', + ), )