mirror of
https://github.com/astral-sh/ruff
synced 2026-01-20 21:10:48 -05:00
Ignore trailing quotes for unclosed l-brace errors (#9388)
## Summary
Given:
```python
F"{"ڤ
```
We try to locate the "unclosed left brace" error by subtracting the
quote size from the lexer offset -- so we subtract 1 from the end of the
source, which puts us in the middle of a Unicode character. I don't
think we should try to adjust the offset in this way, since there can be
content _after_ the quote. For example, with the advent of PEP 701, this
string could reasonably be fixed as:
```python
F"{"ڤ"}"
````
Closes https://github.com/astral-sh/ruff/issues/9379.
This commit is contained in:
@@ -700,7 +700,7 @@ impl<'source> Lexer<'source> {
|
||||
}
|
||||
Some('\r' | '\n') if !triple_quoted => {
|
||||
if let Some(fstring) = self.fstrings.current() {
|
||||
// When we are in an f-string, check whether does the initial quote
|
||||
// When we are in an f-string, check whether the initial quote
|
||||
// matches with f-strings quotes and if it is, then this must be a
|
||||
// missing '}' token so raise the proper error.
|
||||
if fstring.quote_char() == quote && !fstring.is_triple_quoted() {
|
||||
@@ -708,7 +708,7 @@ impl<'source> Lexer<'source> {
|
||||
error: LexicalErrorType::FStringError(
|
||||
FStringErrorType::UnclosedLbrace,
|
||||
),
|
||||
location: self.offset() - fstring.quote_size(),
|
||||
location: self.offset() - TextSize::new(1),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -732,7 +732,7 @@ impl<'source> Lexer<'source> {
|
||||
Some(_) => {}
|
||||
None => {
|
||||
if let Some(fstring) = self.fstrings.current() {
|
||||
// When we are in an f-string, check whether does the initial quote
|
||||
// When we are in an f-string, check whether the initial quote
|
||||
// matches with f-strings quotes and if it is, then this must be a
|
||||
// missing '}' token so raise the proper error.
|
||||
if fstring.quote_char() == quote
|
||||
@@ -742,7 +742,7 @@ impl<'source> Lexer<'source> {
|
||||
error: LexicalErrorType::FStringError(
|
||||
FStringErrorType::UnclosedLbrace,
|
||||
),
|
||||
location: self.offset() - fstring.quote_size(),
|
||||
location: self.offset(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2195,13 +2195,17 @@ f"{(lambda x:{x})}"
|
||||
assert_debug_snapshot!(lex_jupyter_source(source));
|
||||
}
|
||||
|
||||
fn lex_error(source: &str) -> LexicalError {
|
||||
match lex(source, Mode::Module).find_map(Result::err) {
|
||||
Some(err) => err,
|
||||
_ => panic!("Expected at least one error"),
|
||||
}
|
||||
}
|
||||
|
||||
fn lex_fstring_error(source: &str) -> FStringErrorType {
|
||||
match lex(source, Mode::Module).find_map(std::result::Result::err) {
|
||||
Some(err) => match err.error {
|
||||
LexicalErrorType::FStringError(error) => error,
|
||||
_ => panic!("Expected FStringError: {err:?}"),
|
||||
},
|
||||
_ => panic!("Expected atleast one FStringError"),
|
||||
match lex_error(source).error {
|
||||
LexicalErrorType::FStringError(error) => error,
|
||||
err => panic!("Expected FStringError: {err:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2246,4 +2250,25 @@ f"{(lambda x:{x})}"
|
||||
UnterminatedTripleQuotedString
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fstring_error_location() {
|
||||
assert_debug_snapshot!(lex_error("f'{'"), @r###"
|
||||
LexicalError {
|
||||
error: FStringError(
|
||||
UnclosedLbrace,
|
||||
),
|
||||
location: 4,
|
||||
}
|
||||
"###);
|
||||
|
||||
assert_debug_snapshot!(lex_error("f'{'α"), @r###"
|
||||
LexicalError {
|
||||
error: FStringError(
|
||||
UnclosedLbrace,
|
||||
),
|
||||
location: 6,
|
||||
}
|
||||
"###);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use bitflags::bitflags;
|
||||
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FStringContextFlags: u8 {
|
||||
@@ -58,15 +56,6 @@ impl FStringContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of quotes for the current f-string.
|
||||
pub(crate) const fn quote_size(&self) -> TextSize {
|
||||
if self.is_triple_quoted() {
|
||||
TextSize::new(3)
|
||||
} else {
|
||||
TextSize::new(1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the triple quotes for the current f-string if it is a triple-quoted
|
||||
/// f-string, `None` otherwise.
|
||||
pub(crate) const fn triple_quotes(&self) -> Option<&'static str> {
|
||||
|
||||
Reference in New Issue
Block a user