diff --git a/crates/oxc_parser/src/cursor.rs b/crates/oxc_parser/src/cursor.rs index 8bf465ee439aa..0216002e49423 100644 --- a/crates/oxc_parser/src/cursor.rs +++ b/crates/oxc_parser/src/cursor.rs @@ -189,6 +189,21 @@ impl<'a> ParserImpl<'a> { self.advance(kind); } + #[inline] + pub(crate) fn expect_closing(&mut self, kind: Kind, opening_span: Span) { + if !self.at(kind) { + let range = self.cur_token().span(); + let error = diagnostics::expect_closing( + kind.to_str(), + self.cur_kind().to_str(), + range, + opening_span, + ); + self.set_fatal_error(error); + } + self.advance(kind); + } + #[inline] pub(crate) fn expect_conditional_alternative(&mut self, question_span: Span) { if !self.at(Kind::Colon) { @@ -382,6 +397,7 @@ impl<'a> ParserImpl<'a> { where F: Fn(&mut Self) -> T, { + let opening_span = self.cur_token().span(); self.expect(open); let mut list = self.ast.vec(); loop { @@ -394,7 +410,7 @@ impl<'a> ParserImpl<'a> { } list.push(f(self)); } - self.expect(close); + self.expect_closing(close, opening_span); list } @@ -407,6 +423,7 @@ impl<'a> ParserImpl<'a> { where F: Fn(&mut Self) -> Option, { + let opening_span = self.cur_token().span(); self.expect(open); let mut list = self.ast.vec(); loop { @@ -419,7 +436,7 @@ impl<'a> ParserImpl<'a> { break; } } - self.expect(close); + self.expect_closing(close, opening_span); list } diff --git a/crates/oxc_parser/src/diagnostics.rs b/crates/oxc_parser/src/diagnostics.rs index df4c628682c95..43050332baeb4 100644 --- a/crates/oxc_parser/src/diagnostics.rs +++ b/crates/oxc_parser/src/diagnostics.rs @@ -44,6 +44,21 @@ pub fn expect_token(x0: &str, x1: &str, span: Span) -> OxcDiagnostic { .with_label(span.label(format!("`{x0}` expected"))) } +#[cold] +pub fn expect_closing( + expected_closing: &str, + actual: &str, + span: Span, + opening_span: Span, +) -> OxcDiagnostic { + OxcDiagnostic::error(format!("Expected `{expected_closing}` but found `{actual}`")).with_labels( + [ + span.primary_label(format!("`{expected_closing}` expected")), + opening_span.label("Opened here"), + ], + ) +} + #[cold] pub fn expect_closing_or_separator( expected_closing: &str, diff --git a/crates/oxc_parser/src/js/function.rs b/crates/oxc_parser/src/js/function.rs index 59d861cb3491d..513f5decb8843 100644 --- a/crates/oxc_parser/src/js/function.rs +++ b/crates/oxc_parser/src/js/function.rs @@ -30,13 +30,14 @@ impl<'a> ParserImpl<'a> { pub(crate) fn parse_function_body(&mut self) -> Box<'a, FunctionBody<'a>> { let span = self.start_span(); + let opening_span = self.cur_token().span(); self.expect(Kind::LCurly); let (directives, statements) = self.context_add(Context::Return, |p| { p.parse_directives_and_statements(/* is_top_level */ false) }); - self.expect(Kind::RCurly); + self.expect_closing(Kind::RCurly, opening_span); self.ast.alloc_function_body(self.end_span(span), directives, statements) } diff --git a/tasks/coverage/snapshots/parser_babel.snap b/tasks/coverage/snapshots/parser_babel.snap index fedde20820b2e..9742e110ef03d 100644 --- a/tasks/coverage/snapshots/parser_babel.snap +++ b/tasks/coverage/snapshots/parser_babel.snap @@ -1695,6 +1695,8 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc × Expected `}` but found `EOF` ╭─[babel/packages/babel-parser/test/fixtures/core/uncategorised/345/input.js:1:2] 1 │ { + · ┬ + · ╰── Opened here ╰──── × Unexpected token @@ -1944,6 +1946,8 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc ╭─[babel/packages/babel-parser/test/fixtures/core/uncategorised/386/input.js:4:2] 3 │ 4 │ { + · ┬ + · ╰── Opened here ╰──── × Unexpected token @@ -4715,6 +4719,8 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc × Expected `}` but found `EOF` ╭─[babel/packages/babel-parser/test/fixtures/es2015/uncategorised/295/input.js:2:1] 1 │ switch (cond) { case 10: let a = 20; + · ┬ + · ╰── Opened here ╰──── × Cannot assign to 'eval' in strict mode @@ -10631,6 +10637,8 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc × Expected `}` but found `EOF` ╭─[babel/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0000/input.js:2:1] 1 │ { + · ┬ + · ╰── Opened here ╰──── × Unexpected token @@ -11119,6 +11127,8 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc × Expected `}` but found `EOF` ╭─[babel/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0069/input.js:2:1] 1 │ { + · ┬ + · ╰── Opened here ╰──── × Unexpected token @@ -12242,11 +12252,15 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc × Expected `}` but found `EOF` ╭─[babel/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0255/input.js:2:1] 1 │ { ; ; + · ┬ + · ╰── Opened here ╰──── × Expected `}` but found `EOF` ╭─[babel/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0256/input.js:2:1] 1 │ function t() { ; ; + · ┬ + · ╰── Opened here ╰──── × Expected a semicolon or an implicit semicolon after a statement, but found none @@ -12300,6 +12314,8 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc × Expected `}` but found `EOF` ╭─[babel/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0266/input.js:2:1] 1 │ class A { + · ┬ + · ╰── Opened here ╰──── × Expected `{` but found `;` @@ -14906,6 +14922,8 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc × Expected `}` but found `EOF` ╭─[babel/packages/babel-parser/test/fixtures/typescript/types-arrow-function/invalid-incomplete-object-like/input.ts:2:1] 1 │ type F = ({ + · ┬ + · ╰── Opened here ╰──── × Expected `]` but found `EOF` @@ -14916,6 +14934,8 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc × Expected `}` but found `EOF` ╭─[babel/packages/babel-parser/test/fixtures/typescript/types-arrow-function-babel-7/invalid-incomplete-object-like/input.ts:2:1] 1 │ type F = ({ + · ┬ + · ╰── Opened here ╰──── × Missing initializer in destructuring declaration diff --git a/tasks/coverage/snapshots/parser_typescript.snap b/tasks/coverage/snapshots/parser_typescript.snap index f2dbf5f85d14d..3353a273f58c9 100644 --- a/tasks/coverage/snapshots/parser_typescript.snap +++ b/tasks/coverage/snapshots/parser_typescript.snap @@ -8219,6 +8219,8 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/parser/ecmasc × Expected `}` but found `EOF` ╭─[typescript/tests/cases/compiler/exportInFunction.ts:2:16] 1 │ function f() { + · ┬ + · ╰── Opened here 2 │ export = 0; ╰──── @@ -9877,6 +9879,12 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/parser/ecmasc ╰──── × Expected `}` but found `EOF` + ╭─[typescript/tests/cases/compiler/missingCloseBrace.ts:1:22] + 1 │ function base_init() { + · ┬ + · ╰── Opened here + 2 │ { + ╰──── ╭─[typescript/tests/cases/compiler/missingCloseBrace.ts:8:6] 7 │ 8 │ } @@ -11535,6 +11543,8 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/parser/ecmasc × Expected `}` but found `EOF` ╭─[typescript/tests/cases/compiler/prettyContextNotDebugAssertion.ts:1:12] 1 │ if (true) { + · ┬ + · ╰── Opened here ╰──── × TS(2414): Class name cannot be 'any' @@ -21663,6 +21673,13 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/parser/ecmasc × Expected `}` but found `EOF` ╭─[typescript/tests/cases/conformance/interfaces/interfacesExtendingClasses/interfaceExtendingClass2.ts:15:6] + 9 │ + 10 │ interface I2 extends Foo { // error + · ┬ + · ╰── Opened here + 11 │ a: { + 12 │ toString: () => { + 13 │ return 1; 14 │ }; 15 │ } ╰──── @@ -22893,7 +22910,10 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/parser/ecmasc × Expected `}` but found `EOF` ╭─[typescript/tests/cases/conformance/parser/ecmascript5/ErrorRecovery/AccessibilityAfterStatic/parserAccessibilityAfterStatic6.ts:3:14] + 1 │ class Outer 2 │ { + · ┬ + · ╰── Opened here 3 │ static public ╰──── @@ -24232,6 +24252,8 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/parser/ecmasc × Expected `}` but found `EOF` ╭─[typescript/tests/cases/conformance/parser/ecmascript5/RegressionTests/parser512084.ts:1:12] 1 │ class foo { + · ┬ + · ╰── Opened here ╰──── × Expected `,` or `}` but found `;`