fix(parser): validate TypeScript type member separators (#27222)

Type members without an explicit comma or semicolon must end before a line break, `}`, or EOF. The shared helper previously accepted adjacent same-line members such as `type T = { (): void next: string }` without a diagnostic.

Use the existing ASI check after accepting a comma, matching TypeScript's `parseTypeMemberSemicolon`. Valid comma, semicolon, newline, and closing-brace cases remain accepted. Add focused signature and comment-line-break fixtures, reuse upstream property/index regressions, and update the expected diagnostics.
This commit is contained in:
camc314 committed 2026-09-30 09:49:52 +00:00
1 parent 29fdf495d9
commit 3d61a599d1
17 files changed
+124 -68

No files matched your search

@@ -585,12 +585,12 @@ mod tests {
("class Greeter { message: Array<any> }", None),
("class Greeter { message: any[] }", None),
("class Greeter { message: Array<Array<any>> }", None),
("interface Greeter { constructor(param: Array<any>) {} }", None),
// ("interface Greeter { constructor(param: Array<any>) {} }", None),
("interface Greeter { message: any }", None),
("interface Greeter { message: Array<any> }", None),
("interface Greeter { message: any[] }", None),
("interface Greeter { message: Array<Array<any>> }", None),
("type obj = { constructor(param: Array<any>) {} }", None),
// ("type obj = { constructor(param: Array<any>) {} }", None),
("type obj = { message: any }", None),
("type obj = { message: Array<any> }", None),
("type obj = { message: any[] }", None),
@@ -114,12 +114,6 @@ source: crates/oxc_linter/src/tester.rs
╰────
help: Use `unknown` instead, this will force you to explicitly, and safely, assert the type is correct.
× Unexpected token
╭─[no_explicit_any.tsx:1:52]
1 │ interface Greeter { constructor(param: Array<any>) {} }
· ─
╰────
⚠ typescript(no-explicit-any): Unexpected `any`. Specify a different type.
╭─[no_explicit_any.tsx:1:30]
1 │ interface Greeter { message: any }
@@ -148,12 +142,6 @@ source: crates/oxc_linter/src/tester.rs
╰────
help: Use `unknown` instead, this will force you to explicitly, and safely, assert the type is correct.
× Unexpected token
╭─[no_explicit_any.tsx:1:45]
1 │ type obj = { constructor(param: Array<any>) {} }
· ─
╰────
⚠ typescript(no-explicit-any): Unexpected `any`. Specify a different type.
╭─[no_explicit_any.tsx:1:23]
1 │ type obj = { message: any }
+1 -1
View File
@@ -1598,7 +1598,7 @@ impl<'a, C: Config> ParserImpl<'a, C> {
return;
}
// Didn't have a comma. We must have a (possible ASI) semicolon.
self.bump(Kind::Semicolon);
self.asi();
}
fn parse_ts_index_signature_name(&mut self) -> TSIndexSignatureName<'a> {
@@ -0,0 +1 @@
type T = { (): void next: string };
@@ -0,0 +1 @@
interface T { new (): object next: string }
@@ -0,0 +1 @@
type T = { get value(): string next: string };
@@ -0,0 +1 @@
interface T { method(): void next: string }
@@ -0,0 +1 @@
type T = { set value(value: string) next: string };
@@ -0,0 +1,9 @@
interface T {
property: string /*
*/ (): void /*
*/ new (): object /*
*/ method(): void /*
*/ get value(): string /*
*/ set value(value: string) /*
*/ [key: string]: string
}
+2 -2
View File
@@ -1,3 +1,3 @@
codegen_misc Summary:
AST Parsed : 111/111 (100.00%)
Positive Passed: 111/111 (100.00%)
AST Parsed : 112/112 (100.00%)
Positive Passed: 112/112 (100.00%)
+2 -2
View File
@@ -1,3 +1,3 @@
formatter_misc Summary:
AST Parsed : 111/111 (100.00%)
Positive Passed: 111/111 (100.00%)
AST Parsed : 112/112 (100.00%)
Positive Passed: 112/112 (100.00%)
+2 -2
View File
@@ -1,3 +1,3 @@
lexer_misc Summary:
AST Parsed : 348/348 (100.00%)
Positive Passed: 348/348 (100.00%)
AST Parsed : 354/354 (100.00%)
Positive Passed: 354/354 (100.00%)
+4 -3
View File
@@ -14658,11 +14658,12 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc
╰────
help: Try inserting a semicolon here
× Unexpected token
╭─[babel/packages/babel-parser/test/fixtures/typescript/interface/property-initializer/input.ts:1:25]
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[babel/packages/babel-parser/test/fixtures/typescript/interface/property-initializer/input.ts:1:24]
1 │ interface I { x: number = 1;}
· ─
· ▲
╰────
help: Try inserting a semicolon here
× TS(1063): An export assignment cannot be used in a namespace.
╭─[babel/packages/babel-parser/test/fixtures/typescript/module-namespace/invalid-body-declare-export-assignment/input.ts:2:3]
+41 -5
View File
@@ -1,7 +1,7 @@
parser_misc Summary:
AST Parsed : 111/111 (100.00%)
Positive Passed: 111/111 (100.00%)
Negative Passed: 239/239 (100.00%)
AST Parsed : 112/112 (100.00%)
Positive Passed: 112/112 (100.00%)
Negative Passed: 244/244 (100.00%)
× TS(1275): 'accessor' modifier can only appear on a property declaration.
╭─[misc/fail/accessor-modifier-abstract-method.ts:1:29]
@@ -4818,13 +4818,14 @@ Negative Passed: 239/239 (100.00%)
╰────
help: Remove the `?`
× Unexpected token
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[misc/fail/oxc-5955-2.ts:3:8]
2 │ interface B {
3 │ e()?: number;
· ─
· ▲
4 │ }
╰────
help: Try inserting a semicolon here
× TS(1021): An index signature must have a type annotation.
╭─[misc/fail/oxc-5955-3.ts:2:5]
@@ -5082,6 +5083,41 @@ Negative Passed: 239/239 (100.00%)
· ──────
╰────
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[misc/fail/ts-type-member-call-missing-separator.ts:1:20]
1 │ type T = { (): void next: string };
· ▲
╰────
help: Try inserting a semicolon here
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[misc/fail/ts-type-member-construct-missing-separator.ts:1:29]
1 │ interface T { new (): object next: string }
· ▲
╰────
help: Try inserting a semicolon here
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[misc/fail/ts-type-member-getter-missing-separator.ts:1:31]
1 │ type T = { get value(): string next: string };
· ▲
╰────
help: Try inserting a semicolon here
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[misc/fail/ts-type-member-method-missing-separator.ts:1:29]
1 │ interface T { method(): void next: string }
· ▲
╰────
help: Try inserting a semicolon here
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[misc/fail/ts-type-member-setter-missing-separator.ts:1:36]
1 │ type T = { set value(value: string) next: string };
· ▲
╰────
help: Try inserting a semicolon here
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[misc/fail/ts-unerasable-as-chained.ts:4:33]
3 │ // so the trailing `*` still makes the assertion impossible to erase. Syntax error.
+52 -35
View File
@@ -3,7 +3,7 @@ commit: f29aeb9f
parser_typescript Summary:
AST Parsed : 9658/9666 (99.92%)
Positive Passed: 9651/9666 (99.84%)
Negative Passed: 1687/2759 (61.15%)
Negative Passed: 1689/2759 (61.22%)
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
@@ -1838,8 +1838,6 @@ Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/conforma
Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/parser/ecmascript5/FunctionDeclarations/parserFunctionDeclaration7.ts
Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/parser/ecmascript5/IndexMemberDeclarations/parserIndexMemberDeclaration5.ts
Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/parser/ecmascript5/MemberAccessorDeclarations/parserMemberAccessor1.ts
Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/parser/ecmascript5/MemberAccessorDeclarations/parserMemberAccessorDeclaration1.ts
@@ -2072,8 +2070,6 @@ Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/conforma
Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/types/objectTypeLiteral/methodSignatures/methodSignaturesWithOverloads.ts
Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/types/objectTypeLiteral/objectTypeLiteralSyntax2.ts
Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/types/specifyingTypes/typeQueries/typeofANonExportedType.ts
Expect Syntax Error: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/types/specifyingTypes/typeQueries/typeofAnExportedType.ts
@@ -2582,13 +2578,14 @@ Expect to Parse: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/
17 │ }
╰────
× Unexpected token
╭─[typescript/tsc/testdata/tests/cases/compiler/accessorBodyInTypeContext.ts:2:15]
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[typescript/tsc/testdata/tests/cases/compiler/accessorBodyInTypeContext.ts:2:14]
1 │ type A = {
2 │ get foo() { return 0 }
· ─
· ▲
3 │ };
╰────
help: Try inserting a semicolon here
× TS(1090): 'public' modifier cannot appear on a parameter.
╭─[typescript/tsc/testdata/tests/cases/compiler/accessorParameterAccessibilityModifier.ts:2:11]
@@ -6177,12 +6174,13 @@ Expect to Parse: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/
╰────
help: Only 'readonly' modifier is allowed here.
× Unexpected token
╭─[typescript/tsc/testdata/tests/cases/compiler/declarationEmitPrivateMethodSignatures.ts:1:39]
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[typescript/tsc/testdata/tests/cases/compiler/declarationEmitPrivateMethodSignatures.ts:1:38]
1 │ interface A<T> { private a(): boolean {} }
· ─
· ▲
2 │
╰────
help: Try inserting a semicolon here
× TS(1049): A 'set' accessor must have exactly one parameter.
╭─[typescript/tsc/testdata/tests/cases/compiler/declarationEmitSetAccessorNoParameter.ts:2:10]
@@ -7083,21 +7081,23 @@ Expect to Parse: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/
8 │ };
╰────
× Unexpected token
╭─[typescript/tsc/testdata/tests/cases/compiler/errorOnInitializerInInterfaceProperty.ts:2:17]
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[typescript/tsc/testdata/tests/cases/compiler/errorOnInitializerInInterfaceProperty.ts:2:16]
1 │ interface Foo {
2 │ bar: number = 5;
· ─
· ▲
3 │ }
╰────
help: Try inserting a semicolon here
× Unexpected token
╭─[typescript/tsc/testdata/tests/cases/compiler/errorOnInitializerInObjectTypeLiteralProperty.ts:2:17]
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[typescript/tsc/testdata/tests/cases/compiler/errorOnInitializerInObjectTypeLiteralProperty.ts:2:16]
1 │ var Foo: {
2 │ bar: number = 5;
· ─
· ▲
3 │ };
╰────
help: Try inserting a semicolon here
× Expected `,` or `)` but found `Identifier`
╭─[typescript/tsc/testdata/tests/cases/compiler/errorRecoveryInClassDeclaration.ts:4:20]
@@ -8632,13 +8632,14 @@ Expect to Parse: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/
╰────
help: Try inserting a semicolon here
× TS(1099): Type argument list cannot be empty.
╭─[typescript/tsc/testdata/tests/cases/compiler/instantiateTypeParameter.ts:2:13]
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[typescript/tsc/testdata/tests/cases/compiler/instantiateTypeParameter.ts:2:8]
1 │ interface Foo<T> {
2 │ var x: T<>;
· ──
· ▲
3 │ }
╰────
help: Try inserting a semicolon here
× TS(1021): An index signature must have a type annotation.
╭─[typescript/tsc/testdata/tests/cases/compiler/intTypeCheck.ts:37:5]
@@ -10815,13 +10816,14 @@ Expect to Parse: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/
35 │ }
╰────
× Unexpected token
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[typescript/tsc/testdata/tests/cases/compiler/optionalPropertiesSyntax.ts:11:7]
10 │ (): any;
11 │ ()?: any; //err
· ─
· ▲
12 │ ?(): any; //err
╰────
help: Try inserting a semicolon here
× TS(1051): A 'set' accessor cannot have an optional parameter.
╭─[typescript/tsc/testdata/tests/cases/compiler/optionalSetterParam.ts:3:20]
@@ -11061,13 +11063,14 @@ Expect to Parse: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/
2 │ }
╰────
× Unexpected token
╭─[typescript/tsc/testdata/tests/cases/compiler/parseErrorIncorrectReturnToken.ts:2:17]
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[typescript/tsc/testdata/tests/cases/compiler/parseErrorIncorrectReturnToken.ts:2:16]
1 │ type F1 = {
2 │ (n: number) => string; // should be : not =>
· ──
· ▲
3 │ }
╰────
help: Try inserting a semicolon here
× Unexpected token
╭─[typescript/tsc/testdata/tests/cases/compiler/parseImportAttributesError.ts:2:30]
@@ -24919,18 +24922,14 @@ Expect to Parse: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/
╰────
help: Try inserting a semicolon here
× Expected `}` but found `EOF`
╭─[typescript/tsc/testdata/tests/cases/conformance/interfaces/interfacesExtendingClasses/interfaceExtendingClass2.ts:15:6]
9 │
10 │ interface I2 extends Foo { // error
· ┬
· ╰── Opened here
11 │ a: {
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[typescript/tsc/testdata/tests/cases/conformance/interfaces/interfacesExtendingClasses/interfaceExtendingClass2.ts:13:19]
12 │ toString: () => {
13 │ return 1;
· ▲
14 │ };
15 │ }
╰────
help: Try inserting a semicolon here
× Unexpected token
╭─[typescript/tsc/testdata/tests/cases/conformance/internalModules/exportDeclarations/NonInitializedExportInInternalModule.ts:3:8]
@@ -27397,6 +27396,15 @@ Expect to Parse: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/
3 │ }
╰────
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[typescript/tsc/testdata/tests/cases/conformance/parser/ecmascript5/IndexMemberDeclarations/parserIndexMemberDeclaration5.ts:2:23]
1 │ class C {
2 │ [a: string]: number public v: number
· ▲
3 │ }
╰────
help: Try inserting a semicolon here
× TS(1071): 'public' modifier cannot appear on an index signature.
╭─[typescript/tsc/testdata/tests/cases/conformance/parser/ecmascript5/IndexMemberDeclarations/parserIndexMemberDeclaration7.ts:2:4]
1 │ class C {
@@ -31791,13 +31799,22 @@ Expect to Parse: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/
25 │ }
╰────
× Unexpected token
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[typescript/tsc/testdata/tests/cases/conformance/types/objectTypeLiteral/methodSignatures/objectTypesWithOptionalProperties2.ts:4:8]
3 │ var a: {
4 │ x()?: number; // error
· ─
· ▲
5 │ }
╰────
help: Try inserting a semicolon here
× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[typescript/tsc/testdata/tests/cases/conformance/types/objectTypeLiteral/objectTypeLiteralSyntax2.ts:12:21]
11 │
12 │ var z: { foo: string bar: string }
· ▲
╰────
help: Try inserting a semicolon here
× Expected `,` or `}` but found `decimal`
╭─[typescript/tsc/testdata/tests/cases/conformance/types/objectTypeLiteral/propertySignatures/numericNamedPropertyDuplicates.ts:20:5]
+2 -2
View File
@@ -1,6 +1,6 @@
semantic_misc Summary:
AST Parsed : 111/111 (100.00%)
Positive Passed: 105/111 (94.59%)
AST Parsed : 112/112 (100.00%)
Positive Passed: 106/112 (94.64%)
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 : 111/111 (100.00%)
Positive Passed: 111/111 (100.00%)
AST Parsed : 112/112 (100.00%)
Positive Passed: 112/112 (100.00%)