From fa812b353ea2e8d7dc64a1d658dfa02b745499d2 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sat, 29 Nov 2025 17:54:24 +0900 Subject: [PATCH] fix --- .changeset/real-corners-pay.md | 5 + src/index.ts | 16 +- test/decorator_after_interface/expected.json | 409 +++++++++++++++++++ test/decorator_after_interface/input.ts | 9 + 4 files changed, 438 insertions(+), 1 deletion(-) create mode 100644 .changeset/real-corners-pay.md create mode 100644 test/decorator_after_interface/expected.json create mode 100644 test/decorator_after_interface/input.ts diff --git a/.changeset/real-corners-pay.md b/.changeset/real-corners-pay.md new file mode 100644 index 0000000..35728bc --- /dev/null +++ b/.changeset/real-corners-pay.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/acorn-typescript': patch +--- + +fix: handle decorators after type declarations diff --git a/src/index.ts b/src/index.ts index 53c4f17..c1eec5d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2459,11 +2459,25 @@ export function tsPlugin(options?: { node.extends = this.tsParseHeritageClause('extends'); } const body = this.startNode(); - body.body = this.tsInType(this.tsParseObjectTypeMembers.bind(this)); + body.body = this.tsParseInterfaceBody(); node.body = this.finishNode(body, 'TSInterfaceBody'); return this.finishNode(node, 'TSInterfaceDeclaration'); } + /** + * Parse interface body, ensuring the closing brace is read outside of type context + * so that decorators following the interface are properly tokenized. + */ + tsParseInterfaceBody(): Array { + this.expect(tt.braceL); + const oldInType = this.inType; + this.inType = true; + let members = this.tsParseList('TypeMembers', this.tsParseTypeMember.bind(this)); + this.inType = oldInType; + this.expect(tt.braceR); + return members; + } + tsParseAbstractDeclaration(node: any): any | undefined | null { if (this.match(tt._class)) { node.abstract = true; diff --git a/test/decorator_after_interface/expected.json b/test/decorator_after_interface/expected.json new file mode 100644 index 0000000..a68f77a --- /dev/null +++ b/test/decorator_after_interface/expected.json @@ -0,0 +1,409 @@ +{ + "type": "Program", + "start": 0, + "end": 122, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 10, + "column": 0 + } + }, + "body": [ + { + "type": "TSInterfaceDeclaration", + "start": 0, + "end": 48, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 4, + "column": 1 + } + }, + "id": { + "type": "Identifier", + "start": 10, + "end": 14, + "loc": { + "start": { + "line": 1, + "column": 10 + }, + "end": { + "line": 1, + "column": 14 + } + }, + "name": "User" + }, + "body": { + "type": "TSInterfaceBody", + "start": 15, + "end": 48, + "loc": { + "start": { + "line": 1, + "column": 15 + }, + "end": { + "line": 4, + "column": 1 + } + }, + "body": [ + { + "type": "TSPropertySignature", + "start": 19, + "end": 30, + "loc": { + "start": { + "line": 2, + "column": 2 + }, + "end": { + "line": 2, + "column": 13 + } + }, + "computed": false, + "key": { + "type": "Identifier", + "start": 19, + "end": 21, + "loc": { + "start": { + "line": 2, + "column": 2 + }, + "end": { + "line": 2, + "column": 4 + } + }, + "name": "id" + }, + "typeAnnotation": { + "type": "TSTypeAnnotation", + "start": 21, + "end": 29, + "loc": { + "start": { + "line": 2, + "column": 4 + }, + "end": { + "line": 2, + "column": 12 + } + }, + "typeAnnotation": { + "type": "TSNumberKeyword", + "start": 23, + "end": 29, + "loc": { + "start": { + "line": 2, + "column": 6 + }, + "end": { + "line": 2, + "column": 12 + } + } + } + } + }, + { + "type": "TSPropertySignature", + "start": 33, + "end": 46, + "loc": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 15 + } + }, + "computed": false, + "key": { + "type": "Identifier", + "start": 33, + "end": 37, + "loc": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 6 + } + }, + "name": "name" + }, + "typeAnnotation": { + "type": "TSTypeAnnotation", + "start": 37, + "end": 45, + "loc": { + "start": { + "line": 3, + "column": 6 + }, + "end": { + "line": 3, + "column": 14 + } + }, + "typeAnnotation": { + "type": "TSStringKeyword", + "start": 39, + "end": 45, + "loc": { + "start": { + "line": 3, + "column": 8 + }, + "end": { + "line": 3, + "column": 14 + } + } + } + } + } + ] + } + }, + { + "type": "ExportNamedDeclaration", + "start": 64, + "end": 121, + "loc": { + "start": { + "line": 7, + "column": 0 + }, + "end": { + "line": 9, + "column": 1 + } + }, + "exportKind": "value", + "declaration": { + "type": "ClassDeclaration", + "start": 50, + "end": 121, + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 9, + "column": 1 + } + }, + "decorators": [ + { + "type": "Decorator", + "start": 50, + "end": 63, + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 6, + "column": 13 + } + }, + "expression": { + "type": "CallExpression", + "start": 51, + "end": 63, + "loc": { + "start": { + "line": 6, + "column": 1 + }, + "end": { + "line": 6, + "column": 13 + } + }, + "callee": { + "type": "Identifier", + "start": 51, + "end": 61, + "loc": { + "start": { + "line": 6, + "column": 1 + }, + "end": { + "line": 6, + "column": 11 + } + }, + "name": "Injectable" + }, + "arguments": [] + } + } + ], + "id": { + "type": "Identifier", + "start": 77, + "end": 88, + "loc": { + "start": { + "line": 7, + "column": 13 + }, + "end": { + "line": 7, + "column": 24 + } + }, + "name": "UserService" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start": 89, + "end": 121, + "loc": { + "start": { + "line": 7, + "column": 25 + }, + "end": { + "line": 9, + "column": 1 + } + }, + "body": [ + { + "type": "MethodDefinition", + "start": 93, + "end": 119, + "loc": { + "start": { + "line": 8, + "column": 2 + }, + "end": { + "line": 8, + "column": 28 + } + }, + "static": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 93, + "end": 100, + "loc": { + "start": { + "line": 8, + "column": 2 + }, + "end": { + "line": 8, + "column": 9 + } + }, + "name": "getUser" + }, + "kind": "method", + "value": { + "type": "FunctionExpression", + "start": 100, + "end": 119, + "loc": { + "start": { + "line": 8, + "column": 9 + }, + "end": { + "line": 8, + "column": 28 + } + }, + "id": null, + "expression": false, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 103, + "end": 119, + "loc": { + "start": { + "line": 8, + "column": 12 + }, + "end": { + "line": 8, + "column": 28 + } + }, + "body": [ + { + "type": "ReturnStatement", + "start": 105, + "end": 117, + "loc": { + "start": { + "line": 8, + "column": 14 + }, + "end": { + "line": 8, + "column": 26 + } + }, + "argument": { + "type": "Literal", + "start": 112, + "end": 116, + "loc": { + "start": { + "line": 8, + "column": 21 + }, + "end": { + "line": 8, + "column": 25 + } + }, + "value": null, + "raw": "null" + } + } + ] + } + } + } + ] + } + }, + "specifiers": [], + "source": null + } + ], + "sourceType": "module" +} diff --git a/test/decorator_after_interface/input.ts b/test/decorator_after_interface/input.ts new file mode 100644 index 0000000..d6e3739 --- /dev/null +++ b/test/decorator_after_interface/input.ts @@ -0,0 +1,9 @@ +interface User { + id: number; + name: string; +} + +@Injectable() +export class UserService { + getUser() { return null; } +}