From 09aadcb8baa90ff74b01e19fafc160488f0ca149 Mon Sep 17 00:00:00 2001 From: mrazauskas Date: Sat, 22 Aug 2026 12:22:07 +0300 Subject: [PATCH 1/4] Add `hasTrailingComma` getter in `RemoteNodeList` class Signed-off-by: mrazauskas --- .../typescript/src/api/node/node.generated.ts | 6 +++++- packages/typescript/src/api/node/protocol.ts | 5 +++-- tools/scripts/tsc/generate-encoder.ts | 6 +++++- tsc/internal/api/encoder/encoder.go | 18 ++++++++++-------- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/packages/typescript/src/api/node/node.generated.ts b/packages/typescript/src/api/node/node.generated.ts index d9a8092261627..5293baac436ea 100644 --- a/packages/typescript/src/api/node/node.generated.ts +++ b/packages/typescript/src/api/node/node.generated.ts @@ -29,6 +29,7 @@ import { NODE_OFFSET_DATA, NODE_OFFSET_END, NODE_OFFSET_FLAGS, + NODE_OFFSET_HAS_TRAILING_COMMA, NODE_OFFSET_KIND, NODE_OFFSET_NEXT, NODE_OFFSET_PARENT, @@ -43,7 +44,6 @@ export class RemoteNodeList extends Array implements NodeArray implements NodeArray Date: Sat, 22 Aug 2026 13:12:39 +0300 Subject: [PATCH 2/4] update `encoder.ts` Signed-off-by: mrazauskas --- packages/typescript/src/api/node/encoder.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/typescript/src/api/node/encoder.ts b/packages/typescript/src/api/node/encoder.ts index 8f1acc5e43963..08dcdb9c87ae3 100644 --- a/packages/typescript/src/api/node/encoder.ts +++ b/packages/typescript/src/api/node/encoder.ts @@ -207,11 +207,11 @@ export function encodeNode(node: Node): Uint8Array { const extendedDataValues: number[] = []; const structuredWriter = new MsgpackWriter(); - // We'll build an array of uint32 values for the nodes section, 7 per node + // We'll build an array of uint32 values for the nodes section, NODE_FIELDS per node const nodeValues: number[] = []; // Nil node (index 0) - nodeValues.push(0, 0, 0, 0, 0, 0, 0); + nodeValues.push(0, 0, 0, 0, 0, 0, 0, 0); let nodeCount = 0; let parentIndex = 0; @@ -235,6 +235,7 @@ export function encodeNode(node: Node): Uint8Array { parentIndex, data, node.flags, + 0, // hasTrailingComma — not applicable to ordinary nodes ); const saveParentIndex = parentIndex; @@ -267,7 +268,8 @@ export function encodeNode(node: Node): Uint8Array { 0, // next parentIndex, list.length, // data for NodeList is its length - 0, // flags + 0, // flags — not applicable to NodeLists + list.hasTrailingComma ? 1 : 0, ); const saveParentIndex = parentIndex; @@ -313,6 +315,7 @@ export function encodeNode(node: Node): Uint8Array { 0, rootData, node.flags, + 0, // hasTrailingComma — not applicable to ordinary nodes ); const saveParent = parentIndex; From 3195fa95766e39c5dc3309d49f62acdaa96d4f4e Mon Sep 17 00:00:00 2001 From: mrazauskas Date: Sat, 22 Aug 2026 13:12:45 +0300 Subject: [PATCH 3/4] update tests Signed-off-by: mrazauskas --- packages/typescript/test/encoder.test.ts | 6 +++--- tsc/internal/api/encoder/encoder_test.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/typescript/test/encoder.test.ts b/packages/typescript/test/encoder.test.ts index 9e1318bdc770f..c214e0675f56e 100644 --- a/packages/typescript/test/encoder.test.ts +++ b/packages/typescript/test/encoder.test.ts @@ -67,7 +67,7 @@ describe("Encoder", () => { // Verify header const view = new DataView(encoded.buffer, encoded.byteOffset, encoded.byteLength); const metadata = view.getUint32(0, true); - assert.strictEqual(metadata >>> 24, 7, "protocol version should be 7"); + assert.strictEqual(metadata >>> 24, 8, "protocol version should be 8"); // Verify we can decode it const decoded = decode(encoded); @@ -179,11 +179,11 @@ describe("Encoder", () => { assert.strictEqual(rootKind, SyntaxKind.IfStatement); }); - test("protocol version is 7", () => { + test("protocol version is 8", () => { const sf = makeSF("", "/test.ts", []); const encoded = encodeSourceFile(sf); const view = new DataView(encoded.buffer, encoded.byteOffset, encoded.byteLength); - assert.strictEqual(view.getUint32(0, true) >>> 24, 7); + assert.strictEqual(view.getUint32(0, true) >>> 24, 8); }); test("encodes source files without content mapping metadata", () => { diff --git a/tsc/internal/api/encoder/encoder_test.go b/tsc/internal/api/encoder/encoder_test.go index cc2dc00118cc0..4ac3e5ba036a6 100644 --- a/tsc/internal/api/encoder/encoder_test.go +++ b/tsc/internal/api/encoder/encoder_test.go @@ -37,8 +37,8 @@ func TestEncodeSourceFile(t *testing.T) { func TestEncodeContentMapperSourceFileMetadata(t *testing.T) { t.Parallel() - if encoder.ProtocolVersion != 7 { - t.Fatalf("protocol version = %d, want 7", encoder.ProtocolVersion) + if encoder.ProtocolVersion != 8 { + t.Fatalf("protocol version = %d, want 8", encoder.ProtocolVersion) } sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{ FileName: "/component.vue", From d5bdeec2829ee90b71ca11fce94eb6c6964a3178 Mon Sep 17 00:00:00 2001 From: mrazauskas Date: Sat, 22 Aug 2026 14:19:58 +0300 Subject: [PATCH 4/4] add tests Signed-off-by: mrazauskas --- packages/typescript/test/async/api.test.ts | 24 ++++++++++++++++++++++ packages/typescript/test/sync/api.test.ts | 24 ++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/packages/typescript/test/async/api.test.ts b/packages/typescript/test/async/api.test.ts index fe08206bc486b..e8d0b5d07bf07 100644 --- a/packages/typescript/test/async/api.test.ts +++ b/packages/typescript/test/async/api.test.ts @@ -7,6 +7,7 @@ import { getSynthesizedDeepClone, InternalSymbolName, isCallExpression, + isExpressionStatement, isFunctionDeclaration, isIdentifier, isImportDeclaration, @@ -1083,6 +1084,29 @@ describe("SourceFile", () => { }); }); +describe("NodeArray", () => { + test("hasTrailingComma", async () => { + const api = spawnAPI({ + "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/src/main.ts": `declare function foo(...args: any): void;\nfoo("a", "b",);\nfoo("a", "b");`, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const sourceFile = await project.program.getSourceFile("/src/main.ts"); + assert.ok(sourceFile); + const statements = sourceFile.statements.filter(isExpressionStatement); + assert.ok(isCallExpression(statements[0].expression)); + assert.equal(statements[0].expression.arguments.hasTrailingComma, true); + assert.ok(isCallExpression(statements[1].expression)); + assert.equal(statements[1].expression.arguments.hasTrailingComma, false); + } + finally { + await api.close(); + } + }); +}); + test("unicode escapes", async () => { const api = spawnAPI({ "/tsconfig.json": "{}", diff --git a/packages/typescript/test/sync/api.test.ts b/packages/typescript/test/sync/api.test.ts index 6974061a4a6fb..2ba00f75e1227 100644 --- a/packages/typescript/test/sync/api.test.ts +++ b/packages/typescript/test/sync/api.test.ts @@ -15,6 +15,7 @@ import { getSynthesizedDeepClone, InternalSymbolName, isCallExpression, + isExpressionStatement, isFunctionDeclaration, isIdentifier, isImportDeclaration, @@ -1091,6 +1092,29 @@ describe("SourceFile", () => { }); }); +describe("NodeArray", () => { + test("hasTrailingComma", () => { + const api = spawnAPI({ + "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/src/main.ts": `declare function foo(...args: any): void;\nfoo("a", "b",);\nfoo("a", "b");`, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const sourceFile = project.program.getSourceFile("/src/main.ts"); + assert.ok(sourceFile); + const statements = sourceFile.statements.filter(isExpressionStatement); + assert.ok(isCallExpression(statements[0].expression)); + assert.equal(statements[0].expression.arguments.hasTrailingComma, true); + assert.ok(isCallExpression(statements[1].expression)); + assert.equal(statements[1].expression.arguments.hasTrailingComma, false); + } + finally { + api.close(); + } + }); +}); + test("unicode escapes", () => { const api = spawnAPI({ "/tsconfig.json": "{}",