From ca43a3cbefb8d9268648776dfbc23ae76ffcb7bd Mon Sep 17 00:00:00 2001 From: Oscar Wallberg Date: Fri, 3 Jul 2026 07:00:40 +0200 Subject: [PATCH] fix(syntax/lexer): stop strings at a backslash-escaped newline The escape arm consumed whatever byte followed the backslash, so a backslash at the end of a line swallowed the newline and the lexer ran into the following lines until the next quote, restructuring the rest of the document into one string token. The newline guard now applies inside escapes too. --- src/syntax/lexer.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs index 4d25672..0370041 100644 --- a/src/syntax/lexer.rs +++ b/src/syntax/lexer.rs @@ -103,6 +103,12 @@ impl<'a> Lexer<'a> { } b'\\' => { self.pos += 1; + if self.bytes.get(self.pos) == Some(&b'\n') { + // An escaped newline is not a JSON continuation, + // so the newline still terminates the line. + self.error(start, "unterminated string"); + break; + } if self.pos < self.bytes.len() { self.advance_char(); } @@ -259,6 +265,15 @@ mod tests { assert_eq!(errors.len(), 1); } + #[test] + fn backslash_before_newline_ends_the_string() { + let (tokens, errors) = tokenize("\"C:\\\ntrue"); + assert_eq!(tokens[0].kind, SyntaxKind::Str); + assert_eq!(tokens[0].text, "\"C:\\"); + assert_eq!(errors.len(), 1); + assert_eq!(tokens.last().unwrap().kind, SyntaxKind::True); + } + #[test] fn invalid_number_reports_error() { let (_, errors) = tokenize("1.2.3");