fix(parser): reject incompatible class member modifiers (#22843)

`static`/`abstract` and `declare`/`override` are mutually exclusive in either
order (TS1243):

- `static abstract m()` / `abstract static m()`
- `declare override x` / `override declare x`

oxc already validated modifier order (TS1029) and duplicates (TS1030). This extends
the existing `ILLEGAL_PRECEDING_MODIFIERS` table in both directions for these pairs
and reports them as "cannot be used with" (TS1243) rather than "must precede" — the
fast path is unchanged (still a table lookup + AND); only the cold error path picks
the message.

Matches babel and TypeScript: parser_babel +1 negative-pass
(`class/modifiers-incompatible`), positive/AST stay 100% across all suites, and the
correct TS1243 diagnostic is now emitted on existing TypeScript cases.
This commit is contained in:
Boshen committed 2026-05-30 02:23:40 +00:00
1 parent 7e3a5674f9
commit c645615002
9 files changed
+165 -21

No files matched your search

@@ -96,13 +96,13 @@ class A {constructor(public readonly a: number) {}}
----------
class A{constructor(public readonly a:number){}}
########## 22
abstract class A {private abstract static m()}
abstract class A {protected abstract m()}
----------
abstract class A{private abstract static m();}
abstract class A{protected abstract m();}
########## 23
abstract class A {private abstract static readonly prop: string}
class A {private static readonly prop: string}
----------
abstract class A{private abstract static readonly prop:string}
class A{private static readonly prop:string}
########## 24
interface A { a: string, 'b': number, 'c'(): void }
----------
@@ -142,17 +142,17 @@ class A {
}
########## 22
abstract class A {private abstract static m()}
abstract class A {protected abstract m()}
----------
abstract class A {
private abstract static m();
protected abstract m();
}
########## 23
abstract class A {private abstract static readonly prop: string}
class A {private static readonly prop: string}
----------
abstract class A {
private abstract static readonly prop: string;
class A {
private static readonly prop: string;
}
########## 24
+2 -2
View File
@@ -89,8 +89,8 @@ fn ts() {
"function <const T>(){}",
"class A {m?(): void}",
"class A {constructor(public readonly a: number) {}}",
"abstract class A {private abstract static m()}",
"abstract class A {private abstract static readonly prop: string}",
"abstract class A {protected abstract m()}",
"class A {private static readonly prop: string}",
"interface A { a: string, 'b': number, 'c'(): void }",
"enum A { a, 'b' }",
"module 'a'",
@@ -723,7 +723,7 @@ fn test() {
(
"
abstract class Test {
private abstract accessor foo: number;
private accessor foo: number;
}
",
None,
@@ -922,7 +922,7 @@ mod test {
return;
}
private arrow = one => 'arrow';
private abstract abs(one);
private abs(one) {}
}
",
None,
+22
View File
@@ -913,6 +913,28 @@ pub fn modifier_must_precede_other_modifier(
.with_label(modifier.span())
}
#[cold]
pub fn modifier_cannot_be_used_with_other_modifier(
span: Span,
modifier: ModifierKind,
other_modifier: ModifierKind,
) -> OxcDiagnostic {
ts_error(
"1243",
format!("'{modifier}' modifier cannot be used with '{other_modifier}' modifier."),
)
.with_label(span)
}
#[cold]
pub fn modifier_cannot_be_used_in_ambient_context(
span: Span,
modifier: ModifierKind,
) -> OxcDiagnostic {
ts_error("1040", format!("'{modifier}' modifier cannot be used in an ambient context."))
.with_label(span)
}
#[cold]
pub fn modifier_already_seen(modifier: &Modifier) -> OxcDiagnostic {
ts_error("1030", format!("'{}' modifier already seen.", modifier.kind))
+31 -1
View File
@@ -681,17 +681,21 @@ const fn get_illegal_preceding_modifiers(kind: ModifierKind) -> ModifierKinds {
ModifierKind::Async,
ModifierKind::Accessor,
ModifierKind::Override,
ModifierKind::Abstract,
]),
ModifierKind::Override => ModifierKinds::new([
ModifierKind::Override,
ModifierKind::Readonly,
ModifierKind::Accessor,
ModifierKind::Async,
ModifierKind::Declare,
]),
ModifierKind::Abstract => ModifierKinds::new([
ModifierKind::Abstract,
ModifierKind::Override,
ModifierKind::Accessor,
ModifierKind::Static,
ModifierKind::Private,
]),
ModifierKind::Export => ModifierKinds::new([
ModifierKind::Export,
@@ -699,6 +703,9 @@ const fn get_illegal_preceding_modifiers(kind: ModifierKind) -> ModifierKinds {
ModifierKind::Abstract,
ModifierKind::Async,
]),
ModifierKind::Declare => {
ModifierKinds::new([ModifierKind::Declare, ModifierKind::Override])
}
_ => ModifierKinds::new([kind]),
}
}
@@ -731,6 +738,7 @@ impl<C: Config> ParserImpl<'_, C> {
ModifierKind::Private,
ModifierKind::Protected,
]);
use ModifierKind::{Abstract, Declare, Override, Private, Static};
let this_kind = modifier.kind;
let this_kinds = ModifierKinds::new([this_kind]);
@@ -757,7 +765,29 @@ impl<C: Config> ParserImpl<'_, C> {
// If multiple illegal kinds, it's arbitrary which one the error is raised for.
let illegal_kinds = illegal_preceding_modifier_kinds.intersection(existing_kinds);
let illegal_kind = illegal_kinds.iter().next().unwrap();
self.error(diagnostics::modifier_must_precede_other_modifier(modifier, illegal_kind));
// Some pairs are mutually exclusive in either order. TypeScript reports them as TS1243
// ("cannot be used with") or TS1040 ("cannot be used in an ambient context") rather than
// as an ordering error (TS1029). Wording and codes follow `typescript-go`'s
// `checkGrammarModifiers`.
let span = modifier.span();
let error = match (this_kind, illegal_kind) {
(Static, Abstract) | (Abstract, Static) => {
diagnostics::modifier_cannot_be_used_with_other_modifier(span, Static, Abstract)
}
(Private, Abstract) | (Abstract, Private) => {
diagnostics::modifier_cannot_be_used_with_other_modifier(span, Private, Abstract)
}
// `declare override`
(Override, Declare) => {
diagnostics::modifier_cannot_be_used_with_other_modifier(span, Override, Declare)
}
// `override declare`
(Declare, Override) => {
diagnostics::modifier_cannot_be_used_in_ambient_context(span, Override)
}
_ => diagnostics::modifier_must_precede_other_modifier(modifier, illegal_kind),
};
self.error(error);
}
#[inline]
+41 -3
View File
@@ -3,13 +3,11 @@ commit: d9fe348c
parser_babel Summary:
AST Parsed : 2236/2236 (100.00%)
Positive Passed: 2222/2236 (99.37%)
Negative Passed: 1701/1723 (98.72%)
Negative Passed: 1702/1723 (98.78%)
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/cast/unparenthesized-assert-and-assign/input.ts
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/cast/unparenthesized-type-assertion-and-assign/input.ts
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/class/modifiers-incompatible/input.ts
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/conditional/arrow-ambiguity/input.ts
Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/conditional/arrow-like/input.ts
@@ -12902,6 +12900,38 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
3 │ }
╰────
× TS(1243): 'static' modifier cannot be used with 'abstract' modifier.
╭─[babel/packages/babel-parser/test/fixtures/typescript/class/modifiers-incompatible/input.ts:2:10]
1 │ abstract class A extends Base {
2 │ static abstract m1();
· ────────
3 │ abstract static m1();
╰────
× TS(1243): 'static' modifier cannot be used with 'abstract' modifier.
╭─[babel/packages/babel-parser/test/fixtures/typescript/class/modifiers-incompatible/input.ts:3:12]
2 │ static abstract m1();
3 │ abstract static m1();
· ──────
4 │
╰────
× TS(1243): 'override' modifier cannot be used with 'declare' modifier.
╭─[babel/packages/babel-parser/test/fixtures/typescript/class/modifiers-incompatible/input.ts:5:11]
4 │
5 │ declare override prop1: any;
· ────────
6 │ override declare prop2: any;
╰────
× TS(1040): 'override' modifier cannot be used in an ambient context.
╭─[babel/packages/babel-parser/test/fixtures/typescript/class/modifiers-incompatible/input.ts:6:12]
5 │ declare override prop1: any;
6 │ override declare prop2: any;
· ───────
7 │ }
╰────
× TS(1071): 'abstract' modifier cannot appear on an index signature.
╭─[babel/packages/babel-parser/test/fixtures/typescript/class/modifiers-index-signatures/input.ts:2:3]
1 │ class C {
@@ -14499,6 +14529,14 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
3 │ }
╰────
× TS(1243): 'static' modifier cannot be used with 'abstract' modifier.
╭─[babel/packages/babel-parser/test/fixtures/typescript/static-blocks/invalid-static-block-with-modifier-abstract-02/input.ts:2:10]
1 │ class Foo {
2 │ static abstract {}
· ────────
3 │ }
╰────
× Unexpected token
╭─[babel/packages/babel-parser/test/fixtures/typescript/static-blocks/invalid-static-block-with-modifier-abstract-02/input.ts:2:19]
1 │ class Foo {
@@ -3,7 +3,7 @@ commit: e5509e21
parser_typescript Summary:
AST Parsed : 9779/9779 (100.00%)
Positive Passed: 9779/9779 (100.00%)
Negative Passed: 1591/2640 (60.27%)
Negative Passed: 1592/2640 (60.30%)
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/FunctionDeclaration3.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/compiler/FunctionDeclaration4.ts
@@ -1002,8 +1002,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/async/es6
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration3_es6.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractProperties.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/classes/classExpressions/classWithStaticFieldInParameterBindingPattern.2.ts
Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/classes/classExpressions/classWithStaticFieldInParameterInitializer.2.ts
@@ -14558,6 +14556,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
3 │ }
╰────
× TS(1243): 'private' modifier cannot be used with 'abstract' modifier.
╭─[typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMixedWithModifiers.ts:6:13]
5 │ protected abstract foo_c();
6 │ private abstract foo_d();
· ────────
7 │
╰────
× TS(1029): 'public' modifier must precede 'abstract' modifier.
╭─[typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMixedWithModifiers.ts:8:14]
7 │
@@ -14574,7 +14580,7 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
10 │ abstract private foo_dd();
╰────
× TS(1029): 'private' modifier must precede 'abstract' modifier.
× TS(1243): 'private' modifier cannot be used with 'abstract' modifier.
╭─[typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMixedWithModifiers.ts:10:14]
9 │ abstract protected foo_cc();
10 │ abstract private foo_dd();
@@ -14582,6 +14588,22 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
11 │
╰────
× TS(1243): 'static' modifier cannot be used with 'abstract' modifier.
╭─[typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMixedWithModifiers.ts:12:14]
11 │
12 │ abstract static foo_d();
· ──────
13 │ static abstract foo_e();
╰────
× TS(1243): 'static' modifier cannot be used with 'abstract' modifier.
╭─[typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractMixedWithModifiers.ts:13:12]
12 │ abstract static foo_d();
13 │ static abstract foo_e();
· ────────
14 │
╰────
× TS(2391): Function implementation is missing or not immediately following the declaration.
╭─[typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractOverloads.ts:15:5]
14 │
@@ -14590,6 +14612,22 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
16 │ }
╰────
× TS(1243): 'private' modifier cannot be used with 'abstract' modifier.
╭─[typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractProperties.ts:5:13]
4 │ protected abstract z : number;
5 │ private abstract w : number;
· ────────
6 │
╰────
× TS(1243): 'private' modifier cannot be used with 'abstract' modifier.
╭─[typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractProperties.ts:12:13]
11 │ protected abstract foo_z() : number;
12 │ private abstract foo_w() : number;
· ────────
13 │ }
╰────
× TS(1244): Abstract methods can only appear within an abstract class.
╭─[typescript/tests/cases/conformance/classes/classDeclarations/classAbstractKeyword/classAbstractUsingAbstractMethods2.ts:2:14]
1 │ class A {
@@ -23839,6 +23877,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
7 │ super();
╰────
× TS(1040): 'override' modifier cannot be used in an ambient context.
╭─[typescript/tests/cases/conformance/override/override5.ts:14:14]
13 │
14 │ override declare p2: number;
· ───────
15 │
╰────
× TS(1029): 'override' modifier must precede 'readonly' modifier.
╭─[typescript/tests/cases/conformance/override/override5.ts:16:14]
15 │
@@ -23880,6 +23926,14 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
45 │ abstract override b(): void;
╰────
× TS(1040): 'override' modifier cannot be used in an ambient context.
╭─[typescript/tests/cases/conformance/override/override7.ts:11:14]
10 │
11 │ override declare p2: number;
· ───────
12 │
╰────
× TS(1029): 'override' modifier must precede 'readonly' modifier.
╭─[typescript/tests/cases/conformance/override/override7.ts:13:14]
12 │