Merge pull request #973 from pre-commit/string-fixer-3-12

don't rewrite string quotes inside f-strings
diff --git a/pre_commit_hooks/string_fixer.py b/pre_commit_hooks/string_fixer.py
index 0ef9bc7..d1b1c4a 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 @@
     # 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 9dd7315..8eb164c 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',
+    ),
 )