Skip to content

Commit 5d2a124

Browse files
committed
feat(parser): improve error messages for missing closing parentheses
1 parent 12205d8 commit 5d2a124

File tree

5 files changed

+102
-48
lines changed

5 files changed

+102
-48
lines changed

crates/oxc_parser/src/js/expression.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ use crate::{
2424

2525
impl<'a> ParserImpl<'a> {
2626
pub(crate) fn parse_paren_expression(&mut self) -> Expression<'a> {
27+
let opening_span = self.cur_token().span();
2728
self.expect(Kind::LParen);
2829
let expression = self.parse_expr();
29-
self.expect(Kind::RParen);
30+
self.expect_closing(Kind::RParen, opening_span);
3031
expression
3132
}
3233

crates/oxc_parser/src/js/statement.rs

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -303,11 +303,12 @@ impl<'a> ParserImpl<'a> {
303303
false
304304
};
305305

306+
let parenthesis_opening_span = self.cur_token().span();
306307
self.expect(Kind::LParen);
307308

308309
// for (;..
309310
if self.at(Kind::Semicolon) {
310-
return self.parse_for_loop(span, None, r#await);
311+
return self.parse_for_loop(span, parenthesis_opening_span, None, r#await);
311312
}
312313

313314
// `for (const` | `for (var`
@@ -318,6 +319,7 @@ impl<'a> ParserImpl<'a> {
318319
return self.parse_variable_declaration_for_statement(
319320
span,
320321
start_span,
322+
parenthesis_opening_span,
321323
VariableDeclarationKind::Const,
322324
r#await,
323325
);
@@ -328,6 +330,7 @@ impl<'a> ParserImpl<'a> {
328330
return self.parse_variable_declaration_for_statement(
329331
span,
330332
start_span,
333+
parenthesis_opening_span,
331334
VariableDeclarationKind::Var,
332335
r#await,
333336
);
@@ -342,6 +345,7 @@ impl<'a> ParserImpl<'a> {
342345
return self.parse_variable_declaration_for_statement(
343346
span,
344347
start_span,
348+
parenthesis_opening_span,
345349
VariableDeclarationKind::Let,
346350
r#await,
347351
);
@@ -363,7 +367,11 @@ impl<'a> ParserImpl<'a> {
363367
!p.cur_token().is_on_new_line()
364368
})
365369
{
366-
return self.parse_using_declaration_for_statement(span, r#await);
370+
return self.parse_using_declaration_for_statement(
371+
span,
372+
parenthesis_opening_span,
373+
r#await,
374+
);
367375
}
368376

369377
// [+Using] using [no LineTerminator here] ForBinding[?Yield, ?Await, ~Pattern]
@@ -372,11 +380,15 @@ impl<'a> ParserImpl<'a> {
372380
let kind = token.kind();
373381
!token.is_on_new_line() && kind != Kind::Of && kind.is_binding_identifier()
374382
} {
375-
return self.parse_using_declaration_for_statement(span, r#await);
383+
return self.parse_using_declaration_for_statement(
384+
span,
385+
parenthesis_opening_span,
386+
r#await,
387+
);
376388
}
377389

378390
if self.at(Kind::RParen) {
379-
return self.parse_for_loop(span, None, r#await);
391+
return self.parse_for_loop(span, parenthesis_opening_span, None, r#await);
380392
}
381393

382394
let is_let = self.at(Kind::Let);
@@ -390,7 +402,7 @@ impl<'a> ParserImpl<'a> {
390402
Kind::In => {
391403
let target = AssignmentTarget::cover(init_expression, self);
392404
let for_stmt_left = ForStatementLeft::from(target);
393-
self.parse_for_in_loop(span, r#await, for_stmt_left)
405+
self.parse_for_in_loop(span, parenthesis_opening_span, r#await, for_stmt_left)
394406
}
395407
Kind::Of => {
396408
if !r#await && is_async && init_expression.is_identifier_reference() {
@@ -403,16 +415,22 @@ impl<'a> ParserImpl<'a> {
403415
}
404416
let target = AssignmentTarget::cover(init_expression, self);
405417
let for_stmt_left = ForStatementLeft::from(target);
406-
self.parse_for_of_loop(span, r#await, for_stmt_left)
418+
self.parse_for_of_loop(span, parenthesis_opening_span, r#await, for_stmt_left)
407419
}
408-
_ => self.parse_for_loop(span, Some(ForStatementInit::from(init_expression)), r#await),
420+
_ => self.parse_for_loop(
421+
span,
422+
parenthesis_opening_span,
423+
Some(ForStatementInit::from(init_expression)),
424+
r#await,
425+
),
409426
}
410427
}
411428

412429
fn parse_variable_declaration_for_statement(
413430
&mut self,
414431
span: u32,
415432
start_span: u32,
433+
parenthesis_opening_span: Span,
416434
decl_kind: VariableDeclarationKind,
417435
r#await: bool,
418436
) -> Statement<'a> {
@@ -425,7 +443,7 @@ impl<'a> ParserImpl<'a> {
425443
)
426444
});
427445

428-
self.parse_any_for_loop(span, init_declaration, r#await)
446+
self.parse_any_for_loop(span, parenthesis_opening_span, init_declaration, r#await)
429447
}
430448

431449
pub(crate) fn is_using_declaration(&mut self) -> bool {
@@ -434,7 +452,12 @@ impl<'a> ParserImpl<'a> {
434452
(kind.is_binding_identifier() || kind == Kind::LCurly) && !token.is_on_new_line()
435453
}
436454

437-
fn parse_using_declaration_for_statement(&mut self, span: u32, r#await: bool) -> Statement<'a> {
455+
fn parse_using_declaration_for_statement(
456+
&mut self,
457+
span: u32,
458+
parenthesis_opening_span: Span,
459+
r#await: bool,
460+
) -> Statement<'a> {
438461
let using_decl = self.parse_using_declaration(StatementContext::For);
439462

440463
if matches!(self.cur_kind(), Kind::In) {
@@ -450,28 +473,32 @@ impl<'a> ParserImpl<'a> {
450473
}
451474

452475
let init_declaration = self.alloc(using_decl);
453-
self.parse_any_for_loop(span, init_declaration, r#await)
476+
self.parse_any_for_loop(span, parenthesis_opening_span, init_declaration, r#await)
454477
}
455478

456479
fn parse_any_for_loop(
457480
&mut self,
458481
span: u32,
482+
parenthesis_opening_span: Span,
459483
init_declaration: Box<'a, VariableDeclaration<'a>>,
460484
r#await: bool,
461485
) -> Statement<'a> {
462486
match self.cur_kind() {
463487
Kind::In => self.parse_for_in_loop(
464488
span,
489+
parenthesis_opening_span,
465490
r#await,
466491
ForStatementLeft::VariableDeclaration(init_declaration),
467492
),
468493
Kind::Of => self.parse_for_of_loop(
469494
span,
495+
parenthesis_opening_span,
470496
r#await,
471497
ForStatementLeft::VariableDeclaration(init_declaration),
472498
),
473499
_ => self.parse_for_loop(
474500
span,
501+
parenthesis_opening_span,
475502
Some(ForStatementInit::VariableDeclaration(init_declaration)),
476503
r#await,
477504
),
@@ -481,6 +508,7 @@ impl<'a> ParserImpl<'a> {
481508
fn parse_for_loop(
482509
&mut self,
483510
span: u32,
511+
parenthesis_opening_span: Span,
484512
init: Option<ForStatementInit<'a>>,
485513
r#await: bool,
486514
) -> Statement<'a> {
@@ -501,7 +529,7 @@ impl<'a> ParserImpl<'a> {
501529
} else {
502530
Some(self.context_add(Context::In, ParserImpl::parse_expr))
503531
};
504-
self.expect(Kind::RParen);
532+
self.expect_closing(Kind::RParen, parenthesis_opening_span);
505533
if r#await {
506534
self.error(diagnostics::for_await(self.end_span(span)));
507535
}
@@ -512,12 +540,13 @@ impl<'a> ParserImpl<'a> {
512540
fn parse_for_in_loop(
513541
&mut self,
514542
span: u32,
543+
parenthesis_opening_span: Span,
515544
r#await: bool,
516545
left: ForStatementLeft<'a>,
517546
) -> Statement<'a> {
518547
self.bump_any(); // bump `in`
519548
let right = self.parse_expr();
520-
self.expect(Kind::RParen);
549+
self.expect_closing(Kind::RParen, parenthesis_opening_span);
521550

522551
if r#await {
523552
self.error(diagnostics::for_await(self.end_span(span)));
@@ -531,12 +560,13 @@ impl<'a> ParserImpl<'a> {
531560
fn parse_for_of_loop(
532561
&mut self,
533562
span: u32,
563+
parenthesis_opening_span: Span,
534564
r#await: bool,
535565
left: ForStatementLeft<'a>,
536566
) -> Statement<'a> {
537567
self.bump_any(); // bump `of`
538568
let right = self.parse_assignment_expression_or_higher();
539-
self.expect(Kind::RParen);
569+
self.expect_closing(Kind::RParen, parenthesis_opening_span);
540570

541571
let body = self.parse_statement_list_item(StatementContext::For);
542572
let span = self.end_span(span);

tasks/coverage/snapshots/parser_babel.snap

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -385,8 +385,9 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2026
385385
× Expected `)` but found `of`
386386
╭─[babel/packages/babel-parser/test/fixtures/es2026/explicit-resource-management/valid-for-await-using-binding-escaped-of-of/input.mjs:1:29]
387387
1 │ for await (using \u006ff of of);
388-
· ─┬
389-
· ╰── `)` expected
388+
· ┬ ─┬
389+
· │ ╰── `)` expected
390+
· ╰── Opened here
390391
╰────
391392

392393
Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2026/explicit-resource-management/valid-for-using-binding-escaped-of-of/input.js
@@ -400,8 +401,9 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2026
400401
× Expected `)` but found `of`
401402
╭─[babel/packages/babel-parser/test/fixtures/es2026/explicit-resource-management/valid-for-using-binding-escaped-of-of/input.js:1:23]
402403
1 │ for (using o\u0066 of of);
403-
· ─┬
404-
· ╰── `)` expected
404+
· ┬ ─┬
405+
· │ ╰── `)` expected
406+
· ╰── Opened here
405407
╰────
406408

407409
Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2026/explicit-resource-management/valid-for-using-declaration-binding-of/input.js
@@ -3277,8 +3279,9 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
32773279
× Expected `)` but found `,`
32783280
╭─[babel/packages/babel-parser/test/fixtures/es2015/for-of/invalid-expr/input.js:1:16]
32793281
1 │ for (let x of y, z) {}
3280-
· ┬
3281-
· ╰── `)` expected
3282+
· ┬ ┬
3283+
· │ ╰── `)` expected
3284+
· ╰── Opened here
32823285
╰────
32833286

32843287
× The left-hand side of a `for...of` statement may not start with `let`
@@ -5823,8 +5826,9 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
58235826
× Expected `)` but found `;`
58245827
╭─[babel/packages/babel-parser/test/fixtures/es2017/async-functions/invalid-for-await-expression-init/input.js:1:23]
58255828
1 │ for (await o\u0066 [0];;);
5826-
· ┬
5827-
· ╰── `)` expected
5829+
· ┬ ┬
5830+
· │ ╰── `)` expected
5831+
· ╰── Opened here
58285832
╰────
58295833

58305834
× Async functions can only be declared at the top level or inside a block
@@ -9415,8 +9419,9 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
94159419
× Expected `)` but found `of`
94169420
╭─[babel/packages/babel-parser/test/fixtures/es2026/explicit-resource-management/invalid-for-await-using-binding-of-of/input.mjs:1:24]
94179421
1 │ for await (using of of of);
9418-
· ─┬
9419-
· ╰── `)` expected
9422+
· ┬ ─┬
9423+
· │ ╰── `)` expected
9424+
· ╰── Opened here
94209425
╰────
94219426

94229427
× The left-hand side of a for...in statement cannot be an using declaration.
@@ -9435,8 +9440,9 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
94359440
× Expected `)` but found `of`
94369441
╭─[babel/packages/babel-parser/test/fixtures/es2026/explicit-resource-management/invalid-for-using-binding-of-of/input.js:1:18]
94379442
1 │ for (using of of of);
9438-
· ─┬
9439-
· ╰── `)` expected
9443+
· ┬ ─┬
9444+
· │ ╰── `)` expected
9445+
· ╰── Opened here
94409446
╰────
94419447

94429448
× Unexpected token

0 commit comments

Comments
 (0)