Skip to content

Commit 4decc1d

Browse files
committed
feat(parser): improve error message for missing block closing tokens (#15445)
Improves error message for imbalanced tokens for blocks. **Before** ``` × Expected `}` but found `EOF` ╭─[typescript/tests/cases/compiler/missingCloseBrace.ts:8:6] 7 │ 8 │ } ╰──── ``` **After** ``` × 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 │ } ╰──── ```
1 parent e1704a4 commit 4decc1d

File tree

5 files changed

+78
-3
lines changed

5 files changed

+78
-3
lines changed

crates/oxc_parser/src/cursor.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,21 @@ impl<'a> ParserImpl<'a> {
189189
self.advance(kind);
190190
}
191191

192+
#[inline]
193+
pub(crate) fn expect_closing(&mut self, kind: Kind, opening_span: Span) {
194+
if !self.at(kind) {
195+
let range = self.cur_token().span();
196+
let error = diagnostics::expect_closing(
197+
kind.to_str(),
198+
self.cur_kind().to_str(),
199+
range,
200+
opening_span,
201+
);
202+
self.set_fatal_error(error);
203+
}
204+
self.advance(kind);
205+
}
206+
192207
#[inline]
193208
pub(crate) fn expect_conditional_alternative(&mut self, question_span: Span) {
194209
if !self.at(Kind::Colon) {
@@ -382,6 +397,7 @@ impl<'a> ParserImpl<'a> {
382397
where
383398
F: Fn(&mut Self) -> T,
384399
{
400+
let opening_span = self.cur_token().span();
385401
self.expect(open);
386402
let mut list = self.ast.vec();
387403
loop {
@@ -394,7 +410,7 @@ impl<'a> ParserImpl<'a> {
394410
}
395411
list.push(f(self));
396412
}
397-
self.expect(close);
413+
self.expect_closing(close, opening_span);
398414
list
399415
}
400416

@@ -407,6 +423,7 @@ impl<'a> ParserImpl<'a> {
407423
where
408424
F: Fn(&mut Self) -> Option<T>,
409425
{
426+
let opening_span = self.cur_token().span();
410427
self.expect(open);
411428
let mut list = self.ast.vec();
412429
loop {
@@ -419,7 +436,7 @@ impl<'a> ParserImpl<'a> {
419436
break;
420437
}
421438
}
422-
self.expect(close);
439+
self.expect_closing(close, opening_span);
423440
list
424441
}
425442

crates/oxc_parser/src/diagnostics.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,21 @@ pub fn expect_token(x0: &str, x1: &str, span: Span) -> OxcDiagnostic {
4444
.with_label(span.label(format!("`{x0}` expected")))
4545
}
4646

47+
#[cold]
48+
pub fn expect_closing(
49+
expected_closing: &str,
50+
actual: &str,
51+
span: Span,
52+
opening_span: Span,
53+
) -> OxcDiagnostic {
54+
OxcDiagnostic::error(format!("Expected `{expected_closing}` but found `{actual}`")).with_labels(
55+
[
56+
span.primary_label(format!("`{expected_closing}` expected")),
57+
opening_span.label("Opened here"),
58+
],
59+
)
60+
}
61+
4762
#[cold]
4863
pub fn expect_closing_or_separator(
4964
expected_closing: &str,

crates/oxc_parser/src/js/function.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,14 @@ impl<'a> ParserImpl<'a> {
3030

3131
pub(crate) fn parse_function_body(&mut self) -> Box<'a, FunctionBody<'a>> {
3232
let span = self.start_span();
33+
let opening_span = self.cur_token().span();
3334
self.expect(Kind::LCurly);
3435

3536
let (directives, statements) = self.context_add(Context::Return, |p| {
3637
p.parse_directives_and_statements(/* is_top_level */ false)
3738
});
3839

39-
self.expect(Kind::RCurly);
40+
self.expect_closing(Kind::RCurly, opening_span);
4041
self.ast.alloc_function_body(self.end_span(span), directives, statements)
4142
}
4243

tasks/coverage/snapshots/parser_babel.snap

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1695,6 +1695,8 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
16951695
× Expected `}` but found `EOF`
16961696
╭─[babel/packages/babel-parser/test/fixtures/core/uncategorised/345/input.js:1:2]
16971697
1 │ {
1698+
· ┬
1699+
· ╰── Opened here
16981700
╰────
16991701

17001702
× Unexpected token
@@ -1944,6 +1946,8 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
19441946
╭─[babel/packages/babel-parser/test/fixtures/core/uncategorised/386/input.js:4:2]
19451947
3 │
19461948
4 │ {
1949+
· ┬
1950+
· ╰── Opened here
19471951
╰────
19481952

19491953
× Unexpected token
@@ -4715,6 +4719,8 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
47154719
× Expected `}` but found `EOF`
47164720
╭─[babel/packages/babel-parser/test/fixtures/es2015/uncategorised/295/input.js:2:1]
47174721
1 │ switch (cond) { case 10: let a = 20;
4722+
· ┬
4723+
· ╰── Opened here
47184724
╰────
47194725

47204726
× Cannot assign to 'eval' in strict mode
@@ -10631,6 +10637,8 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
1063110637
× Expected `}` but found `EOF`
1063210638
╭─[babel/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0000/input.js:2:1]
1063310639
1 │ {
10640+
· ┬
10641+
· ╰── Opened here
1063410642
╰────
1063510643

1063610644
× Unexpected token
@@ -11119,6 +11127,8 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
1111911127
× Expected `}` but found `EOF`
1112011128
╭─[babel/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0069/input.js:2:1]
1112111129
1 │ {
11130+
· ┬
11131+
· ╰── Opened here
1112211132
╰────
1112311133

1112411134
× Unexpected token
@@ -12242,11 +12252,15 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
1224212252
× Expected `}` but found `EOF`
1224312253
╭─[babel/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0255/input.js:2:1]
1224412254
1 │ { ; ;
12255+
· ┬
12256+
· ╰── Opened here
1224512257
╰────
1224612258

1224712259
× Expected `}` but found `EOF`
1224812260
╭─[babel/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0256/input.js:2:1]
1224912261
1 │ function t() { ; ;
12262+
· ┬
12263+
· ╰── Opened here
1225012264
╰────
1225112265

1225212266
× 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
1230012314
× Expected `}` but found `EOF`
1230112315
╭─[babel/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0266/input.js:2:1]
1230212316
1 │ class A {
12317+
· ┬
12318+
· ╰── Opened here
1230312319
╰────
1230412320

1230512321
× Expected `{` but found `;`
@@ -14906,6 +14922,8 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
1490614922
× Expected `}` but found `EOF`
1490714923
╭─[babel/packages/babel-parser/test/fixtures/typescript/types-arrow-function/invalid-incomplete-object-like/input.ts:2:1]
1490814924
1 │ type F = ({
14925+
· ┬
14926+
· ╰── Opened here
1490914927
╰────
1491014928

1491114929
× Expected `]` but found `EOF`
@@ -14916,6 +14934,8 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
1491614934
× Expected `}` but found `EOF`
1491714935
╭─[babel/packages/babel-parser/test/fixtures/typescript/types-arrow-function-babel-7/invalid-incomplete-object-like/input.ts:2:1]
1491814936
1 │ type F = ({
14937+
· ┬
14938+
· ╰── Opened here
1491914939
╰────
1492014940

1492114941
× Missing initializer in destructuring declaration

tasks/coverage/snapshots/parser_typescript.snap

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8219,6 +8219,8 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/parser/ecmasc
82198219
× Expected `}` but found `EOF`
82208220
╭─[typescript/tests/cases/compiler/exportInFunction.ts:2:16]
82218221
1 │ function f() {
8222+
· ┬
8223+
· ╰── Opened here
82228224
2 │ export = 0;
82238225
╰────
82248226

@@ -9877,6 +9879,12 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/parser/ecmasc
98779879
╰────
98789880

98799881
× Expected `}` but found `EOF`
9882+
╭─[typescript/tests/cases/compiler/missingCloseBrace.ts:1:22]
9883+
1 │ function base_init() {
9884+
· ┬
9885+
· ╰── Opened here
9886+
2 │ {
9887+
╰────
98809888
╭─[typescript/tests/cases/compiler/missingCloseBrace.ts:8:6]
98819889
7 │
98829890
8 │ }
@@ -11535,6 +11543,8 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/parser/ecmasc
1153511543
× Expected `}` but found `EOF`
1153611544
╭─[typescript/tests/cases/compiler/prettyContextNotDebugAssertion.ts:1:12]
1153711545
1 │ if (true) {
11546+
· ┬
11547+
· ╰── Opened here
1153811548
╰────
1153911549

1154011550
× TS(2414): Class name cannot be 'any'
@@ -21663,6 +21673,13 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/parser/ecmasc
2166321673

2166421674
× Expected `}` but found `EOF`
2166521675
╭─[typescript/tests/cases/conformance/interfaces/interfacesExtendingClasses/interfaceExtendingClass2.ts:15:6]
21676+
9 │
21677+
10 │ interface I2 extends Foo { // error
21678+
· ┬
21679+
· ╰── Opened here
21680+
11 │ a: {
21681+
12 │ toString: () => {
21682+
13 │ return 1;
2166621683
14 │ };
2166721684
15 │ }
2166821685
╰────
@@ -22893,7 +22910,10 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/parser/ecmasc
2289322910

2289422911
× Expected `}` but found `EOF`
2289522912
╭─[typescript/tests/cases/conformance/parser/ecmascript5/ErrorRecovery/AccessibilityAfterStatic/parserAccessibilityAfterStatic6.ts:3:14]
22913+
1 │ class Outer
2289622914
2 │ {
22915+
· ┬
22916+
· ╰── Opened here
2289722917
3 │ static public
2289822918
╰────
2289922919

@@ -24232,6 +24252,8 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/parser/ecmasc
2423224252
× Expected `}` but found `EOF`
2423324253
╭─[typescript/tests/cases/conformance/parser/ecmascript5/RegressionTests/parser512084.ts:1:12]
2423424254
1 │ class foo {
24255+
· ┬
24256+
· ╰── Opened here
2423524257
╰────
2423624258

2423724259
× Expected `,` or `}` but found `;`

0 commit comments

Comments
 (0)