-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
feat(es/parser): Add error recovery for recoverable syntax errors #11479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
676a445
94f9d09
48a8687
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -491,6 +491,15 @@ impl<I: Tokens> Parser<I> { | |||||||||||
| let mut elems = Vec::with_capacity(8); | ||||||||||||
|
|
||||||||||||
| while !self.input().is(Token::RBracket) { | ||||||||||||
| // Recovery: check for EOF to prevent infinite loop | ||||||||||||
| if self.input().is(Token::Eof) { | ||||||||||||
| self.emit_err( | ||||||||||||
| self.input().cur_span(), | ||||||||||||
| SyntaxError::Expected("]".into(), "eof".into()), | ||||||||||||
| ); | ||||||||||||
| break; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| if self.input().is(Token::Comma) { | ||||||||||||
| expect!(self, Token::Comma); | ||||||||||||
| elems.push(None); | ||||||||||||
|
|
@@ -500,15 +509,31 @@ impl<I: Tokens> Parser<I> { | |||||||||||
| elems.push(self.allow_in_expr(|p| p.parse_expr_or_spread()).map(Some)?); | ||||||||||||
|
|
||||||||||||
| if !self.input().is(Token::RBracket) { | ||||||||||||
| expect!(self, Token::Comma); | ||||||||||||
| // Recovery: if not a comma, emit error but continue | ||||||||||||
| if !self.input_mut().eat(Token::Comma) { | ||||||||||||
| // If EOF, break out to avoid infinite loop | ||||||||||||
| if self.input().is(Token::Eof) { | ||||||||||||
| self.emit_err( | ||||||||||||
| self.input().cur_span(), | ||||||||||||
| SyntaxError::Expected("]".into(), "eof".into()), | ||||||||||||
| ); | ||||||||||||
| break; | ||||||||||||
| } | ||||||||||||
| // Emit error for missing comma but continue parsing | ||||||||||||
| let span = self.input().cur_span(); | ||||||||||||
| let cur = self.input_mut().dump_cur(); | ||||||||||||
| self.emit_err(span, SyntaxError::Expected(",".into(), cur)); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| if self.input().is(Token::RBracket) { | ||||||||||||
| let prev_span = self.input().prev_span(); | ||||||||||||
| self.state_mut().trailing_commas.insert(start, prev_span); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| expect!(self, Token::RBracket); | ||||||||||||
| // Recovery: use expect_or_recover to allow continuing even without ] | ||||||||||||
| expect_or_recover!(self, Token::RBracket); | ||||||||||||
|
|
||||||||||||
| let span = self.span(start); | ||||||||||||
| Ok(ArrayLit { span, elems }.into()) | ||||||||||||
|
|
@@ -919,10 +944,30 @@ impl<I: Tokens> Parser<I> { | |||||||||||
| let mut expr_or_spreads = Vec::with_capacity(2); | ||||||||||||
|
|
||||||||||||
| while !p.input().is(Token::RParen) { | ||||||||||||
| // Recovery: check for EOF to prevent infinite loop | ||||||||||||
| if p.input().is(Token::Eof) { | ||||||||||||
| p.emit_err( | ||||||||||||
| p.input().cur_span(), | ||||||||||||
| SyntaxError::Expected(")".into(), "eof".into()), | ||||||||||||
| ); | ||||||||||||
| break; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| if first { | ||||||||||||
| first = false; | ||||||||||||
| } else { | ||||||||||||
| expect!(p, Token::Comma); | ||||||||||||
| // Recovery: if not a comma, emit error but continue | ||||||||||||
| if !p.input_mut().eat(Token::Comma) { | ||||||||||||
| // Check if we're at a closing paren or EOF | ||||||||||||
| if p.input().is(Token::RParen) || p.input().is(Token::Eof) { | ||||||||||||
| break; | ||||||||||||
| } | ||||||||||||
| // Emit error for missing comma | ||||||||||||
| let span = p.input().cur_span(); | ||||||||||||
| let cur = p.input_mut().dump_cur(); | ||||||||||||
| p.emit_err(span, SyntaxError::Expected(",".into(), cur)); | ||||||||||||
|
||||||||||||
| p.emit_err(span, SyntaxError::Expected(",".into(), cur)); | |
| p.emit_err(span, SyntaxError::Expected(",".into(), cur)); | |
| // Recovery: consume the unexpected token to ensure progress | |
| p.input_mut().bump(); | |
| continue; |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -22,10 +22,30 @@ impl<I: Tokens> Parser<I> { | |||||||||
| let mut props = Vec::with_capacity(8); | ||||||||||
|
|
||||||||||
| while !p.input_mut().eat(Token::RBrace) { | ||||||||||
| // Recovery: check for EOF to prevent infinite loop | ||||||||||
| if p.input().is(Token::Eof) { | ||||||||||
| p.emit_err( | ||||||||||
| p.input().cur_span(), | ||||||||||
| SyntaxError::Expected("}".into(), "eof".into()), | ||||||||||
| ); | ||||||||||
| break; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| props.push(parse_prop(p)?); | ||||||||||
|
|
||||||||||
| if !p.input().is(Token::RBrace) { | ||||||||||
| expect!(p, Token::Comma); | ||||||||||
| // Recovery: if not a comma, emit error but continue | ||||||||||
| if !p.input_mut().eat(Token::Comma) { | ||||||||||
| // Check if we're at a closing brace or EOF | ||||||||||
| if p.input().is(Token::RBrace) || p.input().is(Token::Eof) { | ||||||||||
| continue; | ||||||||||
| } | ||||||||||
| // Emit error for missing comma | ||||||||||
| let span = p.input().cur_span(); | ||||||||||
| let cur = p.input_mut().dump_cur(); | ||||||||||
| p.emit_err(span, SyntaxError::Expected(",".into(), cur)); | ||||||||||
|
||||||||||
| p.emit_err(span, SyntaxError::Expected(",".into(), cur)); | |
| p.emit_err(span, SyntaxError::Expected(",".into(), cur)); | |
| // Consume the unexpected token to ensure forward progress | |
| p.input_mut().bump(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| let arr = [1, 2, 3 | ||
| let x = 1; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| x Expression expected | ||
| ,-[$DIR/tests/errors/missing_array_bracket/input.js:2:1] | ||
| 1 | let arr = [1, 2, 3 | ||
| 2 | let x = 1; | ||
| : ^ | ||
| `---- | ||
| x Expected ',', got 'let' | ||
| ,-[$DIR/tests/errors/missing_array_bracket/input.js:2:1] | ||
| 1 | let arr = [1, 2, 3 | ||
| 2 | let x = 1; | ||
| : ^^^ | ||
| `---- | ||
| x `let` cannot be used as an identifier in strict mode | ||
| ,-[$DIR/tests/errors/missing_array_bracket/input.js:2:1] | ||
| 1 | let arr = [1, 2, 3 | ||
| 2 | let x = 1; | ||
| : ^^^ | ||
| `---- | ||
| x Expected ',', got 'ident' | ||
| ,-[$DIR/tests/errors/missing_array_bracket/input.js:2:1] | ||
| 1 | let arr = [1, 2, 3 | ||
| 2 | let x = 1; | ||
| : ^ | ||
| `---- | ||
| x Expected ',', got ';' | ||
| ,-[$DIR/tests/errors/missing_array_bracket/input.js:2:1] | ||
| 1 | let arr = [1, 2, 3 | ||
| 2 | let x = 1; | ||
| : ^ | ||
| `---- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| let arr = [1 2 3]; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| x Expected ',', got 'numeric literal' | ||
| ,-[$DIR/tests/errors/missing_comma_array/input.js:1:1] | ||
| 1 | let arr = [1 2 3]; | ||
| : ^ | ||
| `---- | ||
| x Expected ',', got 'numeric literal' | ||
| ,-[$DIR/tests/errors/missing_comma_array/input.js:1:1] | ||
| 1 | let arr = [1 2 3]; | ||
| : ^ | ||
| `---- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| let obj = {a: 1 b: 2}; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| x Expected ',', got 'ident' | ||
| ,-[$DIR/tests/errors/missing_comma_object/input.js:1:1] | ||
| 1 | let obj = {a: 1 b: 2}; | ||
| : ^ | ||
| `---- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| for let i = 0; i < 10; i++ { | ||
| console.log(i); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| x Expected '(', got 'let' | ||
| ,-[$DIR/tests/errors/missing_paren_for/input.js:1:1] | ||
| 1 | for let i = 0; i < 10; i++ { | ||
| : ^^^ | ||
| 2 | console.log(i); | ||
| `---- | ||
| x Expected ')', got '{' | ||
| ,-[$DIR/tests/errors/missing_paren_for/input.js:1:1] | ||
| 1 | for let i = 0; i < 10; i++ { | ||
| : ^ | ||
| 2 | console.log(i); | ||
| `---- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After emitting an error for a missing comma, the parser continues without consuming the unexpected token. This could potentially lead to issues if parse_expr_or_spread on the next iteration doesn't consume the token or make progress. While the tests may pass for common cases, consider adding explicit token consumption after the error to make the recovery more robust and prevent potential infinite loops in edge cases. For example, you could add a check to consume the unexpected token before continuing the loop.