Skip to content

Commit 83f9de7

Browse files
committed
feat(parser): show allowed modifiers in invalid modifier error messages
1 parent aa2a4cb commit 83f9de7

File tree

12 files changed

+397
-15
lines changed

12 files changed

+397
-15
lines changed

crates/oxc_parser/src/diagnostics.rs

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,27 @@ use oxc_ast::ast::REGEXP_FLAGS_LIST;
44
use oxc_diagnostics::OxcDiagnostic;
55
use oxc_span::Span;
66

7-
use crate::modifiers::{Modifier, ModifierKind};
7+
use crate::modifiers::{Modifier, ModifierFlags, ModifierKind};
8+
9+
trait DiagnosticExt {
10+
fn with_allowed_modifier_help(self, allowed: Option<ModifierFlags>) -> Self;
11+
}
12+
13+
impl DiagnosticExt for OxcDiagnostic {
14+
fn with_allowed_modifier_help(self, allowed: Option<ModifierFlags>) -> Self {
15+
if let Some(allowed) = allowed {
16+
if allowed.is_empty() {
17+
self.with_help("No modifiers are allowed here.")
18+
} else if allowed.iter().count() == 1 {
19+
self.with_help(format!("Only '{allowed}' modifier is allowed here."))
20+
} else {
21+
self.with_help(format!("Allowed modifiers are: {allowed}"))
22+
}
23+
} else {
24+
self
25+
}
26+
}
27+
}
828

929
#[inline]
1030
fn ts_error<C, M>(code: C, message: M) -> OxcDiagnostic
@@ -622,14 +642,19 @@ pub fn import_requires_a_specifier(span: Span) -> OxcDiagnostic {
622642
}
623643

