@@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
5
5
use ruff_python_index:: Indexer ;
6
6
use ruff_python_parser:: Tok ;
7
7
use ruff_source_file:: Locator ;
8
- use ruff_text_size:: { Ranged , TextLen , TextRange , TextSize } ;
8
+ use ruff_text_size:: { TextLen , TextRange , TextSize } ;
9
9
10
10
use crate :: fix:: edits:: pad_start;
11
11
@@ -25,23 +25,51 @@ use crate::fix::edits::pad_start;
25
25
/// regex = r"\.png$"
26
26
/// ```
27
27
///
28
+ /// Or, if the string already contains a valid escape sequence:
29
+ /// ```python
30
+ /// value = "new line\nand invalid escape \_ here"
31
+ /// ```
32
+ ///
33
+ /// Use instead:
34
+ /// ```python
35
+ /// value = "new line\nand invalid escape \\_ here"
36
+ /// ```
37
+ ///
28
38
/// ## References
29
39
/// - [Python documentation: String and Bytes literals](https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals)
30
40
#[ violation]
31
- pub struct InvalidEscapeSequence ( char ) ;
41
+ pub struct InvalidEscapeSequence {
42
+ ch : char ,
43
+ fix_title : FixTitle ,
44
+ }
32
45
33
46
impl AlwaysFixableViolation for InvalidEscapeSequence {
34
47
#[ derive_message_formats]
35
48
fn message ( & self ) -> String {
36
- let InvalidEscapeSequence ( char ) = self ;
37
- format ! ( "Invalid escape sequence: `\\ {char }`" )
49
+ let InvalidEscapeSequence { ch , .. } = self ;
50
+ format ! ( "Invalid escape sequence: `\\ {ch }`" )
38
51
}
39
52
40
53
fn fix_title ( & self ) -> String {
41
- "Add backslash to escape sequence" . to_string ( )
54
+ match self . fix_title {
55
+ FixTitle :: AddBackslash => format ! ( "Add backslash to escape sequence" ) ,
56
+ FixTitle :: UseRawStringLiteral => format ! ( "Use a raw string literal" ) ,
57
+ }
42
58
}
43
59
}
44
60
61
+ #[ derive( Debug , PartialEq , Eq ) ]
62
+ enum FixTitle {
63
+ AddBackslash ,
64
+ UseRawStringLiteral ,
65
+ }
66
+
67
+ #[ derive( Debug ) ]
68
+ struct InvalidEscapeChar {
69
+ ch : char ,
70
+ range : TextRange ,
71
+ }
72
+
45
73
/// W605
46
74
pub ( crate ) fn invalid_escape_sequence (
47
75
diagnostics : & mut Vec < Diagnostic > ,
@@ -67,7 +95,7 @@ pub(crate) fn invalid_escape_sequence(
67
95
} ;
68
96
69
97
let mut contains_valid_escape_sequence = false ;
70
- let mut invalid_escape_sequence = Vec :: new ( ) ;
98
+ let mut invalid_escape_chars = Vec :: new ( ) ;
71
99
72
100
let mut prev = None ;
73
101
let bytes = token_source_code. as_bytes ( ) ;
@@ -154,16 +182,28 @@ pub(crate) fn invalid_escape_sequence(
154
182
155
183
let location = token_range. start ( ) + TextSize :: try_from ( i) . unwrap ( ) ;
156
184
let range = TextRange :: at ( location, next_char. text_len ( ) + TextSize :: from ( 1 ) ) ;
157
- invalid_escape_sequence. push ( Diagnostic :: new ( InvalidEscapeSequence ( next_char) , range) ) ;
185
+ invalid_escape_chars. push ( InvalidEscapeChar {
186
+ ch : next_char,
187
+ range,
188
+ } ) ;
158
189
}
159
190
191
+ let mut invalid_escape_sequence = Vec :: new ( ) ;
160
192
if contains_valid_escape_sequence {
161
193
// Escape with backslash.
162
- for diagnostic in & mut invalid_escape_sequence {
163
- diagnostic. set_fix ( Fix :: safe_edit ( Edit :: insertion (
194
+ for invalid_escape_char in & invalid_escape_chars {
195
+ let diagnostic = Diagnostic :: new (
196
+ InvalidEscapeSequence {
197
+ ch : invalid_escape_char. ch ,
198
+ fix_title : FixTitle :: AddBackslash ,
199
+ } ,
200
+ invalid_escape_char. range ,
201
+ )
202
+ . with_fix ( Fix :: safe_edit ( Edit :: insertion (
164
203
r"\" . to_string ( ) ,
165
- diagnostic . start ( ) + TextSize :: from ( 1 ) ,
204
+ invalid_escape_char . range . start ( ) + TextSize :: from ( 1 ) ,
166
205
) ) ) ;
206
+ invalid_escape_sequence. push ( diagnostic) ;
167
207
}
168
208
} else {
169
209
let tok_start = if token. is_f_string_middle ( ) {
@@ -178,14 +218,24 @@ pub(crate) fn invalid_escape_sequence(
178
218
token_range. start ( )
179
219
} ;
180
220
// Turn into raw string.
181
- for diagnostic in & mut invalid_escape_sequence {
182
- // If necessary, add a space between any leading keyword (`return`, `yield`,
183
- // `assert`, etc.) and the string. For example, `return"foo"` is valid, but
184
- // `returnr"foo"` is not.
185
- diagnostic. set_fix ( Fix :: safe_edit ( Edit :: insertion (
186
- pad_start ( "r" . to_string ( ) , tok_start, locator) ,
187
- tok_start,
188
- ) ) ) ;
221
+ for invalid_escape_char in & invalid_escape_chars {
222
+ let diagnostic = Diagnostic :: new (
223
+ InvalidEscapeSequence {
224
+ ch : invalid_escape_char. ch ,
225
+ fix_title : FixTitle :: UseRawStringLiteral ,
226
+ } ,
227
+ invalid_escape_char. range ,
228
+ )
229
+ . with_fix (
230
+ // If necessary, add a space between any leading keyword (`return`, `yield`,
231
+ // `assert`, etc.) and the string. For example, `return"foo"` is valid, but
232
+ // `returnr"foo"` is not.
233
+ Fix :: safe_edit ( Edit :: insertion (
234
+ pad_start ( "r" . to_string ( ) , tok_start, locator) ,
235
+ tok_start,
236
+ ) ) ,
237
+ ) ;
238
+ invalid_escape_sequence. push ( diagnostic) ;
189
239
}
190
240
}
191
241
0 commit comments