Check an f-string literal element for invalid escapes. The range covers content only (no prefix/quotes). Also handles `\{` / `\}` at the literal–interpolation boundary, equivalent to `_PyTokenizer_warn_invalid_escape_sequence` handling `FSTRING_MIDDLE` / `FSTRING_END` tokens.
(&self, range: TextRange)
| 223 | /// equivalent to `_PyTokenizer_warn_invalid_escape_sequence` handling |
| 224 | /// `FSTRING_MIDDLE` / `FSTRING_END` tokens. |
| 225 | fn check_fstring_literal(&self, range: TextRange) { |
| 226 | let start = range.start().to_usize(); |
| 227 | let end = range.end().to_usize(); |
| 228 | if start >= end || end > self.source.len() { |
| 229 | return; |
| 230 | } |
| 231 | if let Some((ch, offset)) = first_invalid_escape(self.source, start, end, false) { |
| 232 | warn_invalid_escape_sequence(self.source, ch, offset, self.filename, self.vm); |
| 233 | return; |
| 234 | } |
| 235 | // In CPython, _PyTokenizer_warn_invalid_escape_sequence handles |
| 236 | // `\{` and `\}` for FSTRING_MIDDLE/FSTRING_END tokens. Ruff |
| 237 | // splits the literal element before the interpolation delimiter, |
| 238 | // so the `\` sits at the end of the literal range and the `{`/`}` |
| 239 | // sits just after it. Only warn when the number of trailing |
| 240 | // backslashes is odd (an even count means they are all escaped). |
| 241 | let trailing_bs = self.source.as_bytes()[start..end] |
| 242 | .iter() |
| 243 | .rev() |
| 244 | .take_while(|&&b| b == b'\\') |
| 245 | .count(); |
| 246 | if trailing_bs % 2 == 1 |
| 247 | && let Some(&after) = self.source.as_bytes().get(end) |
| 248 | && (after == b'{' || after == b'}') |
| 249 | { |
| 250 | warn_invalid_escape_sequence( |
| 251 | self.source, |
| 252 | after as char, |
| 253 | end - 1, |
| 254 | self.filename, |
| 255 | self.vm, |
| 256 | ); |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | /// Visit f-string elements, checking literals and recursing into |
| 261 | /// interpolation expressions and format specs. |
no test coverage detected