mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:38:25 +00: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:
parent
9a14f403c8
commit
f0d43dafcf
2 changed files with 35 additions and 21 deletions
|
@ -700,7 +700,7 @@ impl<'source> Lexer<'source> {
|
||||||
}
|
}
|
||||||
Some('\r' | '\n') if !triple_quoted => {
|
Some('\r' | '\n') if !triple_quoted => {
|
||||||
if let Some(fstring) = self.fstrings.current() {
|
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
|
// matches with f-strings quotes and if it is, then this must be a
|
||||||
// missing '}' token so raise the proper error.
|
// missing '}' token so raise the proper error.
|
||||||
if fstring.quote_char() == quote && !fstring.is_triple_quoted() {
|
if fstring.quote_char() == quote && !fstring.is_triple_quoted() {
|
||||||
|
@ -708,7 +708,7 @@ impl<'source> Lexer<'source> {
|
||||||
error: LexicalErrorType::FStringError(
|
error: LexicalErrorType::FStringError(
|
||||||
FStringErrorType::UnclosedLbrace,
|
FStringErrorType::UnclosedLbrace,
|
||||||
),
|
),
|
||||||
location: self.offset() - fstring.quote_size(),
|
location: self.offset() - TextSize::new(1),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -732,7 +732,7 @@ impl<'source> Lexer<'source> {
|
||||||
Some(_) => {}
|
Some(_) => {}
|
||||||
None => {
|
None => {
|
||||||
if let Some(fstring) = self.fstrings.current() {
|
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
|
// matches with f-strings quotes and if it is, then this must be a
|
||||||
// missing '}' token so raise the proper error.
|
// missing '}' token so raise the proper error.
|
||||||
if fstring.quote_char() == quote
|
if fstring.quote_char() == quote
|
||||||
|
@ -742,7 +742,7 @@ impl<'source> Lexer<'source> {
|
||||||
error: LexicalErrorType::FStringError(
|
error: LexicalErrorType::FStringError(
|
||||||
FStringErrorType::UnclosedLbrace,
|
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));
|
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 {
|
fn lex_fstring_error(source: &str) -> FStringErrorType {
|
||||||
match lex(source, Mode::Module).find_map(std::result::Result::err) {
|
match lex_error(source).error {
|
||||||
Some(err) => match err.error {
|
LexicalErrorType::FStringError(error) => error,
|
||||||
LexicalErrorType::FStringError(error) => error,
|
err => panic!("Expected FStringError: {err:?}"),
|
||||||
_ => panic!("Expected FStringError: {err:?}"),
|
|
||||||
},
|
|
||||||
_ => panic!("Expected atleast one FStringError"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2246,4 +2250,25 @@ f"{(lambda x:{x})}"
|
||||||
UnterminatedTripleQuotedString
|
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 bitflags::bitflags;
|
||||||
|
|
||||||
use ruff_text_size::TextSize;
|
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct FStringContextFlags: u8 {
|
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
|
/// Returns the triple quotes for the current f-string if it is a triple-quoted
|
||||||
/// f-string, `None` otherwise.
|
/// f-string, `None` otherwise.
|
||||||
pub(crate) const fn triple_quotes(&self) -> Option<&'static str> {
|
pub(crate) const fn triple_quotes(&self) -> Option<&'static str> {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue