Skip to content

Commit 682dca2

Browse files
committed
feat(parser): add more helps to parser errors (#15186)
Added more helps to parser errors to make it easier to understand how the error can be resolved.
1 parent 9d568eb commit 682dca2

File tree

16 files changed

+1081
-457
lines changed

16 files changed

+1081
-457
lines changed

crates/oxc_ast/src/ast/literal.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ pub struct RegExpPattern<'a> {
178178
pub pattern: Option<Box<'a, Pattern<'a>>>,
179179
}
180180

181+
/// The list of valid regular expression flags.
182+
pub const REGEXP_FLAGS_LIST: &str = "gimsuydv";
183+
181184
bitflags! {
182185
/// Regular expression flags.
183186
///

crates/oxc_linter/src/snapshots/eslint_no_nonoctal_decimal_escape.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ source: crates/oxc_linter/src/tester.rs
404404
1var foo = '8'\n bar = '\\9'
405405
· ▲
406406
╰────
407-
help: Try insert a semicolon here
407+
help: Try inserting a semicolon here
408408

409409
eslint(no-nonoctal-decimal-escape): Don't use '\8' escape sequence.
410410
╭─[no_nonoctal_decimal_escape.tsx:1:5]

crates/oxc_linter/src/snapshots/jsx_a11y_label_has_associated_control.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ source: crates/oxc_linter/src/tester.rs
103103
1 │ <label><span><span><span>A label<input /></span></span></span></label>'
104104
·
105105
╰────
106-
help: Try insert a semicolon here
106+
help: Try inserting a semicolon here
107107

108108
eslint-plugin-jsx-a11y(label-has-associated-control): A form label must be associated with a control.
109109
╭─[label_has_associated_control.tsx:1:1]

crates/oxc_linter/src/snapshots/react_button_has_type.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ source: crates/oxc_linter/src/tester.rs
9797
1 │ button type/>
9898
· ▲
9999
╰────
100-
help: Try insert a semicolon here
100+
help: Try inserting a semicolon here
101101
102102
⚠ eslint-plugin-react(button-has-type): `button` elements must have a valid `type` attribute.
103103
╭─[button_has_type.tsx:1:9]

crates/oxc_parser/src/diagnostics.rs

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::borrow::Cow;
22

3+
use oxc_ast::ast::REGEXP_FLAGS_LIST;
34
use oxc_diagnostics::OxcDiagnostic;
45
use oxc_span::Span;
56

@@ -77,12 +78,14 @@ pub fn unterminated_string(span: Span) -> OxcDiagnostic {
7778
pub fn reg_exp_flag(x0: char, span1: Span) -> OxcDiagnostic {
7879
OxcDiagnostic::error(format!("Unexpected flag {x0} in regular expression literal"))
7980
.with_label(span1)
81+
.with_help(format!("The allowed flags are `{REGEXP_FLAGS_LIST}`"))
8082
}
8183

8284
#[cold]
8385
pub fn reg_exp_flag_twice(x0: char, span1: Span) -> OxcDiagnostic {
8486
OxcDiagnostic::error(format!("Flag {x0} is mentioned twice in regular expression literal"))
8587
.with_label(span1)
88+
.with_help("Remove the duplicated flag here")
8689
}
8790

8891
#[cold]
@@ -116,23 +119,29 @@ pub fn auto_semicolon_insertion(span: Span) -> OxcDiagnostic {
116119
OxcDiagnostic::error(
117120
"Expected a semicolon or an implicit semicolon after a statement, but found none",
118121
)
119-
.with_help("Try insert a semicolon here")
122+
.with_help("Try inserting a semicolon here")
120123
.with_label(span)
121124
}
122125

123126
#[cold]
124127
pub fn lineterminator_before_arrow(span: Span) -> OxcDiagnostic {
125-
OxcDiagnostic::error("Line terminator not permitted before arrow").with_label(span)
128+
OxcDiagnostic::error("Line terminator not permitted before arrow")
129+
.with_label(span)
130+
.with_help("Remove the line break before here")
126131
}
127132

128133
#[cold]
129134
pub fn invalid_destrucuring_declaration(span: Span) -> OxcDiagnostic {
130-
OxcDiagnostic::error("Missing initializer in destructuring declaration").with_label(span)
135+
OxcDiagnostic::error("Missing initializer in destructuring declaration")
136+
.with_label(span)
137+
.with_help("Add an initializer (e.g. ` = undefined`) here")
131138
}
132139

133140
#[cold]
134-
pub fn missinginitializer_in_const(span: Span) -> OxcDiagnostic {
135-
OxcDiagnostic::error("Missing initializer in const declaration").with_label(span)
141+
pub fn missing_initializer_in_const(span: Span) -> OxcDiagnostic {
142+
OxcDiagnostic::error("Missing initializer in const declaration")
143+
.with_label(span)
144+
.with_help("Add an initializer (e.g. ` = undefined`) here")
136145
}
137146

138147
#[cold]
@@ -160,12 +169,14 @@ pub fn await_expression(span: Span) -> OxcDiagnostic {
160169
"`await` is only allowed within async functions and at the top levels of modules",
161170
)
162171
.with_label(span)
172+
.with_help("Either remove this `await` or add the `async` keyword to the enclosing function")
163173
}
164174

165175
#[cold]
166176
pub fn yield_expression(span: Span) -> OxcDiagnostic {
167177
OxcDiagnostic::error("A 'yield' expression is only allowed in a generator body.")
168178
.with_label(span)
179+
.with_help("Either remove this `yield` or change the enclosing function to a generator function (`function*`)")
169180
}
170181

171182
#[cold]
@@ -191,14 +202,21 @@ pub fn extends_clause_already_seen(span: Span) -> OxcDiagnostic {
191202

192203
// 'extends' clause must precede 'implements' clause. ts(1173)
193204
#[cold]
194-
pub fn extends_clause_must_precede_implements(span: Span) -> OxcDiagnostic {
195-
ts_error("1173", "'extends' clause must precede 'implements' clause").with_label(span)
205+
pub fn extends_clause_must_precede_implements(span: Span, implements_span: Span) -> OxcDiagnostic {
206+
ts_error("1173", "'extends' clause must precede 'implements' clause")
207+
.with_labels([
208+
implements_span.label("'implements' clause found here"),
209+
span.primary_label("'extends' clause found here"),
210+
])
211+
.with_help("Move the 'extends' clause before the 'implements' clause")
196212
}
197213

198214
// 'implements' clause already seen. ts(1175)
199215
#[cold]
200-
pub fn implements_clause_already_seen(span: Span) -> OxcDiagnostic {
201-
ts_error("1175", "'implements' clause already seen").with_label(span)
216+
pub fn implements_clause_already_seen(span: Span, seen_span: Span) -> OxcDiagnostic {
217+
ts_error("1175", "'implements' clause already seen")
218+
.with_labels([seen_span, span])
219+
.with_help("Merge the two 'implements' clauses into one by a ','")
202220
}
203221

204222
#[cold]
@@ -220,6 +238,7 @@ pub fn spread_last_element(span: Span) -> OxcDiagnostic {
220238
pub fn rest_element_trailing_comma(span: Span) -> OxcDiagnostic {
221239
OxcDiagnostic::error("A rest parameter or binding pattern may not have a trailing comma.")
222240
.with_label(span)
241+
.with_help("Remove the trailing comma here")
223242
}
224243

225244
#[cold]
@@ -231,7 +250,9 @@ pub fn invalid_binding_rest_element(span: Span) -> OxcDiagnostic {
231250

232251
#[cold]
233252
pub fn a_rest_parameter_cannot_be_optional(span: Span) -> OxcDiagnostic {
234-
OxcDiagnostic::error("A rest parameter cannot be optional").with_label(span)
253+
OxcDiagnostic::error("A rest parameter cannot be optional")
254+
.with_label(span)
255+
.with_help("Remove this `?`. The default value is an empty array")
235256
}
236257

237258
#[cold]
@@ -268,12 +289,14 @@ pub fn decorator_optional(span: Span) -> OxcDiagnostic {
268289
pub fn for_loop_async_of(span: Span) -> OxcDiagnostic {
269290
OxcDiagnostic::error("The left-hand side of a `for...of` statement may not be `async`")
270291
.with_label(span)
292+
.with_help("Did you mean to use a for await...of statement?")
271293
}
272294

273295
#[cold]
274296
pub fn for_await(span: Span) -> OxcDiagnostic {
275297
OxcDiagnostic::error("await can only be used in conjunction with `for...of` statements")
276298
.with_label(span)
299+
.with_help("Did you mean to use a for...of statement?")
277300
}
278301

279302
#[cold]
@@ -288,7 +311,8 @@ pub fn private_name_constructor(span: Span) -> OxcDiagnostic {
288311

289312
#[cold]
290313
pub fn static_prototype(span: Span) -> OxcDiagnostic {
291-
OxcDiagnostic::error("Classes may not have a static property named prototype").with_label(span)
314+
OxcDiagnostic::error("Classes may not have a static property named 'prototype'")
315+
.with_label(span)
292316
}
293317

294318
#[cold]
@@ -303,7 +327,9 @@ pub fn constructor_async(span: Span) -> OxcDiagnostic {
303327

304328
#[cold]
305329
pub fn optional_accessor_property(span: Span) -> OxcDiagnostic {
306-
ts_error("1276", "An 'accessor' property cannot be declared optional.").with_label(span)
330+
ts_error("1276", "An 'accessor' property cannot be declared optional.")
331+
.with_label(span)
332+
.with_help("Remove this `?`")
307333
}
308334

309335
#[cold]
@@ -438,7 +464,9 @@ pub fn expect_function_name(span: Span) -> OxcDiagnostic {
438464

439465
#[cold]
440466
pub fn expect_catch_finally(span: Span) -> OxcDiagnostic {
441-
OxcDiagnostic::error("Missing catch or finally clause").with_label(span)
467+
OxcDiagnostic::error("Missing catch or finally clause")
468+
.with_label(span)
469+
.with_help("Either unwrap this try block or add catch / finally clause")
442470
}
443471

444472
#[cold]
@@ -449,7 +477,7 @@ pub fn v8_intrinsic_spread_elem(span: Span) -> OxcDiagnostic {
449477

450478
#[cold]
451479
pub fn a_set_accessor_cannot_have_a_return_type_annotation(span: Span) -> OxcDiagnostic {
452-
ts_error("1095", " A 'set' accessor cannot have a return type annotation.").with_label(span)
480+
ts_error("1095", "A 'set' accessor cannot have a return type annotation.").with_label(span)
453481
}
454482

455483
#[cold]
@@ -469,6 +497,7 @@ pub fn await_using_declaration_not_allowed_in_for_in_statement(span: Span) -> Ox
469497
"The left-hand side of a for...in statement cannot be an await using declaration.",
470498
)
471499
.with_label(span)
500+
.with_help("Did you mean to use a for...of statement?")
472501
}
473502

474503
#[cold]
@@ -477,11 +506,14 @@ pub fn using_declaration_not_allowed_in_for_in_statement(span: Span) -> OxcDiagn
477506
"The left-hand side of a for...in statement cannot be an using declaration.",
478507
)
479508
.with_label(span)
509+
.with_help("Did you mean to use a for...of statement?")
480510
}
481511

482512
#[cold]
483513
pub fn using_declarations_must_be_initialized(span: Span) -> OxcDiagnostic {
484-
OxcDiagnostic::error("Using declarations must have an initializer.").with_label(span)
514+
OxcDiagnostic::error("Using declarations must have an initializer.")
515+
.with_label(span)
516+
.with_help("Add an initializer (e.g. ` = undefined`) here")
485517
}
486518

487519
#[cold]
@@ -638,13 +670,15 @@ pub fn accessibility_modifier_on_private_property(modifier: &Modifier) -> OxcDia
638670
#[cold]
639671
pub fn type_modifier_on_named_type_import(span: Span) -> OxcDiagnostic {
640672
ts_error("2206", "The 'type' modifier cannot be used on a named import when 'import type' is used on its import statement.")
641-
.with_label(span)
673+
.with_label(span)
674+
.with_help("Remove this 'type' modifier")
642675
}
643676

644677
#[cold]
645678
pub fn type_modifier_on_named_type_export(span: Span) -> OxcDiagnostic {
646679
ts_error("2207", "The 'type' modifier cannot be used on a named export when 'export type' is used on its export statement.")
647680
.with_label(span)
681+
.with_help("Remove this 'type' modifier")
648682
}
649683

650684
#[cold]
@@ -737,6 +771,7 @@ pub fn decorators_are_not_valid_here(span: Span) -> OxcDiagnostic {
737771
pub fn decorator_on_overload(span: Span) -> OxcDiagnostic {
738772
ts_error("1249", "A decorator can only decorate a method implementation, not an overload.")
739773
.with_label(span)
774+
.with_help("Move this after all the overloads")
740775
}
741776

742777
#[cold]
@@ -797,11 +832,18 @@ pub fn reg_exp_flag_u_and_v(span: Span) -> OxcDiagnostic {
797832
"The 'u' and 'v' regular expression flags cannot be enabled at the same time",
798833
)
799834
.with_label(span)
835+
.with_help("v flag enables additional syntax over u flag")
800836
}
801837

802838
#[cold]
803-
pub fn setter_with_parameters(span: Span) -> OxcDiagnostic {
804-
OxcDiagnostic::error("A 'set' accessor must have exactly one parameter.").with_label(span)
839+
pub fn setter_with_parameters(span: Span, parameters_count: usize) -> OxcDiagnostic {
840+
OxcDiagnostic::error("A 'set' accessor must have exactly one parameter.")
841+
.with_label(span)
842+
.with_help(if parameters_count == 0 {
843+
"Add a parameter here"
844+
} else {
845+
"Remove parameters except the first one here"
846+
})
805847
}
806848

807849
#[cold]
@@ -815,7 +857,9 @@ pub fn setter_with_assignment_pattern(span: Span) -> OxcDiagnostic {
815857

816858
#[cold]
817859
pub fn getter_parameters(span: Span) -> OxcDiagnostic {
818-
OxcDiagnostic::error("A 'get' accessor must not have any formal parameters.").with_label(span)
860+
OxcDiagnostic::error("A 'get' accessor must not have any formal parameters.")
861+
.with_label(span)
862+
.with_help("Remove these parameters here")
819863
}
820864

821865
#[cold]

crates/oxc_parser/src/js/class.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,17 +127,19 @@ impl<'a> ParserImpl<'a> {
127127
self.error(diagnostics::extends_clause_already_seen(
128128
self.cur_token().span(),
129129
));
130-
} else if implements.is_some() {
130+
} else if let Some((implements_span, _)) = implements {
131131
self.error(diagnostics::extends_clause_must_precede_implements(
132132
self.cur_token().span(),
133+
implements_span,
133134
));
134135
}
135136
extends = Some(self.parse_extends_clause());
136137
}
137138
Kind::Implements => {
138-
if implements.is_some() {
139+
if let Some((implements_span, _)) = implements {
139140
self.error(diagnostics::implements_clause_already_seen(
140141
self.cur_token().span(),
142+
implements_span,
141143
));
142144
}
143145
let implements_kw_span = self.cur_token().span();
@@ -651,7 +653,10 @@ impl<'a> ParserImpl<'a> {
651653
if let Some(rest) = &function.params.rest {
652654
self.error(diagnostics::setter_with_rest_parameter(rest.span));
653655
} else if function.params.parameters_count() != 1 {
654-
self.error(diagnostics::setter_with_parameters(function.params.span));
656+
self.error(diagnostics::setter_with_parameters(
657+
function.params.span,
658+
function.params.parameters_count(),
659+
));
655660
} else if self.is_ts
656661
&& function.params.items.first().unwrap().pattern.kind.is_assignment_pattern()
657662
{

crates/oxc_parser/src/js/declaration.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ impl<'a> ParserImpl<'a> {
150150
self.error(diagnostics::invalid_destrucuring_declaration(decl.id.span()));
151151
} else if decl.kind == VariableDeclarationKind::Const {
152152
// It is a Syntax Error if Initializer is not present and IsConstantDeclaration of the LexicalDeclaration containing this LexicalBinding is true.
153-
self.error(diagnostics::missinginitializer_in_const(decl.id.span()));
153+
self.error(diagnostics::missing_initializer_in_const(decl.id.span()));
154154
}
155155
}
156156
}

crates/oxc_semantic/src/checker/javascript.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,9 @@ pub fn check_binding_identifier(ident: &BindingIdentifier, ctx: &SemanticBuilder
204204
}
205205

206206
fn unexpected_arguments(x0: &str, span1: Span) -> OxcDiagnostic {
207-
OxcDiagnostic::error(format!("'arguments' is not allowed in {x0}")).with_label(span1)
207+
OxcDiagnostic::error(format!("'arguments' is not allowed in {x0}"))
208+
.with_label(span1)
209+
.with_help("Assign the 'arguments' variable to a temporary variable outside")
208210
}
209211

210212
pub fn check_identifier_reference(ident: &IdentifierReference, ctx: &SemanticBuilder<'_>) {
@@ -372,6 +374,9 @@ fn illegal_use_strict(span: Span) -> OxcDiagnostic {
372374
"Illegal 'use strict' directive in function with non-simple parameter list",
373375
)
374376
.with_label(span)
377+
.with_help(
378+
"Wrap this function with an IIFE with a 'use strict' directive that returns this function",
379+
)
375380
}
376381

377382
// It is a Syntax Error if FunctionBodyContainsUseStrict of AsyncFunctionBody is true and IsSimpleParameterList of FormalParameters is false.
@@ -442,8 +447,8 @@ pub fn check_module_declaration(decl: &ModuleDeclarationKind, ctx: &SemanticBuil
442447

443448
fn new_target(span: Span) -> OxcDiagnostic {
444449
OxcDiagnostic::error("Unexpected new.target expression")
445-
.with_help("new.target is only allowed in constructors and functions invoked using thew `new` operator")
446-
.with_label(span)
450+
.with_help("new.target is only allowed in constructors and functions invoked using the `new` operator")
451+
.with_label(span)
447452
}
448453

449454
fn import_meta(span: Span) -> OxcDiagnostic {

napi/parser/test/parse.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -836,9 +836,9 @@ describe('UTF-16 span', () => {
836836
1 | "🤨";asdf asdf
837837
: ^
838838
\`----
839-
help: Try insert a semicolon here
839+
help: Try inserting a semicolon here
840840
",
841-
"helpMessage": "Try insert a semicolon here",
841+
"helpMessage": "Try inserting a semicolon here",
842842
"labels": [
843843
{
844844
"end": 9,
@@ -861,7 +861,7 @@ describe('error', () => {
861861
expect(ret.errors.length).toBe(1);
862862
delete ret.errors[0].codeframe;
863863
expect(ret.errors[0]).toStrictEqual({
864-
helpMessage: 'Try insert a semicolon here',
864+
helpMessage: 'Try inserting a semicolon here',
865865
labels: [
866866
{
867867
end: 4,

tasks/coverage/snapshots/formatter_typescript.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Unexpected token
1212
Expect to Parse: tasks/coverage/typescript/tests/cases/compiler/sourceMapValidationDecorators.ts
1313
Unexpected token
1414
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/classes/propertyMemberDeclarations/staticPropertyNameConflicts.ts
15-
Classes may not have a static property named prototypeClasses may not have a static property named prototypeClasses may not have a static property named prototypeClasses may not have a static property named prototypeClasses may not have a static property named prototypeClasses may not have a static property named prototype
15+
Classes may not have a static property named 'prototype'Classes may not have a static property named 'prototype'Classes may not have a static property named 'prototype'Classes may not have a static property named 'prototype'Classes may not have a static property named 'prototype'Classes may not have a static property named 'prototype'
1616
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/es2019/importMeta/importMeta.ts
1717
The only valid meta property for import is import.meta
1818
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/expressions/elementAccess/letIdentifierInElementAccess01.ts

0 commit comments

Comments
 (0)