mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
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:
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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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 │
|
||||
|
||||
Reference in new issue
Block a user