624644
#[cold]
625-
pub fn modifier_cannot_be_used_here(modifier: &Modifier) -> OxcDiagnostic {
645+
pub fn modifier_cannot_be_used_here(
646+
modifier: &Modifier,
647+
allowed: Option<ModifierFlags>,
648+
) -> OxcDiagnostic {
626649
OxcDiagnostic::error(format!("'{}' modifier cannot be used here.", modifier.kind))
627650
.with_label(modifier.span)
651+
.with_allowed_modifier_help(allowed)
628652
}
629653

630654
#[cold]
631655
pub fn modifier_only_on_property_declaration_or_index_signature(
632656
modifier: &Modifier,
657+
allowed: Option<ModifierFlags>,
633658
) -> OxcDiagnostic {
634659
ts_error(
635660
"1024",
@@ -639,6 +664,7 @@ pub fn modifier_only_on_property_declaration_or_index_signature(
639664
),
640665
)
641666
.with_label(modifier.span)
667+
.with_allowed_modifier_help(allowed)
642668
}
643669

644670
#[cold]
@@ -667,23 +693,35 @@ pub fn modifier_already_seen(modifier: &Modifier) -> OxcDiagnostic {
667693
.with_help("Remove the duplicate modifier.")
668694
}
669695

670-
pub fn cannot_appear_on_class_elements(modifier: &Modifier) -> OxcDiagnostic {
696+
pub fn cannot_appear_on_class_elements(
697+
modifier: &Modifier,
698+
allowed: Option<ModifierFlags>,
699+
) -> OxcDiagnostic {
671700
ts_error(
672701
"1031",
673702
format!("'{}' modifier cannot appear on class elements of this kind.", modifier.kind),
674703
)
675704
.with_label(modifier.span)
705+
.with_allowed_modifier_help(allowed)
676706
}
677707

678-
pub fn cannot_appear_on_a_type_member(modifier: &Modifier) -> OxcDiagnostic {
708+
pub fn cannot_appear_on_a_type_member(
709+
modifier: &Modifier,
710+
allowed: Option<ModifierFlags>,
711+
) -> OxcDiagnostic {
679712
ts_error("1070", format!("'{}' modifier cannot appear on a type member.", modifier.kind))
680713
.with_label(modifier.span)
714+
.with_allowed_modifier_help(allowed)
681715
}
682716

683717
#[cold]
684-
pub fn cannot_appear_on_a_type_parameter(modifier: &Modifier) -> OxcDiagnostic {
718+
pub fn cannot_appear_on_a_type_parameter(
719+
modifier: &Modifier,
720+
allowed: Option<ModifierFlags>,
721+
) -> OxcDiagnostic {
685722
ts_error("1273", format!("'{}' modifier cannot be used on a type parameter.", modifier.kind))
686723
.with_label(modifier.span)
724+
.with_allowed_modifier_help(allowed)
687725
}
688726

689727
#[cold]
@@ -695,22 +733,31 @@ pub fn can_only_appear_on_a_type_parameter_of_a_class_interface_or_type_alias(
695733
.with_label(span)
696734
}
697735

698-
pub fn cannot_appear_on_a_parameter(modifier: &Modifier) -> OxcDiagnostic {
736+
pub fn cannot_appear_on_a_parameter(
737+
modifier: &Modifier,
738+
allowed: Option<ModifierFlags>,
739+
) -> OxcDiagnostic {
699740
ts_error("1090", format!("'{}' modifier cannot appear on a parameter.", modifier.kind))
700741
.with_label(modifier.span)
742+
.with_allowed_modifier_help(allowed)
701743
}
702744

703-
pub fn cannot_appear_on_an_index_signature(modifier: &Modifier) -> OxcDiagnostic {
745+
pub fn cannot_appear_on_an_index_signature(
746+
modifier: &Modifier,
747+
allowed: Option<ModifierFlags>,
748+
) -> OxcDiagnostic {
704749
ts_error("1071", format!("'{}' modifier cannot appear on an index signature.", modifier.kind))
705750
.with_label(modifier.span)
751+
.with_allowed_modifier_help(allowed)
706752
}
707753

708-
pub fn accessor_modifier(modifier: &Modifier) -> OxcDiagnostic {
754+
pub fn accessor_modifier(modifier: &Modifier, allowed: Option<ModifierFlags>) -> OxcDiagnostic {
709755
ts_error(
710756
"1243",
711757
format!("'accessor' modifier cannot be used with '{}' modifier.", modifier.kind),
712758
)
713759
.with_label(modifier.span)
760+
.with_allowed_modifier_help(allowed.map(|a| a - ModifierFlags::ACCESSOR))
714761
}
715762

716763
#[cold]
@@ -720,7 +767,10 @@ pub fn readonly_in_array_or_tuple_type(span: Span) -> OxcDiagnostic {
720767
}
721768

722769
#[cold]
723-
pub fn accessibility_modifier_on_private_property(modifier: &Modifier) -> OxcDiagnostic {
770+
pub fn accessibility_modifier_on_private_property(
771+
modifier: &Modifier,
772+
_allowed: Option<ModifierFlags>,
773+
) -> OxcDiagnostic {
724774
ts_error("18010", "An accessibility modifier cannot be used with a private identifier.")
725775
.with_label(modifier.span)
726776
.with_help("Private identifiers are enforced at runtime, while accessibility modifiers only affect type checking, so using both is redundant.")
@@ -863,9 +913,13 @@ pub fn rest_after_tuple_member_name(span: Span) -> OxcDiagnostic {
863913
}
864914

865915
#[cold]
866-
pub fn parameter_modifiers_in_ts(modifier: &Modifier) -> OxcDiagnostic {
916+
pub fn parameter_modifiers_in_ts(
917+
modifier: &Modifier,
918+
allowed: Option<ModifierFlags>,
919+
) -> OxcDiagnostic {
867920
ts_error("8012", "Parameter modifiers can only be used in TypeScript files.")
868921
.with_label(modifier.span)
922+
.with_allowed_modifier_help(allowed)
869923
}
870924

871925
#[cold]

crates/oxc_parser/src/js/class.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ impl<'a> ParserImpl<'a> {
9696
self.verify_modifiers(
9797
modifiers,
9898
ModifierFlags::DECLARE | ModifierFlags::ABSTRACT,
99+
true,
99100
diagnostics::modifier_cannot_be_used_here,
100101
);
101102

@@ -226,6 +227,7 @@ impl<'a> ParserImpl<'a> {
226227
self.verify_modifiers(
227228
&modifiers,
228229
ModifierFlags::all() - ModifierFlags::EXPORT,
230+
false,
229231
diagnostics::cannot_appear_on_class_elements,
230232
);
231233

@@ -292,7 +294,10 @@ impl<'a> ParserImpl<'a> {
292294
));
293295
}
294296
}
295-
_ => self.error(diagnostics::cannot_appear_on_an_index_signature(modifier)),
297+
_ => self.error(diagnostics::cannot_appear_on_an_index_signature(
298+
modifier,
299+
Some(ModifierFlags::READONLY | ModifierFlags::STATIC),
300+
)),
296301
}
297302
}
298303

@@ -336,6 +341,7 @@ impl<'a> ParserImpl<'a> {
336341
self.verify_modifiers(
337342
modifiers,
338343
ModifierFlags::all() - ModifierFlags::ACCESSIBILITY,
344+
false,
339345
diagnostics::accessibility_modifier_on_private_property,
340346
);
341347
}
@@ -382,6 +388,7 @@ impl<'a> ParserImpl<'a> {
382388
| ModifierFlags::STATIC
383389
| ModifierFlags::ABSTRACT
384390
| ModifierFlags::OVERRIDE,
391+
true,
385392
diagnostics::accessor_modifier,
386393
);
387394
self.ast.class_element_accessor_property(
@@ -430,6 +437,7 @@ impl<'a> ParserImpl<'a> {
430437
self.verify_modifiers(
431438
modifiers,
432439
ModifierFlags::all() - ModifierFlags::ASYNC - ModifierFlags::DECLARE,
440+
false,
433441
diagnostics::modifier_cannot_be_used_here,
434442
);
435443
ClassElement::MethodDefinition(method_definition)
@@ -505,12 +513,28 @@ impl<'a> ParserImpl<'a> {
505513
for modifier in modifiers.iter() {
506514
match modifier.kind {
507515
ModifierKind::Declare => {
508-
self.error(diagnostics::cannot_appear_on_class_elements(modifier));
516+
self.error(diagnostics::cannot_appear_on_class_elements(
517+
modifier,
518+
Some(
519+
ModifierFlags::ACCESSIBILITY
520+
| ModifierFlags::STATIC
521+
| ModifierFlags::ABSTRACT
522+
| ModifierFlags::OVERRIDE
523+
| ModifierFlags::ASYNC,
524+
),
525+
));
509526
}
510527
ModifierKind::Readonly => {
511528
self.error(
512529
diagnostics::modifier_only_on_property_declaration_or_index_signature(
513530
modifier,
531+
Some(
532+
ModifierFlags::ACCESSIBILITY
533+
| ModifierFlags::STATIC
534+
| ModifierFlags::ABSTRACT
535+
| ModifierFlags::OVERRIDE
536+
| ModifierFlags::ASYNC,
537+
),
514538
),
515539
);
516540
}

crates/oxc_parser/src/js/function.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,14 @@ impl<'a> ParserImpl<'a> {
7878
self.verify_modifiers(
7979
&modifiers,
8080
allowed_modifiers,
81+
true,
8182
diagnostics::cannot_appear_on_a_parameter,
8283
);
8384
} else {
8485
self.verify_modifiers(
8586
&modifiers,
8687
ModifierFlags::empty(),
88+
true,
8789
diagnostics::parameter_modifiers_in_ts,
8890
);
8991
}
@@ -167,6 +169,7 @@ impl<'a> ParserImpl<'a> {
167169
self.verify_modifiers(
168170
modifiers,
169171
ModifierFlags::DECLARE | ModifierFlags::ASYNC,
172+
true,
170173
diagnostics::modifier_cannot_be_used_here,
171174
);
172175

crates/oxc_parser/src/js/object.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ impl<'a> ParserImpl<'a> {
6868
self.verify_modifiers(
6969
&modifiers,
7070
ModifierFlags::ASYNC,
71+
true,
7172
diagnostics::modifier_cannot_be_used_here,
7273
);
7374
let method = self.parse_method(
@@ -89,6 +90,7 @@ impl<'a> ParserImpl<'a> {
8990
self.verify_modifiers(
9091
&modifiers,
9192
ModifierFlags::empty(),
93+
true,
9294
diagnostics::modifier_cannot_be_used_here,
9395
);
9496

@@ -210,6 +212,7 @@ impl<'a> ParserImpl<'a> {
210212
self.verify_modifiers(
211213
modifiers,
212214
ModifierFlags::empty(),
215+
true,
213216
diagnostics::modifier_cannot_be_used_here,
214217
);
215218
self.ast.alloc_object_property(

crates/oxc_parser/src/modifiers.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
use std::fmt::Display;
2+
13
use bitflags::bitflags;
4+
use cow_utils::CowUtils;
25
use oxc_allocator::Vec;
36
use oxc_ast::ast::TSAccessibility;
47
use oxc_diagnostics::OxcDiagnostic;
@@ -30,6 +33,7 @@ bitflags! {
3033
const ACCESSOR = 1 << 14;
3134
const EXPORT = 1 << 15;
3235
const ACCESSIBILITY = Self::PRIVATE.bits() | Self::PROTECTED.bits() | Self::PUBLIC.bits();
36+
const TYPE_PARAM = Self::CONST.bits() | Self::IN.bits() | Self::OUT.bits();
3337
}
3438
}
3539

@@ -97,6 +101,18 @@ impl ModifierFlags {
97101
}
98102
}
99103

104+
impl Display for ModifierFlags {
105+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106+
for (i, (name, _)) in self.iter_names().enumerate() {
107+
if i != 0 {
108+
write!(f, ", ")?;
109+
}
110+
write!(f, "{}", name.cow_to_lowercase())?;
111+
}
112+
Ok(())
113+
}
114+
}
115+
100116
#[derive(Debug, Hash)]
101117
pub struct Modifier {
102118
pub span: Span,
@@ -483,13 +499,16 @@ impl<'a> ParserImpl<'a> {
483499
&mut self,
484500
modifiers: &Modifiers<'a>,
485501
allowed: ModifierFlags,
502+
// if true, allowed is exact match; if false, allowed is a superset
503+
// used whether to use for the diagnostic message
504+
strict: bool,
486505
diagnose: F,
487506
) where
488-
F: Fn(&Modifier) -> OxcDiagnostic,
507+
F: Fn(&Modifier, Option<ModifierFlags>) -> OxcDiagnostic,
489508
{
490509
for modifier in modifiers.iter() {
491510
if !allowed.contains(modifier.kind.into()) {
492-
self.error(diagnose(modifier));
511+
self.error(diagnose(modifier, strict.then_some(allowed)));
493512
}
494513
}
495514
}

crates/oxc_parser/src/ts/statement.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ impl<'a> ParserImpl<'a> {
3939
self.verify_modifiers(
4040
modifiers,
4141
ModifierFlags::DECLARE | ModifierFlags::CONST,
42+
true,
4243
diagnostics::modifier_cannot_be_used_here,
4344
);
4445
self.ast.declaration_ts_enum(
@@ -172,6 +173,7 @@ impl<'a> ParserImpl<'a> {
172173
self.verify_modifiers(
173174
modifiers,
174175
ModifierFlags::DECLARE,
176+
true,
175177
diagnostics::modifier_cannot_be_used_here,
176178
);
177179

@@ -200,6 +202,7 @@ impl<'a> ParserImpl<'a> {
200202
self.verify_modifiers(
201203
modifiers,
202204
ModifierFlags::DECLARE,
205+
true,
203206
diagnostics::modifier_cannot_be_used_here,
204207
);
205208
if let Some((implements_kw_span, _)) = implements {
@@ -253,6 +256,7 @@ impl<'a> ParserImpl<'a> {
253256
self.verify_modifiers(
254257
&modifiers,
255258
ModifierFlags::READONLY,
259+
true,
256260
diagnostics::cannot_appear_on_an_index_signature,
257261
);
258262
return TSSignature::TSIndexSignature(
@@ -263,6 +267,7 @@ impl<'a> ParserImpl<'a> {
263267
self.verify_modifiers(
264268
&modifiers,
265269
ModifierFlags::READONLY,
270+
true,
266271
diagnostics::cannot_appear_on_a_type_member,
267272
);
268273

@@ -396,6 +401,7 @@ impl<'a> ParserImpl<'a> {
396401
self.verify_modifiers(
397402
modifiers,
398403
ModifierFlags::DECLARE,
404+
true,
399405
diagnostics::modifier_cannot_be_used_here,
400406
);
401407
self.ast.alloc_ts_module_declaration(
@@ -440,6 +446,7 @@ impl<'a> ParserImpl<'a> {
440446
self.verify_modifiers(
441447
modifiers,
442448
ModifierFlags::DECLARE,
449+
true,
443450
diagnostics::modifier_cannot_be_used_here,
444451
);
445452
let decl = self.parse_variable_declaration(

0 commit comments

Comments
 (0)