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 6233ed9353308..e1864fa381755 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/checkers/tokens.rs b/crates/ruff_linter/src/checkers/tokens.rs index 93174b0cbdcbb..4ebcd7bab782d 100644 --- a/crates/ruff_linter/src/checkers/tokens.rs +++ b/crates/ruff_linter/src/checkers/tokens.rs @@ -98,9 +98,7 @@ pub(crate) fn check_tokens( Rule::InvalidCharacterZeroWidthSpace, ]) { for (tok, range) in tokens.iter().flatten() { - if tok.is_string() { - pylint::rules::invalid_string_characters(&mut diagnostics, *range, locator); - } + pylint::rules::invalid_string_characters(&mut diagnostics, tok, *range, locator); } } 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 2bd640f326c4a..2a345a60404d5 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 @@ -4,6 +4,7 @@ use ruff_diagnostics::AlwaysAutofixableViolation; use ruff_diagnostics::Edit; use ruff_diagnostics::{Diagnostic, DiagnosticKind, Fix}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_parser::Tok; use ruff_source_file::Locator; /// ## What it does @@ -173,10 +174,15 @@ impl AlwaysAutofixableViolation for InvalidCharacterZeroWidthSpace { /// PLE2510, PLE2512, PLE2513, PLE2514, PLE2515 pub(crate) fn invalid_string_characters( diagnostics: &mut Vec, + tok: &Tok, range: TextRange, locator: &Locator, ) { - let text = locator.slice(range); + let text = match tok { + Tok::String { .. } => locator.slice(range), + Tok::FStringMiddle { value, .. } => value.as_str(), + _ => return, + }; for (column, match_) in text.match_indices(&['\x08', '\x1A', '\x1B', '\0', '\u{200b}']) { let c = match_.chars().next().unwrap(); 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 48a360b3c363e..25e9c12e479f0 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 @@ -7,8 +7,7 @@ invalid_characters.py:15:6: PLE2510 [*] Invalid unescaped character backspace, u 14 | #foo = 'hi' 15 | b = '' | PLE2510 -16 | -17 | b_ok = '\\b' +16 | b = f'' | = help: Replace with escape sequence @@ -18,8 +17,45 @@ invalid_characters.py:15:6: PLE2510 [*] Invalid unescaped character backspace, u 14 14 | #foo = 'hi' 15 |-b = '' 15 |+b = '\b' -16 16 | -17 17 | b_ok = '\\b' -18 18 | +16 16 | b = f'' +17 17 | +18 18 | b_ok = '\\b' + +invalid_characters.py:16:7: PLE2510 [*] Invalid unescaped character backspace, use "\b" instead + | +14 | #foo = 'hi' +15 | b = '' +16 | b = f'' + | PLE2510 +17 | +18 | b_ok = '\\b' + | + = help: Replace with escape sequence + +ℹ Fix +13 13 | # (Pylint, "C3002") => Rule::UnnecessaryDirectLambdaCall, +14 14 | #foo = 'hi' +15 15 | b = '' +16 |-b = f'' + 16 |+b = f'\b' +17 17 | +18 18 | b_ok = '\\b' +19 19 | b_ok = f'\\b' + +invalid_characters.py:55:21: PLE2510 [*] Invalid unescaped character backspace, use "\b" instead + | +53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +54 | +55 | nested_fstrings = f'{f'{f''}'}' + | PLE2510 + | + = help: Replace with escape sequence + +ℹ Fix +52 52 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +53 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +54 54 | +55 |-nested_fstrings = f'{f'{f''}'}' + 55 |+nested_fstrings = f'\b{f'{f''}'}' 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 eaab51876b789..efda03729de47 100644 --- 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 @@ -1,25 +1,60 @@ --- source: crates/ruff_linter/src/rules/pylint/mod.rs --- -invalid_characters.py:21:12: PLE2512 [*] Invalid unescaped character SUB, use "\x1A" instead +invalid_characters.py:24:12: PLE2512 [*] Invalid unescaped character SUB, use "\x1A" instead | -19 | cr_ok = '\\r' -20 | -21 | sub = 'sub ' +22 | cr_ok = f'\\r' +23 | +24 | sub = 'sub ' | PLE2512 -22 | -23 | sub_ok = '\x1a' +25 | sub = f'sub ' | = help: Replace with escape sequence ℹ Fix -18 18 | -19 19 | cr_ok = '\\r' -20 20 | -21 |-sub = 'sub ' - 21 |+sub = 'sub \x1A' -22 22 | -23 23 | sub_ok = '\x1a' -24 24 | +21 21 | cr_ok = '\\r' +22 22 | cr_ok = f'\\r' +23 23 | +24 |-sub = 'sub ' + 24 |+sub = 'sub \x1A' +25 25 | sub = f'sub ' +26 26 | +27 27 | sub_ok = '\x1a' + +invalid_characters.py:25:13: PLE2512 [*] Invalid unescaped character SUB, use "\x1A" instead + | +24 | sub = 'sub ' +25 | sub = f'sub ' + | PLE2512 +26 | +27 | sub_ok = '\x1a' + | + = help: Replace with escape sequence + +ℹ Fix +22 22 | cr_ok = f'\\r' +23 23 | +24 24 | sub = 'sub ' +25 |-sub = f'sub ' + 25 |+sub = f'sub \x1A' +26 26 | +27 27 | sub_ok = '\x1a' +28 28 | sub_ok = f'\x1a' + +invalid_characters.py:55:25: PLE2512 [*] Invalid unescaped character SUB, use "\x1A" instead + | +53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +54 | +55 | nested_fstrings = f'{f'{f''}'}' + | PLE2512 + | + = help: Replace with escape sequence + +ℹ Fix +52 52 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +53 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +54 54 | +55 |-nested_fstrings = f'{f'{f''}'}' + 55 |+nested_fstrings = f'{f'\x1A{f''}'}' 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 ae13f2baba496..fbfd76cfa23a8 100644 --- 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 @@ -1,25 +1,60 @@ --- source: crates/ruff_linter/src/rules/pylint/mod.rs --- -invalid_characters.py:25:16: PLE2513 [*] Invalid unescaped character ESC, use "\x1B" instead +invalid_characters.py:30:16: PLE2513 [*] Invalid unescaped character ESC, use "\x1B" instead | -23 | sub_ok = '\x1a' -24 | -25 | esc = 'esc esc ' +28 | sub_ok = f'\x1a' +29 | +30 | esc = 'esc esc ' | PLE2513 -26 | -27 | esc_ok = '\x1b' +31 | esc = f'esc esc ' | = help: Replace with escape sequence ℹ Fix -22 22 | -23 23 | sub_ok = '\x1a' -24 24 | -25 |-esc = 'esc esc ' - 25 |+esc = 'esc esc \x1B' -26 26 | -27 27 | esc_ok = '\x1b' -28 28 | +27 27 | sub_ok = '\x1a' +28 28 | sub_ok = f'\x1a' +29 29 | +30 |-esc = 'esc esc ' + 30 |+esc = 'esc esc \x1B' +31 31 | esc = f'esc esc ' +32 32 | +33 33 | esc_ok = '\x1b' + +invalid_characters.py:31:17: PLE2513 [*] Invalid unescaped character ESC, use "\x1B" instead + | +30 | esc = 'esc esc ' +31 | esc = f'esc esc ' + | PLE2513 +32 | +33 | esc_ok = '\x1b' + | + = help: Replace with escape sequence + +ℹ Fix +28 28 | sub_ok = f'\x1a' +29 29 | +30 30 | esc = 'esc esc ' +31 |-esc = f'esc esc ' + 31 |+esc = f'esc esc \x1B' +32 32 | +33 33 | esc_ok = '\x1b' +34 34 | esc_ok = f'\x1b' + +invalid_characters.py:55:29: PLE2513 [*] Invalid unescaped character ESC, use "\x1B" instead + | +53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +54 | +55 | nested_fstrings = f'{f'{f''}'}' + | PLE2513 + | + = help: Replace with escape sequence + +ℹ Fix +52 52 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +53 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +54 54 | +55 |-nested_fstrings = f'{f'{f''}'}' + 55 |+nested_fstrings = f'{f'{f'\x1B'}'}' 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 49a6a4f5186dd..0227171c311f9 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 25210fb48e358..3a9ba051eea5a 100644 --- 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 @@ -1,73 +1,165 @@ --- source: crates/ruff_linter/src/rules/pylint/mod.rs --- -invalid_characters.py:34:13: PLE2515 [*] Invalid unescaped character zero-width-space, use "\u200B" instead +invalid_characters.py:44:13: PLE2515 [*] Invalid unescaped character zero-width-space, use "\u200B" instead | -32 | nul_ok = '\0' -33 | -34 | zwsp = 'zero​width' +42 | nul_ok = f'\0' +43 | +44 | zwsp = 'zero​width' | PLE2515 -35 | -36 | zwsp_ok = '\u200b' +45 | zwsp = f'zero​width' | = help: Replace with escape sequence ℹ Fix -31 31 | -32 32 | nul_ok = '\0' -33 33 | -34 |-zwsp = 'zero​width' - 34 |+zwsp = 'zero\u200bwidth' -35 35 | -36 36 | zwsp_ok = '\u200b' -37 37 | - -invalid_characters.py:38:36: PLE2515 [*] Invalid unescaped character zero-width-space, use "\u200B" instead - | -36 | zwsp_ok = '\u200b' -37 | -38 | zwsp_after_multibyte_character = "ಫ​" +41 41 | nul_ok = '\0' +42 42 | nul_ok = f'\0' +43 43 | +44 |-zwsp = 'zero​width' + 44 |+zwsp = 'zero\u200bwidth' +45 45 | zwsp = f'zero​width' +46 46 | +47 47 | zwsp_ok = '\u200b' + +invalid_characters.py:45:14: PLE2515 [*] Invalid unescaped character zero-width-space, use "\u200B" instead + | +44 | zwsp = 'zero​width' +45 | zwsp = f'zero​width' + | PLE2515 +46 | +47 | zwsp_ok = '\u200b' + | + = help: Replace with escape sequence + +ℹ Fix +42 42 | nul_ok = f'\0' +43 43 | +44 44 | zwsp = 'zero​width' +45 |-zwsp = f'zero​width' + 45 |+zwsp = f'zero\u200bwidth' +46 46 | +47 47 | zwsp_ok = '\u200b' +48 48 | zwsp_ok = f'\u200b' + +invalid_characters.py:50:36: PLE2515 [*] Invalid unescaped character zero-width-space, use "\u200B" instead + | +48 | zwsp_ok = f'\u200b' +49 | +50 | zwsp_after_multibyte_character = "ಫ​" | PLE2515 -39 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +51 | zwsp_after_multibyte_character = f"ಫ​" +52 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" + | + = help: Replace with escape sequence + +ℹ Fix +47 47 | zwsp_ok = '\u200b' +48 48 | zwsp_ok = f'\u200b' +49 49 | +50 |-zwsp_after_multibyte_character = "ಫ​" + 50 |+zwsp_after_multibyte_character = "ಫ\u200b" +51 51 | zwsp_after_multibyte_character = f"ಫ​" +52 52 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +53 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" + +invalid_characters.py:51:37: PLE2515 [*] Invalid unescaped character zero-width-space, use "\u200B" instead + | +50 | zwsp_after_multibyte_character = "ಫ​" +51 | zwsp_after_multibyte_character = f"ಫ​" + | PLE2515 +52 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" | = help: Replace with escape sequence ℹ Fix -35 35 | -36 36 | zwsp_ok = '\u200b' -37 37 | -38 |-zwsp_after_multibyte_character = "ಫ​" - 38 |+zwsp_after_multibyte_character = "ಫ\u200b" -39 39 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +48 48 | zwsp_ok = f'\u200b' +49 49 | +50 50 | zwsp_after_multibyte_character = "ಫ​" +51 |-zwsp_after_multibyte_character = f"ಫ​" + 51 |+zwsp_after_multibyte_character = f"ಫ\u200b" +52 52 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +53 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +54 54 | -invalid_characters.py:39:60: PLE2515 [*] Invalid unescaped character zero-width-space, use "\u200B" instead +invalid_characters.py:52:60: PLE2515 [*] Invalid unescaped character zero-width-space, use "\u200B" instead | -38 | zwsp_after_multibyte_character = "ಫ​" -39 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +50 | zwsp_after_multibyte_character = "ಫ​" +51 | zwsp_after_multibyte_character = f"ಫ​" +52 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" | PLE2515 +53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" | = help: Replace with escape sequence ℹ Fix -36 36 | zwsp_ok = '\u200b' -37 37 | -38 38 | zwsp_after_multibyte_character = "ಫ​" -39 |-zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" - 39 |+zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ \u200b​" +49 49 | +50 50 | zwsp_after_multibyte_character = "ಫ​" +51 51 | zwsp_after_multibyte_character = f"ಫ​" +52 |-zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" + 52 |+zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ \u200b​" +53 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +54 54 | +55 55 | nested_fstrings = f'{f'{f''}'}' -invalid_characters.py:39:61: PLE2515 [*] Invalid unescaped character zero-width-space, use "\u200B" instead +invalid_characters.py:52:61: PLE2515 [*] Invalid unescaped character zero-width-space, use "\u200B" instead | -38 | zwsp_after_multibyte_character = "ಫ​" -39 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +50 | zwsp_after_multibyte_character = "ಫ​" +51 | zwsp_after_multibyte_character = f"ಫ​" +52 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" | PLE2515 +53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" + | + = help: Replace with escape sequence + +ℹ Fix +49 49 | +50 50 | zwsp_after_multibyte_character = "ಫ​" +51 51 | zwsp_after_multibyte_character = f"ಫ​" +52 |-zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" + 52 |+zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​\u200b" +53 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +54 54 | +55 55 | nested_fstrings = f'{f'{f''}'}' + +invalid_characters.py:53:61: PLE2515 [*] Invalid unescaped character zero-width-space, use "\u200B" instead + | +51 | zwsp_after_multibyte_character = f"ಫ​" +52 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" + | PLE2515 +54 | +55 | nested_fstrings = f'{f'{f''}'}' + | + = help: Replace with escape sequence + +ℹ Fix +50 50 | zwsp_after_multibyte_character = "ಫ​" +51 51 | zwsp_after_multibyte_character = f"ಫ​" +52 52 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +53 |-zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" + 53 |+zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ \u200b​" +54 54 | +55 55 | nested_fstrings = f'{f'{f''}'}' + +invalid_characters.py:53:62: PLE2515 [*] Invalid unescaped character zero-width-space, use "\u200B" instead + | +51 | zwsp_after_multibyte_character = f"ಫ​" +52 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" + | PLE2515 +54 | +55 | nested_fstrings = f'{f'{f''}'}' | = help: Replace with escape sequence ℹ Fix -36 36 | zwsp_ok = '\u200b' -37 37 | -38 38 | zwsp_after_multibyte_character = "ಫ​" -39 |-zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" - 39 |+zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​\u200b" +50 50 | zwsp_after_multibyte_character = "ಫ​" +51 51 | zwsp_after_multibyte_character = f"ಫ​" +52 52 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" +53 |-zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​" + 53 |+zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​\u200b" +54 54 | +55 55 | nested_fstrings = f'{f'{f''}'}'