fix(parser): reject TypeScript class modifiers in JavaScript (#27312)

Reject TypeScript-only class member modifiers in JavaScript with TS8009. Valid JavaScript modifiers and members named `declare`, `override`, or `readonly` remain accepted.

Reuse the existing modifier validation and storage, retaining its deduplication and keyword-span behavior. Coverage fixtures and updated conformance snapshots cover ordinary, repeated, and escaped modifiers.

fixes #27290
closes #27294
This commit is contained in:
camc314 committed 2026-10-04 11:05:16 +00:00
1 parent 335ce792fa
commit 4359a66953
16 files changed
+208 -18

No files matched your search

+5
View File
@@ -1197,6 +1197,11 @@ parser_diagnostics! {
ts_error("5087", "A labeled tuple element is declared as rest with a '...' before the name, rather than before the type.").with_label(span)
};
modifier_in_ts(kind: ModifierKind, span: Span) => {
ts_error("8009", format!("The '{kind}' modifier can only be used in TypeScript files."))
.with_label(span)
};
parameter_modifiers_in_ts(modifier: Modifier, allowed: Option<ModifierKinds>) => {
ts_error("8012", "Parameter modifiers can only be used in TypeScript files.")
.with_label(modifier.span())
+14
View File
@@ -267,6 +267,20 @@ impl<'a, C: Config> ParserImpl<'a, C> {
false,
diagnostics::cannot_appear_on_class_elements,
);
if !parser.is_ts {
parser.verify_modifiers(
modifiers,
ModifierKinds::new([
ModifierKind::Export,
ModifierKind::Default,
ModifierKind::Static,
ModifierKind::Async,
ModifierKind::Accessor,
]),
false,
|modifier, _| diagnostics::modifier_in_ts(modifier.kind, modifier.span()),
);
}
}
let start = self.cur_start();
@@ -0,0 +1,3 @@
class A {
declare y;
}
@@ -0,0 +1,4 @@
class A {
pr\u0069vate x;
publ\u0069c f() {}
}
@@ -0,0 +1,3 @@
class A {
private g() {}
}
@@ -0,0 +1,3 @@
class A {
protected f() {}
}
@@ -0,0 +1,3 @@
class A {
readonly x = 1;
}
@@ -0,0 +1,5 @@
class A {
private private x = 1;
public public f() {}
readonly readonly y = 1;
}
+15
View File
@@ -0,0 +1,15 @@
class A {
declare() {}
override() {}
readonly() {}
accessor value = 1;
static field = 1;
async method() {}
}
class B {
declare = 1;
override = 1;
readonly = 1;
}
+2 -2
View File
@@ -1,3 +1,3 @@
codegen_misc Summary:
AST Parsed : 112/112 (100.00%)
Positive Passed: 112/112 (100.00%)
AST Parsed : 113/113 (100.00%)
Positive Passed: 113/113 (100.00%)
+2 -2
View File
@@ -1,3 +1,3 @@
formatter_misc Summary:
AST Parsed : 112/112 (100.00%)
Positive Passed: 112/112 (100.00%)
AST Parsed : 113/113 (100.00%)
Positive Passed: 113/113 (100.00%)
+2 -2
View File
@@ -1,3 +1,3 @@
lexer_misc Summary:
AST Parsed : 353/353 (100.00%)
Positive Passed: 353/353 (100.00%)
AST Parsed : 360/360 (100.00%)
Positive Passed: 360/360 (100.00%)
+118 -3
View File
@@ -1,7 +1,7 @@
parser_misc Summary:
AST Parsed : 112/112 (100.00%)
Positive Passed: 112/112 (100.00%)
Negative Passed: 243/243 (100.00%)
AST Parsed : 113/113 (100.00%)
Positive Passed: 113/113 (100.00%)
Negative Passed: 249/249 (100.00%)
× TS(1275): 'accessor' modifier can only appear on a property declaration.
╭─[misc/fail/accessor-modifier-abstract-method.ts:1:29]
@@ -4649,6 +4649,121 @@ Negative Passed: 243/243 (100.00%)
7 │ }
╰────
× TS(8009): The 'declare' modifier can only be used in TypeScript files.
╭─[misc/fail/oxc-27290-declare.js:2:3]
1 │ class A {
2 │ declare y;
· ───────
3 │ }
╰────
× Keywords cannot contain escape characters
╭─[misc/fail/oxc-27290-escaped.js:2:3]
1 │ class A {
2 │ pr\u0069vate x;
· ────────────
3 │ publ\u0069c f() {}
╰────
× TS(8009): The 'private' modifier can only be used in TypeScript files.
╭─[misc/fail/oxc-27290-escaped.js:2:3]
1 │ class A {
2 │ pr\u0069vate x;
· ───────
3 │ publ\u0069c f() {}
╰────
× Keywords cannot contain escape characters
╭─[misc/fail/oxc-27290-escaped.js:3:3]
2 │ pr\u0069vate x;
3 │ publ\u0069c f() {}
· ───────────
4 │ }
╰────
× TS(8009): The 'public' modifier can only be used in TypeScript files.
╭─[misc/fail/oxc-27290-escaped.js:3:3]
2 │ pr\u0069vate x;
3 │ publ\u0069c f() {}
· ──────
4 │ }
╰────
× TS(8009): The 'private' modifier can only be used in TypeScript files.
╭─[misc/fail/oxc-27290-private.js:2:3]
1 │ class A {
2 │ private g() {}
· ───────
3 │ }
╰────
× TS(8009): The 'protected' modifier can only be used in TypeScript files.
╭─[misc/fail/oxc-27290-protected.js:2:3]
1 │ class A {
2 │ protected f() {}
· ─────────
3 │ }
╰────
× TS(8009): The 'readonly' modifier can only be used in TypeScript files.
╭─[misc/fail/oxc-27290-readonly.js:2:3]
1 │ class A {
2 │ readonly x = 1;
· ────────
3 │ }
╰────
× TS(1028): Accessibility modifier already seen.
╭─[misc/fail/oxc-27290-repeated.js:2:11]
1 │ class A {
2 │ private private x = 1;
· ───────
3 │ public public f() {}
╰────
help: Remove the duplicate modifier.
× TS(8009): The 'private' modifier can only be used in TypeScript files.
╭─[misc/fail/oxc-27290-repeated.js:2:11]
1 │ class A {
2 │ private private x = 1;
· ───────
3 │ public public f() {}
╰────
× TS(1028): Accessibility modifier already seen.
╭─[misc/fail/oxc-27290-repeated.js:3:10]
2 │ private private x = 1;
3 │ public public f() {}
· ──────
4 │ readonly readonly y = 1;
╰────
help: Remove the duplicate modifier.
× TS(8009): The 'public' modifier can only be used in TypeScript files.
╭─[misc/fail/oxc-27290-repeated.js:3:10]
2 │ private private x = 1;
3 │ public public f() {}
· ──────
4 │ readonly readonly y = 1;
╰────
× TS(1030): 'readonly' modifier already seen.
╭─[misc/fail/oxc-27290-repeated.js:4:12]
3 │ public public f() {}
4 │ readonly readonly y = 1;
· ────────
5 │ }
╰────
help: Remove the duplicate modifier.
× TS(8009): The 'readonly' modifier can only be used in TypeScript files.
╭─[misc/fail/oxc-27290-repeated.js:4:12]
3 │ public public f() {}
4 │ readonly readonly y = 1;
· ────────
5 │ }
╰────
× Expected `:` but found `[`
╭─[misc/fail/oxc-3320.tsx:1:8]
1 │ m< $<{3[ $<{3[ $<{3[ m< m$<{3[ m< mm< $<{3[ $<{3[ $<{3[ m< m$<{3[ m< m$<{3[ $<{3[ m< m$<{3[
@@ -3,7 +3,7 @@ commit: f29aeb9f
parser_typescript Summary:
AST Parsed : 9658/9666 (99.92%)
Positive Passed: 9651/9666 (99.84%)
Negative Passed: 1689/2759 (61.22%)
Negative Passed: 1691/2759 (61.29%)
Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/compiler/FunctionDeclaration3.ts
Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/compiler/FunctionDeclaration4.ts
@@ -570,8 +570,6 @@ Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/compiler
Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/compiler/jsFileCompilationOptionalClassElementSyntaxOfClass.ts
Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/compiler/jsFileCompilationPublicMethodSyntaxOfClass.ts
Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/compiler/jsFunctionWithPrototypeNoErrorTruncationNoCrash.ts
Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/compiler/jsTypedefMergedWithModuleExportProperty.ts
@@ -1802,8 +1800,6 @@ Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/conforma
Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/node/nodeModulesTopLevelAwait.ts
Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/override/override_js3.ts
Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/parser/ecmascript3/Accessors/parserES3Accessors1.ts
Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/parser/ecmascript3/Accessors/parserES3Accessors3.ts
@@ -2764,6 +2760,14 @@ Expect to Parse: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/
7 │ }
╰────
× TS(8009): The 'declare' modifier can only be used in TypeScript files.
╭─[typescript/tsc/testdata/tests/cases/compiler/ambientPropertyDeclarationInJs.ts:6:5]
5 │
6 │ declare prop: string;
· ───────
7 │
╰────
× Expected `;` but found `:`
╭─[typescript/tsc/testdata/tests/cases/compiler/ambientPropertyDeclarationInJs.ts:6:17]
5 │
@@ -8933,6 +8937,14 @@ Expect to Parse: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/
· ╰── Opened here
╰────
× TS(8009): The 'public' modifier can only be used in TypeScript files.
╭─[typescript/tsc/testdata/tests/cases/compiler/jsFileCompilationPublicMethodSyntaxOfClass.ts:2:5]
1 │ class C {
2 │ public foo() {
· ──────
3 │ }
╰────
× TS(8012): Parameter modifiers can only be used in TypeScript files.
╭─[typescript/tsc/testdata/tests/cases/compiler/jsFileCompilationPublicParameterModifier.ts:1:23]
1 │ class C { constructor(public x) { }}
@@ -25981,6 +25993,14 @@ Expect to Parse: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/
╰────
help: No modifiers are allowed here.
× TS(8009): The 'override' modifier can only be used in TypeScript files.
╭─[typescript/tsc/testdata/tests/cases/conformance/override/override_js3.ts:7:5]
6 │ class D extends B {
7 │ override foo (v) {}
· ────────
8 │ /** @override */
╰────
× Cannot use `await` as an identifier in an async context
╭─[typescript/tsc/testdata/tests/cases/conformance/parser/ecmascript2018/asyncGenerators/parser.asyncGenerators.classMethods.es2018.ts:3:15]
2 │ class C4 {
+2 -2
View File
@@ -1,6 +1,6 @@
semantic_misc Summary:
AST Parsed : 112/112 (100.00%)
Positive Passed: 106/112 (94.64%)
AST Parsed : 113/113 (100.00%)
Positive Passed: 107/113 (94.69%)
semantic Error: tasks/coverage/misc/pass/declare-let-private.ts
Bindings mismatch:
after transform: ScopeId(0): ["private"]
@@ -1,3 +1,3 @@
transformer_misc Summary:
AST Parsed : 112/112 (100.00%)
Positive Passed: 112/112 (100.00%)
AST Parsed : 113/113 (100.00%)
Positive Passed: 113/113 (100.00%)