diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/invalid_characters.py b/crates/ruff_linter/resources/test/fixtures/pylint/invalid_characters.py index 3c533e74a20346..c8f1872cff1858 100644 Binary files a/crates/ruff_linter/resources/test/fixtures/pylint/invalid_characters.py and b/crates/ruff_linter/resources/test/fixtures/pylint/invalid_characters.py differ diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs index dc7404ec99cdbd..e83afc284fa7fc 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs @@ -265,9 +265,16 @@ pub(crate) fn invalid_string_characters<'a>( let replacement: &str = match c { '\\' => "\\\\", '\'' | '"' => { - // Quotes don't have to be escaped in triple-quoted strings, - // *except* at the very end (like """this: \""""). - if string_flags.is_triple_quoted() && column + 1 < string_content.len() { + // Quotes only have to be escaped in triple-quoted strings at the beginning + // of a triplet (like `\"""\"""` within the string, or `\""""` at the end). + // For simplicity, escape all quotes not followed by the same character + // (e.g., `r""" \""" \""""` becomes `""" \\\"\"" \""""`). + if string_flags.is_triple_quoted() + && string_content + .as_bytes() + .get(column + 1) + .is_some_and(|c2| char::from(*c2) != c) + { continue; } match (c, string_flags.quote_style()) { diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2510_invalid_characters.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2510_invalid_characters.py.snap index 2687f9a81814b2..86167ee4e1adbb 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2510_invalid_characters.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2510_invalid_characters.py.snap @@ -69,7 +69,7 @@ invalid_characters.py:63:35: PLE2510 [*] Invalid unescaped character backspace, 63 | raw_single_singlequote = r'\ \' " ␈' | ^ PLE2510 64 | raw_triple_singlequote = r'''\ ' " ''' -65 | raw_triple_singlequote_2 = r'''​\'''' +65 | raw_triple_singlequote_2 = r'''​ \''' \'''' | = help: Replace with escape sequence @@ -80,7 +80,7 @@ invalid_characters.py:63:35: PLE2510 [*] Invalid unescaped character backspace, 63 |-raw_single_singlequote = r'\ \' " ␈' 63 |+raw_single_singlequote = '\\ \\\' " \b' 64 64 | raw_triple_singlequote = r'''\ ' " ''' -65 65 | raw_triple_singlequote_2 = r'''​\'''' +65 65 | raw_triple_singlequote_2 = r'''​ \''' \'''' 66 66 | raw_single_doublequote = r"\ ' \" ␛" invalid_characters.py:77:1: PLE2510 [*] Invalid unescaped character backspace, use "\b" instead diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2512_invalid_characters.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2512_invalid_characters.py.snap index e012b9700f35b2..ca47b78b75dc3b 100644 Binary files a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2512_invalid_characters.py.snap and b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2512_invalid_characters.py.snap differ diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2513_invalid_characters.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2513_invalid_characters.py.snap index 9f322086af413c..e4a324ae17d12d 100644 Binary files a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2513_invalid_characters.py.snap and b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2513_invalid_characters.py.snap differ diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2514_invalid_characters.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2514_invalid_characters.py.snap index 5a4fd3ee9d5eef..426c787310024e 100644 Binary files a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2514_invalid_characters.py.snap and b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2514_invalid_characters.py.snap differ diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2515_invalid_characters.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2515_invalid_characters.py.snap index 6456a3631ce596..b8ad690a123a2f 100644 Binary files a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2515_invalid_characters.py.snap and b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2515_invalid_characters.py.snap differ