diff --git a/black.py b/black.py index 01390b3..4995b53 100644 --- a/black.py +++ b/black.py @@ -1917,10 +1917,20 @@ def normalize_string_quotes(leaf: Leaf) -> None: if first_quote_pos == -1: return # There's an internal error + prefix = leaf.value[:first_quote_pos] body = leaf.value[first_quote_pos + len(orig_quote):-len(orig_quote)] - new_body = body.replace(f"\\{orig_quote}", orig_quote).replace( - new_quote, f"\\{new_quote}" - ) + if "r" in prefix.casefold(): + if body.count(new_quote) != body.count(f"\\{new_quote}"): + # There's at least one unescaped new_quote in this raw string + # so converting is impossible + return + + # Do not introduce or remove backslashes in raw strings + new_body = body + else: + new_body = body.replace(f"\\{orig_quote}", orig_quote).replace( + new_quote, f"\\{new_quote}" + ) if new_quote == '"""' and new_body[-1] == '"': # edge case: new_body = new_body[:-1] + '\\"' @@ -1932,7 +1942,6 @@ def normalize_string_quotes(leaf: Leaf) -> None: if new_escape_count == orig_escape_count and orig_quote == '"': return # Prefer double quotes - prefix = leaf.value[:first_quote_pos] leaf.value = f"{prefix}{new_quote}{new_body}{new_quote}" diff --git a/tests/string_quotes.py b/tests/string_quotes.py index 1080aaf..f2d720b 100644 --- a/tests/string_quotes.py +++ b/tests/string_quotes.py @@ -15,6 +15,9 @@ f'MOAR {" ".join([])}' f"MOAR {' '.join([])}" r"raw string ftw" +r'Date d\'expiration:(.*)' +r'Tricky "quote' +r'Not-so-tricky \"quote' # output @@ -35,3 +38,6 @@ f'MOAR {" ".join([])}' f"MOAR {' '.join([])}" r"raw string ftw" +r"Date d\'expiration:(.*)" +r'Tricky "quote' +r"Not-so-tricky \"quote"