From e91ffe3e93a1c7c8be6b1fa6eeaf21f3c82b8ff5 Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Sun, 1 Oct 2023 07:58:20 +0530 Subject: [PATCH] Consume the escaped Windows newline (`\r\n`) for `FStringMiddle` (#7722) ## Summary This PR fixes a bug where if a Windows newline (`\r\n`) character was escaped, then only the `\r` was consumed and not `\n` leading to an unterminated string error. ## Test Plan Add new test cases to check the newline escapes. fixes: #7632 --- crates/ruff_python_parser/src/lexer.rs | 28 +++++++++++++++++-- ...__fstring_single_quote_escape_mac_eol.snap | 25 +++++++++++++++++ ..._fstring_single_quote_escape_unix_eol.snap | 25 +++++++++++++++++ ...tring_single_quote_escape_windows_eol.snap | 25 +++++++++++++++++ 4 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_mac_eol.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_unix_eol.snap create mode 100644 crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_windows_eol.snap diff --git a/crates/ruff_python_parser/src/lexer.rs b/crates/ruff_python_parser/src/lexer.rs index 5fca16e6f2fc2..c2aa3772898fe 100644 --- a/crates/ruff_python_parser/src/lexer.rs +++ b/crates/ruff_python_parser/src/lexer.rs @@ -584,7 +584,7 @@ impl<'source> Lexer<'source> { location: self.offset(), }); } - '\n' if !fstring.is_triple_quoted() => { + '\n' | '\r' if !fstring.is_triple_quoted() => { return Err(LexicalError { error: LexicalErrorType::FStringError(FStringErrorType::UnterminatedString), location: self.offset(), @@ -603,7 +603,11 @@ impl<'source> Lexer<'source> { } } // Consume the escaped character. - self.cursor.bump(); + if self.cursor.eat_char('\r') { + self.cursor.eat_char('\n'); + } else { + self.cursor.bump(); + } } quote @ ('\'' | '"') if quote == fstring.quote_char() => { if let Some(triple_quotes) = fstring.triple_quotes() { @@ -1983,6 +1987,26 @@ def f(arg=%timeit a = b): assert_debug_snapshot!(lex_source(source)); } + fn fstring_single_quote_escape_eol(eol: &str) -> Vec { + let source = format!(r"f'text \{eol} more text'"); + lex_source(&source) + } + + #[test] + fn test_fstring_single_quote_escape_unix_eol() { + assert_debug_snapshot!(fstring_single_quote_escape_eol(UNIX_EOL)); + } + + #[test] + fn test_fstring_single_quote_escape_mac_eol() { + assert_debug_snapshot!(fstring_single_quote_escape_eol(MAC_EOL)); + } + + #[test] + fn test_fstring_single_quote_escape_windows_eol() { + assert_debug_snapshot!(fstring_single_quote_escape_eol(WINDOWS_EOL)); + } + #[test] fn test_fstring_escape() { let source = r#"f"\{x:\"\{x}} \"\"\ diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_mac_eol.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_mac_eol.snap new file mode 100644 index 0000000000000..c955bded5f680 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_mac_eol.snap @@ -0,0 +1,25 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: fstring_single_quote_escape_eol(MAC_EOL) +--- +[ + ( + FStringStart, + 0..2, + ), + ( + FStringMiddle { + value: "text \\\r more text", + is_raw: false, + }, + 2..19, + ), + ( + FStringEnd, + 19..20, + ), + ( + Newline, + 20..20, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_unix_eol.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_unix_eol.snap new file mode 100644 index 0000000000000..0b30f70577f83 --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_unix_eol.snap @@ -0,0 +1,25 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: fstring_single_quote_escape_eol(UNIX_EOL) +--- +[ + ( + FStringStart, + 0..2, + ), + ( + FStringMiddle { + value: "text \\\n more text", + is_raw: false, + }, + 2..19, + ), + ( + FStringEnd, + 19..20, + ), + ( + Newline, + 20..20, + ), +] diff --git a/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_windows_eol.snap b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_windows_eol.snap new file mode 100644 index 0000000000000..e7b88d9f94b7f --- /dev/null +++ b/crates/ruff_python_parser/src/snapshots/ruff_python_parser__lexer__tests__fstring_single_quote_escape_windows_eol.snap @@ -0,0 +1,25 @@ +--- +source: crates/ruff_python_parser/src/lexer.rs +expression: fstring_single_quote_escape_eol(WINDOWS_EOL) +--- +[ + ( + FStringStart, + 0..2, + ), + ( + FStringMiddle { + value: "text \\\r\n more text", + is_raw: false, + }, + 2..20, + ), + ( + FStringEnd, + 20..21, + ), + ( + Newline, + 21..21, + ), +]