From 33a453acd805f0c492c5bffd8b0c4245aeee56be Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 14 Sep 2026 15:23:42 -0700 Subject: [PATCH 1/5] Improve performance of batched requests --- Herebyfile.mjs | 2 +- packages/typescript/src/api/async/api.ts | 80 +- packages/typescript/src/api/async/client.ts | 134 +- packages/typescript/src/api/batch.ts | 37 + .../typescript/src/api/proto.generated.ts | 71 +- packages/typescript/src/api/sync/api.ts | 154 +- packages/typescript/src/api/sync/client.ts | 27 +- packages/typescript/test/async/api.test.ts | 55 +- tools/gen-proto/main.go | 165 +- tools/gen-proto/main_test.go | 28 +- tsc/internal/api/batch_decoder_generated.go | 2420 +++++++++++++++++ tsc/internal/api/proto.go | 117 +- tsc/internal/api/session.go | 566 ++-- tsc/internal/api/session_batch_test.go | 157 +- 14 files changed, 3499 insertions(+), 514 deletions(-) create mode 100644 packages/typescript/src/api/batch.ts create mode 100644 tsc/internal/api/batch_decoder_generated.go diff --git a/Herebyfile.mjs b/Herebyfile.mjs index 22c617436aef0..b8b6474618c5c 100644 --- a/Herebyfile.mjs +++ b/Herebyfile.mjs @@ -977,7 +977,7 @@ export const generateAST = task({ }); async function runGenerateAPI() { - await run("go", ["-C", "./tools", "run", "./gen-proto", "../tsc/internal/api/proto.go", "../packages/typescript/src/api/proto.generated.ts"]); + await run("go", ["-C", "./tools", "run", "./gen-proto", "../tsc/internal/api/proto.go", "../packages/typescript/src/api/proto.generated.ts", "../tsc/internal/api/batch_decoder_generated.go"]); await run("npx", ["dprint", "fmt", "packages/typescript/src/api/proto.generated.ts"]); } diff --git a/packages/typescript/src/api/async/api.ts b/packages/typescript/src/api/async/api.ts index 35a9c99ad47b5..92a158b64a6e7 100644 --- a/packages/typescript/src/api/async/api.ts +++ b/packages/typescript/src/api/async/api.ts @@ -34,6 +34,10 @@ import { unescapeLeadingUnderscores, } from "../../ast/index.ts"; import { assertNever } from "../../internal/utils.ts"; +import { + createGroupedBatchRequest, + getBatchResults, +} from "../batch.ts"; import { encodeNode, uint8ArrayToBase64, @@ -270,7 +274,7 @@ export class API implements FormatDiagnosticsHo // @sync-skip-block-end // @sync-only-start // batch(...requestGenerators: T): ExecutedGeneratorsResults { - // return executeRequestGenerators(requestGenerators, requests => this.client.batchRequests(requests).responses); + // return executeRequestGenerators(requestGenerators, requests => this.client.batchRequests(requests)); // } // @sync-only-end @@ -1597,12 +1601,12 @@ export class Checker { getSymbolAtLocation(nodes: readonly Node[]): Promise<(Symbol | undefined)[]>; async getSymbolAtLocation(nodeOrNodes: Node | readonly Node[]): Promise { if (Array.isArray(nodeOrNodes)) { - const data = await this.client.apiRequest("getSymbolsAtLocations", { - snapshot: this.snapshotId, - project: this.project.id, - locations: nodeOrNodes.map(node => getNodeId(node)), - }); - return data.map(d => d ? this.objectRegistry.getOrCreateSymbol(d) : undefined); + const response = await this.client.batchRequest(createGroupedBatchRequest( + "getSymbolAtLocation", + { snapshot: this.snapshotId, project: this.project.id }, + { location: nodeOrNodes.map(node => getNodeId(node)) }, + )); + return getBatchResults(response).map(data => data ? this.objectRegistry.getOrCreateSymbol(data) : undefined); } const data = await this.client.apiRequest("getSymbolAtLocation", { snapshot: this.snapshotId, @@ -1624,25 +1628,24 @@ export class Checker { }); return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; } - const data = await this.client.apiRequest("getSymbolsAtPositions", { - snapshot: this.snapshotId, - project: this.project.id, - file, - positions: positionOrPositions, - }); - return data.map(d => d ? this.objectRegistry.getOrCreateSymbol(d) : undefined); + const response = await this.client.batchRequest(createGroupedBatchRequest( + "getSymbolAtPosition", + { snapshot: this.snapshotId, project: this.project.id, file }, + { position: positionOrPositions }, + )); + return getBatchResults(response).map(data => data ? this.objectRegistry.getOrCreateSymbol(data) : undefined); } getSymbolOfSourceFile(file: DocumentIdentifier): Promise; getSymbolOfSourceFile(files: readonly DocumentIdentifier[]): Promise<(Symbol | undefined)[]>; async getSymbolOfSourceFile(fileOrFiles: DocumentIdentifier | readonly DocumentIdentifier[]): Promise { if (Array.isArray(fileOrFiles)) { - const data = await this.client.apiRequest("getSymbolsOfSourceFiles", { - snapshot: this.snapshotId, - project: this.project.id, - files: fileOrFiles, - }); - return data.map(d => d ? this.objectRegistry.getOrCreateSymbol(d) : undefined); + const response = await this.client.batchRequest(createGroupedBatchRequest( + "getSymbolOfSourceFile", + { snapshot: this.snapshotId, project: this.project.id }, + { file: fileOrFiles }, + )); + return getBatchResults(response).map(data => data ? this.objectRegistry.getOrCreateSymbol(data) : undefined); } const data = await this.client.apiRequest("getSymbolOfSourceFile", { snapshot: this.snapshotId, @@ -1661,12 +1664,12 @@ export class Checker { getTypeOfSymbol(symbols: readonly Symbol[]): Promise; async getTypeOfSymbol(symbolOrSymbols: Symbol | readonly Symbol[]): Promise { if (Array.isArray(symbolOrSymbols)) { - const data = await this.client.apiRequest("getTypesOfSymbols", { - snapshot: this.snapshotId, - project: this.project.id, - symbols: symbolOrSymbols.map(s => s.id), - }); - return data.map(d => this.objectRegistry.getOrCreateType(d)); + const response = await this.client.batchRequest(createGroupedBatchRequest( + "getTypeOfSymbol", + { snapshot: this.snapshotId, project: this.project.id }, + { symbol: symbolOrSymbols.map(symbol => symbol.id) }, + )); + return getBatchResults(response).map(data => this.objectRegistry.getOrCreateType(data)); } const data = await this.client.apiRequest("getTypeOfSymbol", { snapshot: this.snapshotId, @@ -1739,12 +1742,12 @@ export class Checker { getTypeAtLocation(nodes: readonly Node[]): Promise; async getTypeAtLocation(nodeOrNodes: Node | readonly Node[]): Promise { if (Array.isArray(nodeOrNodes)) { - const data = await this.client.apiRequest("getTypeAtLocations", { - snapshot: this.snapshotId, - project: this.project.id, - locations: nodeOrNodes.map(node => getNodeId(node)), - }); - return data.map(d => this.objectRegistry.getOrCreateType(d)); + const response = await this.client.batchRequest(createGroupedBatchRequest( + "getTypeAtLocation", + { snapshot: this.snapshotId, project: this.project.id }, + { location: nodeOrNodes.map(node => getNodeId(node)) }, + )); + return getBatchResults(response).map(data => this.objectRegistry.getOrCreateType(data)); } const data = await this.client.apiRequest("getTypeAtLocation", { snapshot: this.snapshotId, @@ -1784,13 +1787,12 @@ export class Checker { }); return data ? this.objectRegistry.getOrCreateType(data) : undefined; } - const data = await this.client.apiRequest("getTypesAtPositions", { - snapshot: this.snapshotId, - project: this.project.id, - file, - positions: positionOrPositions, - }); - return data.map(d => d ? this.objectRegistry.getOrCreateType(d) : undefined); + const response = await this.client.batchRequest(createGroupedBatchRequest( + "getTypeAtPosition", + { snapshot: this.snapshotId, project: this.project.id, file }, + { position: positionOrPositions }, + )); + return getBatchResults(response).map(data => data ? this.objectRegistry.getOrCreateType(data) : undefined); } async resolveName( diff --git a/packages/typescript/src/api/async/client.ts b/packages/typescript/src/api/async/client.ts index 03809285c9b4d..2efec00b15c03 100644 --- a/packages/typescript/src/api/async/client.ts +++ b/packages/typescript/src/api/async/client.ts @@ -39,6 +39,79 @@ import { export type { ClientOptions, ClientSocketOptions, ClientSpawnOptions }; +interface GroupedBatchRequest { + method: APIRequest["method"]; + base?: Record; + count: number; + fields?: Record; + requests?: unknown[]; +} + +function groupBatchRequests(requests: readonly T[]): { + groups: GroupedBatchRequest[]; + groupOrder: number[]; +} | undefined { + const groupIndexes = new Map(); + const requestsByMethod: T[][] = []; + const groupOrder: number[] = []; + for (const request of requests) { + let groupIndex = groupIndexes.get(request.method); + if (groupIndex === undefined) { + groupIndex = requestsByMethod.length; + groupIndexes.set(request.method, groupIndex); + requestsByMethod.push([]); + } + requestsByMethod[groupIndex].push(request); + groupOrder.push(groupIndex); + } + if (!requestsByMethod.some(group => group.length >= 4)) return undefined; + + const groups: GroupedBatchRequest[] = []; + for (const [method, groupIndex] of groupIndexes) { + const groupedRequests = requestsByMethod[groupIndex]; + const params = groupedRequests.map(request => request.params); + if (params.every(param => typeof param === "object" && param !== null && !Array.isArray(param))) { + const records = params as Record[]; + const base = commonParams(records); + const commonKeys = new Set(Object.keys(base)); + const deltas = records.map(param => Object.fromEntries(Object.entries(param).filter(([key]) => !commonKeys.has(key)))); + const fields = parameterColumns(deltas); + groups.push( + fields + ? { method, base, count: records.length, fields } + : { method, base, count: records.length, requests: deltas }, + ); + } + else { + groups.push({ method, count: params.length, requests: params }); + } + } + return { groups, groupOrder }; +} + +function parameterColumns(params: readonly Record[]): Record | undefined { + const keys = Object.keys(params[0]); + if ( + !params.every(param => { + const paramKeys = Object.keys(param); + return paramKeys.length === keys.length + && keys.every(key => Object.hasOwn(param, key) && param[key] !== undefined); + }) + ) return undefined; + return Object.fromEntries(keys.map(key => [key, params.map(param => param[key])])) as Record; +} + +function commonParams(params: readonly Record[]): Record { + const first = params[0]; + return Object.fromEntries( + Object.entries(first).filter(([key, value]) => { + const type = typeof value; + return (value === null || type !== "object" && type !== "undefined") + && params.every(param => Object.hasOwn(param, key) && param[key] === value); + }), + ); +} + /** * Client handles communication with the TypeScript API server * over STDIO (spawned process) or a Unix domain socket using JSON-RPC. @@ -220,34 +293,20 @@ export class Client { return; } - const requestType = new RequestType("batchRequests"); - const params: BatchRequestsParams = { requests: requests.map(request => ({ method: request.method, params: request.params })) }; - if (this.options.maxResponseBytesPerPage !== undefined) { - params.maxResponseBytesPerPage = this.options.maxResponseBytesPerPage; - } - const response = await this.sendRequestWithTiming(requestType, params); - let responses = response.responses; - let continuationToken = response.continuationToken; - while (continuationToken) { - const pageParams: BatchRequestsParams = { - requests: [], - continuationToken, - }; - if (this.options.maxResponseBytesPerPage !== undefined) { - pageParams.maxResponseBytesPerPage = this.options.maxResponseBytesPerPage; - } - const page = await this.sendRequestWithTiming(requestType, pageParams); - responses = responses.concat(page.responses); - continuationToken = page.continuationToken; - } + // Paired benchmarks show grouping pays for its construction cost at four requests. + const grouped = requests.length >= 4 ? groupBatchRequests(requests) : undefined; + const params: BatchRequestsParams | { groups: GroupedBatchRequest[]; groupOrder: number[]; maxResponseBytesPerPage?: number | undefined; } = grouped + ? { groups: grouped.groups, groupOrder: grouped.groupOrder, maxResponseBytesPerPage: this.options.maxResponseBytesPerPage } + : { requests: requests.map(request => ({ method: request.method, params: request.params })), maxResponseBytesPerPage: this.options.maxResponseBytesPerPage }; + const response = await this.batchRequest(params as BatchRequestsParams); for (let i = 0; i < requests.length; i++) { const { resolve, reject } = requests[i]; - const item = responses[i]; - if (item.error !== undefined) { - reject(new Error(item.error)); + const error = response.errors?.[i]; + if (error !== undefined) { + reject(new Error(error)); } else { - resolve(item.result); + resolve(response.results[i]); } } } @@ -256,6 +315,33 @@ export class Client { } } + async batchRequest(params: BatchRequestsParams): Promise { + if (this.closed) throw new Error("Client is closed"); + if (!this.connected) { + await this.connect(); + } + const requestType = new RequestType("batchRequests"); + const response = await this.sendRequestWithTiming(requestType, params); + let results = response.results; + let errors = response.errors; + let continuationToken = response.continuationToken; + while (continuationToken) { + const page = await this.sendRequestWithTiming(requestType, { + requests: [], + continuationToken, + maxResponseBytesPerPage: this.options.maxResponseBytesPerPage, + }); + const offset = results.length; + results = results.concat(page.results); + if (page.errors) { + errors ??= {}; + for (const [index, message] of Object.entries(page.errors)) errors[Number(index) + offset] = message; + } + continuationToken = page.continuationToken; + } + return errors ? { results, errors } : { results }; + } + private scheduleImmediateBatch(): void { if (this.nextBatch) return; this.nextBatch = setImmediate(this.doBatch.bind(this)); diff --git a/packages/typescript/src/api/batch.ts b/packages/typescript/src/api/batch.ts new file mode 100644 index 0000000000000..60fb96ad1b446 --- /dev/null +++ b/packages/typescript/src/api/batch.ts @@ -0,0 +1,37 @@ +import type { + APIRequest, + BatchRequestsParams, + BatchRequestsResponse, +} from "./proto.ts"; + +export function createGroupedBatchRequest( + method: APIRequest["method"], + base: Record, + fields: Record, +): BatchRequestsParams { + const entries = Object.entries(fields); + const count = entries[0]?.[1].length ?? 0; + if (!entries.every(([, values]) => values.length === count)) { + throw new Error("Grouped batch parameter columns must have equal lengths"); + } + if (count < 4) { + return { + requests: Array.from({ length: count }, (_, index) => ({ + method, + params: { ...base, ...Object.fromEntries(entries.map(([key, values]) => [key, values[index]])) }, + })), + }; + } + return { + groups: [{ method, base, count, fields: Object.fromEntries(entries.map(([key, values]) => [key, [...values]])) }], + groupOrder: Array(count).fill(0), + }; +} + +export function getBatchResults(response: BatchRequestsResponse): T[] { + return response.results.map((result, index) => { + const error = response.errors?.[index]; + if (error !== undefined) throw new Error(error); + return result as T; + }); +} diff --git a/packages/typescript/src/api/proto.generated.ts b/packages/typescript/src/api/proto.generated.ts index 5b910f578913c..c25e5d1fe15ee 100644 --- a/packages/typescript/src/api/proto.generated.ts +++ b/packages/typescript/src/api/proto.generated.ts @@ -33,13 +33,9 @@ export interface APIMethodInfo { transpileDeclarationFromFile: APIMethod; getDefaultProjectForFile: APIMethod; getSymbolAtPosition: APIMethod; - getSymbolsAtPositions: APIMethod; getSymbolAtLocation: APIMethod; - getSymbolsAtLocations: APIMethod; getSymbolOfSourceFile: APIMethod; - getSymbolsOfSourceFiles: APIMethod; getTypeOfSymbol: APIMethod; - getTypesOfSymbols: APIMethod; getDeclaredTypeOfSymbol: APIMethod; getNonMissingTypeOfSymbol: APIMethod; getSourceFile: APIMethod; @@ -56,9 +52,7 @@ export interface APIMethodInfo { getSignaturesOfType: APIMethod; getResolvedSignature: APIMethod; getTypeAtLocation: APIMethod; - getTypeAtLocations: APIMethod; getTypeAtPosition: APIMethod; - getTypesAtPositions: APIMethod; getParentOfSymbol: APIMethod; getMembersOfSymbol: APIMethod; getExportsOfSymbol: APIMethod; @@ -173,13 +167,16 @@ export interface ReleaseParams { } export interface BatchRequestsParams { - requests: readonly BatchRequest[] | null; + requests?: readonly BatchRequest[] | undefined; + groups?: readonly BatchRequestGroup[] | undefined; + groupOrder?: readonly number[] | undefined; continuationToken?: string | undefined; maxResponseBytesPerPage?: number | undefined; } export interface BatchRequestsResponse { - responses: BatchResponse[]; + results: unknown[]; + errors?: Record | undefined; continuationToken?: string | undefined; } @@ -361,37 +358,18 @@ export interface SymbolResponse { exportSymbol?: number | undefined; } -export interface GetSymbolsAtPositionsParams { - snapshot: number; - project: string; - file: DocumentIdentifier; - positions: readonly number[] | null; -} - export interface GetSymbolAtLocationParams { snapshot: number; project: string; location: string; } -export interface GetSymbolsAtLocationsParams { - snapshot: number; - project: string; - locations: readonly string[] | null; -} - export interface GetSymbolOfSourceFileParams { snapshot: number; project: string; file: DocumentIdentifier; } -export interface GetSymbolsOfSourceFilesParams { - snapshot: number; - project: string; - files: readonly DocumentIdentifier[] | null; -} - export interface GetTypeOfSymbolParams { snapshot: number; project: string; @@ -444,12 +422,6 @@ export interface TypeResponse { symbol?: number | undefined; } -export interface GetTypesOfSymbolsParams { - snapshot: number; - project: string; - symbols: readonly number[] | null; -} - export interface GetSourceFileParams { snapshot: number; project: string; @@ -597,12 +569,6 @@ export interface GetTypeAtLocationParams { location: string; } -export interface GetTypeAtLocationsParams { - snapshot: number; - project: string; - locations: readonly string[] | null; -} - export interface GetTypeAtPositionParams { snapshot: number; project: string; @@ -610,13 +576,6 @@ export interface GetTypeAtPositionParams { position: number; } -export interface GetTypesAtPositionsParams { - snapshot: number; - project: string; - file: DocumentIdentifier; - positions: readonly number[] | null; -} - /** GetSymbolPropertyParams is used for all symbol sub-property endpoints. */ export interface GetSymbolPropertyParams { snapshot: number; @@ -1066,10 +1025,7 @@ export interface BatchRequest { | "getSymbolAtPosition" | "getSymbolOfSourceFile" | "getSymbolOfType" - | "getSymbolsAtLocations" - | "getSymbolsAtPositions" | "getSymbolsInScope" - | "getSymbolsOfSourceFiles" | "getSyntacticDiagnostics" | "getTargetOfSignature" | "getTargetOfType" @@ -1078,7 +1034,6 @@ export interface BatchRequest { | "getTrueTypeOfConditionalType" | "getTypeArguments" | "getTypeAtLocation" - | "getTypeAtLocations" | "getTypeAtPosition" | "getTypeFromTypeNode" | "getTypeOfSymbol" @@ -1087,8 +1042,6 @@ export interface BatchRequest { | "getTypeParametersOfSignature" | "getTypeParametersOfType" | "getTypePredicateOfSignature" - | "getTypesAtPositions" - | "getTypesOfSymbols" | "getTypesOfType" | "getUndefinedType" | "getUnknownType" @@ -1124,7 +1077,7 @@ export interface BatchRequest { params?: unknown | undefined; } -export interface BatchResponse { +export interface BatchRequestGroup { method: | "batchRequests" | "createProgram" @@ -1217,10 +1170,7 @@ export interface BatchResponse { | "getSymbolAtPosition" | "getSymbolOfSourceFile" | "getSymbolOfType" - | "getSymbolsAtLocations" - | "getSymbolsAtPositions" | "getSymbolsInScope" - | "getSymbolsOfSourceFiles" | "getSyntacticDiagnostics" | "getTargetOfSignature" | "getTargetOfType" @@ -1229,7 +1179,6 @@ export interface BatchResponse { | "getTrueTypeOfConditionalType" | "getTypeArguments" | "getTypeAtLocation" - | "getTypeAtLocations" | "getTypeAtPosition" | "getTypeFromTypeNode" | "getTypeOfSymbol" @@ -1238,8 +1187,6 @@ export interface BatchResponse { | "getTypeParametersOfSignature" | "getTypeParametersOfType" | "getTypePredicateOfSignature" - | "getTypesAtPositions" - | "getTypesOfSymbols" | "getTypesOfType" | "getUndefinedType" | "getUnknownType" @@ -1272,8 +1219,10 @@ export interface BatchResponse { | "typeToTypeNode" | "updateSnapshot" | "updateTemporarySnapshot"; - result: unknown; - error?: string | undefined; + base?: unknown | undefined; + count: number; + fields?: unknown | undefined; + requests?: unknown[] | undefined; } /** diff --git a/packages/typescript/src/api/sync/api.ts b/packages/typescript/src/api/sync/api.ts index 222c7dd95c0c7..6b6b19bdc2e8a 100644 --- a/packages/typescript/src/api/sync/api.ts +++ b/packages/typescript/src/api/sync/api.ts @@ -51,6 +51,10 @@ import { unescapeLeadingUnderscores, } from "../../ast/index.ts"; import { assertNever } from "../../internal/utils.ts"; +import { + createGroupedBatchRequest, + getBatchResults, +} from "../batch.ts"; import { encodeNode, uint8ArrayToBase64, @@ -298,7 +302,7 @@ export class API implements FormatDiagnosticsHo } batch(...requestGenerators: T): ExecutedGeneratorsResults { - return executeRequestGenerators(requestGenerators, requests => this.client.batchRequests(requests).responses); + return executeRequestGenerators(requestGenerators, requests => this.client.batchRequests(requests)); } private get ensureInitialized(): { @@ -3099,12 +3103,12 @@ export class Checker { function getSymbolAtLocation(nodes: readonly Node[]): (Symbol | undefined)[]; function getSymbolAtLocation(nodeOrNodes: Node | readonly Node[]): Symbol | (Symbol | undefined)[] | undefined { if (Array.isArray(nodeOrNodes)) { - const data = owner.client.apiRequest("getSymbolsAtLocations", { - snapshot: owner.snapshotId, - project: owner.project.id, - locations: nodeOrNodes.map(node => getNodeId(node)), - }); - return data.map(d => d ? owner.objectRegistry.getOrCreateSymbol(d) : undefined); + const response = owner.client.batchRequest(createGroupedBatchRequest( + "getSymbolAtLocation", + { snapshot: owner.snapshotId, project: owner.project.id }, + { location: nodeOrNodes.map(node => getNodeId(node)) }, + )); + return getBatchResults(response).map(data => data ? owner.objectRegistry.getOrCreateSymbol(data) : undefined); } const data = owner.client.apiRequest("getSymbolAtLocation", { snapshot: owner.snapshotId, @@ -3117,12 +3121,12 @@ export class Checker { function gen(nodes: readonly Node[]): Generator; function* gen(nodeOrNodes: Node | readonly Node[]): Generator { if (Array.isArray(nodeOrNodes)) { - const data = yield* apiRequest("getSymbolsAtLocations", { - snapshot: owner.snapshotId, - project: owner.project.id, - locations: nodeOrNodes.map(node => getNodeId(node)), - }); - return data.map(d => d ? owner.objectRegistry.getOrCreateSymbol(d) : undefined); + const response = owner.client.batchRequest(createGroupedBatchRequest( + "getSymbolAtLocation", + { snapshot: owner.snapshotId, project: owner.project.id }, + { location: nodeOrNodes.map(node => getNodeId(node)) }, + )); + return getBatchResults(response).map(data => data ? owner.objectRegistry.getOrCreateSymbol(data) : undefined); } const data = yield* apiRequest("getSymbolAtLocation", { snapshot: owner.snapshotId, @@ -3153,13 +3157,12 @@ export class Checker { }); return data ? owner.objectRegistry.getOrCreateSymbol(data) : undefined; } - const data = owner.client.apiRequest("getSymbolsAtPositions", { - snapshot: owner.snapshotId, - project: owner.project.id, - file, - positions: positionOrPositions, - }); - return data.map(d => d ? owner.objectRegistry.getOrCreateSymbol(d) : undefined); + const response = owner.client.batchRequest(createGroupedBatchRequest( + "getSymbolAtPosition", + { snapshot: owner.snapshotId, project: owner.project.id, file }, + { position: positionOrPositions }, + )); + return getBatchResults(response).map(data => data ? owner.objectRegistry.getOrCreateSymbol(data) : undefined); } function gen(file: DocumentIdentifier, position: number): Generator; function gen(file: DocumentIdentifier, positions: readonly number[]): Generator; @@ -3173,13 +3176,12 @@ export class Checker { }); return data ? owner.objectRegistry.getOrCreateSymbol(data) : undefined; } - const data = yield* apiRequest("getSymbolsAtPositions", { - snapshot: owner.snapshotId, - project: owner.project.id, - file, - positions: positionOrPositions, - }); - return data.map(d => d ? owner.objectRegistry.getOrCreateSymbol(d) : undefined); + const response = owner.client.batchRequest(createGroupedBatchRequest( + "getSymbolAtPosition", + { snapshot: owner.snapshotId, project: owner.project.id, file }, + { position: positionOrPositions }, + )); + return getBatchResults(response).map(data => data ? owner.objectRegistry.getOrCreateSymbol(data) : undefined); } return cacheGeneratorMethod(owner, "getSymbolAtPosition", getSymbolAtPosition, gen); } @@ -3195,12 +3197,12 @@ export class Checker { function getSymbolOfSourceFile(files: readonly DocumentIdentifier[]): (Symbol | undefined)[]; function getSymbolOfSourceFile(fileOrFiles: DocumentIdentifier | readonly DocumentIdentifier[]): Symbol | (Symbol | undefined)[] | undefined { if (Array.isArray(fileOrFiles)) { - const data = owner.client.apiRequest("getSymbolsOfSourceFiles", { - snapshot: owner.snapshotId, - project: owner.project.id, - files: fileOrFiles, - }); - return data.map(d => d ? owner.objectRegistry.getOrCreateSymbol(d) : undefined); + const response = owner.client.batchRequest(createGroupedBatchRequest( + "getSymbolOfSourceFile", + { snapshot: owner.snapshotId, project: owner.project.id }, + { file: fileOrFiles }, + )); + return getBatchResults(response).map(data => data ? owner.objectRegistry.getOrCreateSymbol(data) : undefined); } const data = owner.client.apiRequest("getSymbolOfSourceFile", { snapshot: owner.snapshotId, @@ -3213,12 +3215,12 @@ export class Checker { function gen(files: readonly DocumentIdentifier[]): Generator; function* gen(fileOrFiles: DocumentIdentifier | readonly DocumentIdentifier[]): Generator { if (Array.isArray(fileOrFiles)) { - const data = yield* apiRequest("getSymbolsOfSourceFiles", { - snapshot: owner.snapshotId, - project: owner.project.id, - files: fileOrFiles, - }); - return data.map(d => d ? owner.objectRegistry.getOrCreateSymbol(d) : undefined); + const response = owner.client.batchRequest(createGroupedBatchRequest( + "getSymbolOfSourceFile", + { snapshot: owner.snapshotId, project: owner.project.id }, + { file: fileOrFiles }, + )); + return getBatchResults(response).map(data => data ? owner.objectRegistry.getOrCreateSymbol(data) : undefined); } const data = yield* apiRequest("getSymbolOfSourceFile", { snapshot: owner.snapshotId, @@ -3246,12 +3248,12 @@ export class Checker { function getTypeOfSymbol(symbols: readonly Symbol[]): Type[]; function getTypeOfSymbol(symbolOrSymbols: Symbol | readonly Symbol[]): Type | Type[] { if (Array.isArray(symbolOrSymbols)) { - const data = owner.client.apiRequest("getTypesOfSymbols", { - snapshot: owner.snapshotId, - project: owner.project.id, - symbols: symbolOrSymbols.map(s => s.id), - }); - return data.map(d => owner.objectRegistry.getOrCreateType(d)); + const response = owner.client.batchRequest(createGroupedBatchRequest( + "getTypeOfSymbol", + { snapshot: owner.snapshotId, project: owner.project.id }, + { symbol: symbolOrSymbols.map(symbol => symbol.id) }, + )); + return getBatchResults(response).map(data => owner.objectRegistry.getOrCreateType(data)); } const data = owner.client.apiRequest("getTypeOfSymbol", { snapshot: owner.snapshotId, @@ -3264,12 +3266,12 @@ export class Checker { function gen(symbols: readonly Symbol[]): Generator; function* gen(symbolOrSymbols: Symbol | readonly Symbol[]): Generator { if (Array.isArray(symbolOrSymbols)) { - const data = yield* apiRequest("getTypesOfSymbols", { - snapshot: owner.snapshotId, - project: owner.project.id, - symbols: symbolOrSymbols.map(s => s.id), - }); - return data.map(d => owner.objectRegistry.getOrCreateType(d)); + const response = owner.client.batchRequest(createGroupedBatchRequest( + "getTypeOfSymbol", + { snapshot: owner.snapshotId, project: owner.project.id }, + { symbol: symbolOrSymbols.map(symbol => symbol.id) }, + )); + return getBatchResults(response).map(data => owner.objectRegistry.getOrCreateType(data)); } const data = yield* apiRequest("getTypeOfSymbol", { snapshot: owner.snapshotId, @@ -3445,12 +3447,12 @@ export class Checker { function getTypeAtLocation(nodes: readonly Node[]): Type[]; function getTypeAtLocation(nodeOrNodes: Node | readonly Node[]): Type | Type[] { if (Array.isArray(nodeOrNodes)) { - const data = owner.client.apiRequest("getTypeAtLocations", { - snapshot: owner.snapshotId, - project: owner.project.id, - locations: nodeOrNodes.map(node => getNodeId(node)), - }); - return data.map(d => owner.objectRegistry.getOrCreateType(d)); + const response = owner.client.batchRequest(createGroupedBatchRequest( + "getTypeAtLocation", + { snapshot: owner.snapshotId, project: owner.project.id }, + { location: nodeOrNodes.map(node => getNodeId(node)) }, + )); + return getBatchResults(response).map(data => owner.objectRegistry.getOrCreateType(data)); } const data = owner.client.apiRequest("getTypeAtLocation", { snapshot: owner.snapshotId, @@ -3463,12 +3465,12 @@ export class Checker { function gen(nodes: readonly Node[]): Generator; function* gen(nodeOrNodes: Node | readonly Node[]): Generator { if (Array.isArray(nodeOrNodes)) { - const data = yield* apiRequest("getTypeAtLocations", { - snapshot: owner.snapshotId, - project: owner.project.id, - locations: nodeOrNodes.map(node => getNodeId(node)), - }); - return data.map(d => owner.objectRegistry.getOrCreateType(d)); + const response = owner.client.batchRequest(createGroupedBatchRequest( + "getTypeAtLocation", + { snapshot: owner.snapshotId, project: owner.project.id }, + { location: nodeOrNodes.map(node => getNodeId(node)) }, + )); + return getBatchResults(response).map(data => owner.objectRegistry.getOrCreateType(data)); } const data = yield* apiRequest("getTypeAtLocation", { snapshot: owner.snapshotId, @@ -3548,13 +3550,12 @@ export class Checker { }); return data ? owner.objectRegistry.getOrCreateType(data) : undefined; } - const data = owner.client.apiRequest("getTypesAtPositions", { - snapshot: owner.snapshotId, - project: owner.project.id, - file, - positions: positionOrPositions, - }); - return data.map(d => d ? owner.objectRegistry.getOrCreateType(d) : undefined); + const response = owner.client.batchRequest(createGroupedBatchRequest( + "getTypeAtPosition", + { snapshot: owner.snapshotId, project: owner.project.id, file }, + { position: positionOrPositions }, + )); + return getBatchResults(response).map(data => data ? owner.objectRegistry.getOrCreateType(data) : undefined); } function gen(file: DocumentIdentifier, position: number): Generator; function gen(file: DocumentIdentifier, positions: readonly number[]): Generator; @@ -3568,13 +3569,12 @@ export class Checker { }); return data ? owner.objectRegistry.getOrCreateType(data) : undefined; } - const data = yield* apiRequest("getTypesAtPositions", { - snapshot: owner.snapshotId, - project: owner.project.id, - file, - positions: positionOrPositions, - }); - return data.map(d => d ? owner.objectRegistry.getOrCreateType(d) : undefined); + const response = owner.client.batchRequest(createGroupedBatchRequest( + "getTypeAtPosition", + { snapshot: owner.snapshotId, project: owner.project.id, file }, + { position: positionOrPositions }, + )); + return getBatchResults(response).map(data => data ? owner.objectRegistry.getOrCreateType(data) : undefined); } return cacheGeneratorMethod(owner, "getTypeAtPosition", getTypeAtPosition, gen); } diff --git a/packages/typescript/src/api/sync/client.ts b/packages/typescript/src/api/sync/client.ts index b174784d7006e..bdcce7b1392c6 100644 --- a/packages/typescript/src/api/sync/client.ts +++ b/packages/typescript/src/api/sync/client.ts @@ -101,13 +101,21 @@ export class Client { return undefined as APIMethodInfo[K]["result"]; } - batchRequests(requests: readonly APIRequest[]): BatchRequestsResponse { - const params: BatchRequestsParams = { requests }; + batchRequests(requests: readonly APIRequest[]): { result: unknown; error?: string | undefined; }[] { + const response = this.batchRequest({ requests }); + return response.results.map((result, index) => { + const error = response.errors?.[index]; + return error === undefined ? { result } : { result, error }; + }); + } + + batchRequest(params: BatchRequestsParams): BatchRequestsResponse { if (this.maxResponseBytesPerPage !== undefined) { params.maxResponseBytesPerPage = this.maxResponseBytesPerPage; } const response = this.apiRequest("batchRequests", params); - let responses = response.responses; + let results = response.results; + let errors = response.errors; let continuationToken = response.continuationToken; while (continuationToken) { const pageParams: BatchRequestsParams = { @@ -118,16 +126,15 @@ export class Client { pageParams.maxResponseBytesPerPage = this.maxResponseBytesPerPage; } const page = this.apiRequest("batchRequests", pageParams); - if (page.responses.length < 200) { - responses.push(...page.responses); - } - else { - // If the number of responses is approaching the max argument length, we need to concat instead of push - responses = responses.concat(page.responses); + const offset = results.length; + results = results.concat(page.results); + if (page.errors) { + errors ??= {}; + for (const [index, message] of Object.entries(page.errors)) errors[Number(index) + offset] = message; } continuationToken = page.continuationToken; } - return { responses }; + return errors ? { results, errors } : { results }; } apiRequestBinary(method: K, params?: APIMethodInfo[K]["params"]): Uint8Array | undefined { diff --git a/packages/typescript/test/async/api.test.ts b/packages/typescript/test/async/api.test.ts index 0dacd55c1c1e7..2892f55952b44 100644 --- a/packages/typescript/test/async/api.test.ts +++ b/packages/typescript/test/async/api.test.ts @@ -680,6 +680,57 @@ describe("API - automatic batching", () => { assert.equal(parseCommandLine.options.strict, true); assert.deepStrictEqual(readConfigFile.config, {}); }); + + test("coalesces position requests with shared context", async () => { + await using api = spawnAPI({ ...defaultFiles }, { collectTiming: true }); + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + await api.resetTimingInfo(); + + const symbols = await Promise.all([ + project.checker.getSymbolAtPosition("/src/index.ts", 9), + project.checker.getSymbolAtPosition("/src/index.ts", 9), + project.checker.getSymbolAtPosition("/src/index.ts", 9), + project.checker.getSymbolAtPosition("/src/index.ts", 9), + ]); + + assert.ok(symbols.every(symbol => symbol?.id === symbols[0]?.id)); + const timing = await api.getTimingInfo(); + assert.equal(timing.totals.requestCount, 1); + assert.equal(timing.recentRequests[0].method, "batchRequests"); + + await api.resetTimingInfo(); + const types = await Promise.all([ + project.checker.getTypeAtPosition("/src/index.ts", 9), + project.checker.getTypeAtPosition("/src/index.ts", 9), + project.checker.getTypeAtPosition("/src/index.ts", 9), + project.checker.getTypeAtPosition("/src/index.ts", 9), + ]); + + assert.ok(types.every(type => type?.id === types[0]?.id)); + const typeTiming = await api.getTimingInfo(); + assert.equal(typeTiming.totals.requestCount, 1); + assert.equal(typeTiming.recentRequests[0].method, "batchRequests"); + + await api.resetTimingInfo(); + await Promise.all([ + project.checker.getSymbolAtPosition("/src/index.ts", 9), + project.checker.getSymbolAtPosition("/src/foo.ts", 13), + project.checker.getSymbolAtPosition("/src/index.ts", 9), + project.checker.getSymbolAtPosition("/src/foo.ts", 13), + ]); + const mixedTiming = await api.getTimingInfo(); + assert.equal(mixedTiming.totals.requestCount, 1); + assert.equal(mixedTiming.recentRequests[0].method, "batchRequests"); + + const settled = await Promise.allSettled([ + project.checker.getSymbolAtPosition("/src/index.ts", 9), + project.checker.getSymbolAtPosition("/src/missing.ts", 0), + project.checker.getSymbolAtPosition("/src/foo.ts", 13), + project.checker.getSymbolAtPosition("/src/index.ts", 9), + ]); + assert.deepEqual(settled.map(result => result.status), ["fulfilled", "rejected", "fulfilled", "fulfilled"]); + }); }); describe("API - batchContext", () => { @@ -692,13 +743,15 @@ describe("API - batchContext", () => { api.parseCommandLine(["--strict"]), api.readConfigFile("/tsconfig.json"), api.parseCommandLine(["--noImplicitAny"]), + api.parseCommandLine(["--allowJs"]), ] as const; })(); - const [strict, config, noImplicitAny] = await Promise.all(requests); + const [strict, config, noImplicitAny, allowJs] = await Promise.all(requests); assert.equal(strict.options.strict, true); assert.deepEqual(config.config, {}); assert.equal(noImplicitAny.options.noImplicitAny, true); + assert.equal(allowJs.options.allowJs, true); } finally { await api.close(); diff --git a/tools/gen-proto/main.go b/tools/gen-proto/main.go index ea26612e6fad1..f7b6e1196c744 100644 --- a/tools/gen-proto/main.go +++ b/tools/gen-proto/main.go @@ -6,7 +6,9 @@ import ( "fmt" "go/ast" "go/constant" + "go/format" "go/types" + "maps" "os" "path/filepath" "reflect" @@ -23,18 +25,23 @@ func main() { } func run() int { - if len(os.Args) != 3 { - fmt.Fprintln(os.Stderr, "Usage: gen-proto .go .ts") + if len(os.Args) != 4 { + fmt.Fprintln(os.Stderr, "Usage: gen-proto .go .ts .go") return 1 } if err := generate(os.Args[1], os.Args[2]); err != nil { fmt.Fprintln(os.Stderr, err) return 1 } + if err := generateBatchDecoders(os.Args[1], os.Args[3]); err != nil { + fmt.Fprintln(os.Stderr, err) + return 1 + } return 0 } type methodInfo struct { + constName string name string params types.Type result types.Type @@ -147,6 +154,158 @@ func generate(inputPath string, outputPath string) error { return nil } +func generateBatchDecoders(inputPath string, outputPath string) error { + absInput, err := filepath.Abs(inputPath) + if err != nil { + return err + } + pkg, inputFile, err := loadAPIPackage(absInput) + if err != nil { + return err + } + methods, methodObjects, err := declaredMethods(pkg, inputFile) + if err != nil { + return err + } + resultTypeOverrides, nullableResults, err := discoverResultMetadata(pkg) + if err != nil { + return err + } + discoverSessionMethods(pkg, methodObjects, methods, resultTypeOverrides, nullableResults) + + type decoderType struct { + name string + named *types.Named + fields []batchField + } + decoderTypes := make(map[*types.Named]*decoderType) + methodTypes := make(map[*methodInfo]*decoderType) + imports := make(map[string]string) + qualifier := func(imported *types.Package) string { + if imported == nil || imported.Path() == pkg.Types.Path() { + return "" + } + imports[imported.Path()] = imported.Name() + return imported.Name() + } + + for _, method := range methods { + if method.constName == "MethodBatchRequests" { + continue + } + pointer, ok := types.Unalias(method.params).(*types.Pointer) + if !ok { + continue + } + named, ok := types.Unalias(pointer.Elem()).(*types.Named) + if !ok || named.Obj().Pkg() != pkg.Types { + continue + } + structType, ok := named.Underlying().(*types.Struct) + if !ok { + continue + } + decoder := decoderTypes[named] + if decoder == nil { + decoder = &decoderType{name: named.Obj().Name(), named: named} + for i := range structType.NumFields() { + field := structType.Field(i) + if !field.Exported() || field.Embedded() { + continue + } + jsonName, _, _ := strings.Cut(reflect.StructTag(structType.Tag(i)).Get("json"), ",") + if jsonName == "" || jsonName == "-" { + continue + } + decoder.fields = append(decoder.fields, batchField{ + goName: field.Name(), + jsonName: jsonName, + typeName: types.TypeString(field.Type(), qualifier), + }) + } + decoderTypes[named] = decoder + } + methodTypes[method] = decoder + } + + var body bytes.Buffer + decoders := slices.Collect(maps.Values(decoderTypes)) + slices.SortFunc(decoders, func(a, b *decoderType) int { return strings.Compare(a.name, b.name) }) + for _, decoder := range decoders { + fmt.Fprintf(&body, "type batchColumns%s struct {\n", decoder.name) + for _, field := range decoder.fields { + fmt.Fprintf(&body, "\t%s []%s `json:\"%s,omitempty\"`\n", field.goName, field.typeName, field.jsonName) + } + body.WriteString("}\n\n") + fmt.Fprintf(&body, "func newBatchDecoder%s(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) {\n", decoder.name) + fmt.Fprintf(&body, "\tvar columns batchColumns%s\n", decoder.name) + body.WriteString("\tif err := json.Unmarshal(fields, &columns); err != nil {\n\t\treturn nil, err\n\t}\n") + for _, field := range decoder.fields { + fmt.Fprintf(&body, "\tif err := validateBatchColumn(\"%s\", len(columns.%s), count); err != nil { return nil, err }\n", field.jsonName, field.goName) + } + fmt.Fprintf(&body, "\treturn newTypedBatchRequestDecoder[%s](base, func(params *%s, index int) {\n", decoder.name, decoder.name) + for _, field := range decoder.fields { + fmt.Fprintf(&body, "\t\tif columns.%s != nil { params.%s = columns.%s[index] }\n", field.goName, field.goName, field.goName) + } + body.WriteString("\t})\n}\n\n") + } + body.WriteString("func newGeneratedBatchRequestDecoder(method Method, base json.Value, fields json.Value, count int) (batchRequestDecoder, error) {\n\tswitch method {\n") + for _, method := range methods { + if decoder := methodTypes[method]; decoder != nil { + fmt.Fprintf(&body, "\tcase %s:\n\t\treturn newBatchDecoder%s(base, fields, count)\n", method.constName, decoder.name) + } + } + body.WriteString("\tdefault:\n\t\treturn nil, nil\n\t}\n}\n") + + var out bytes.Buffer + out.WriteString("// Code generated by tools/gen-proto. DO NOT EDIT.\n\npackage api\n\n") + out.WriteString("import (\n\t\"github.com/microsoft/TypeScript/tsc/internal/json\"\n") + paths := slices.Sorted(maps.Keys(imports)) + for _, path := range paths { + if path == "github.com/microsoft/TypeScript/tsc/internal/json" { + continue + } + fmt.Fprintf(&out, "\t%s \"%s\"\n", imports[path], path) + } + out.WriteString(")\n\n") + out.Write(body.Bytes()) + formatted, err := format.Source(out.Bytes()) + if err != nil { + return fmt.Errorf("format batch decoders: %w\n%s", err, out.String()) + } + return os.WriteFile(outputPath, formatted, 0o644) +} + +type batchField struct { + goName string + jsonName string + typeName string +} + +func loadAPIPackage(absInput string) (*packages.Package, *ast.File, error) { + cfg := &packages.Config{ + Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | + packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo | + packages.NeedImports | packages.NeedDeps, + Dir: filepath.Dir(absInput), + } + pkgs, err := packages.Load(cfg, ".") + if err != nil { + return nil, nil, fmt.Errorf("load API package: %w", err) + } + if len(pkgs) != 1 { + return nil, nil, fmt.Errorf("load API package: expected one package, got %d", len(pkgs)) + } + if packages.PrintErrors(pkgs) != 0 { + return nil, nil, errors.New("load API package: package contains errors") + } + inputFile := findSyntaxFile(pkgs[0], absInput) + if inputFile == nil { + return nil, nil, fmt.Errorf("input file %q was not part of package %q", absInput, pkgs[0].PkgPath) + } + return pkgs[0], inputFile, nil +} + func findSyntaxFile(pkg *packages.Package, path string) *ast.File { cleanPath := filepath.Clean(path) for i, filePath := range pkg.CompiledGoFiles { @@ -176,7 +335,7 @@ func declaredMethods(pkg *packages.Package, file *ast.File) ([]*methodInfo, map[ if !ok || constantObject.Val().Kind() != constant.String { return nil, nil, fmt.Errorf("%s is not a string method constant", name.Name) } - method := &methodInfo{name: constant.StringVal(constantObject.Val())} + method := &methodInfo{constName: name.Name, name: constant.StringVal(constantObject.Val())} methods = append(methods, method) methodObjects[obj] = method } diff --git a/tools/gen-proto/main_test.go b/tools/gen-proto/main_test.go index affa05e857a52..2fa6edb5854c2 100644 --- a/tools/gen-proto/main_test.go +++ b/tools/gen-proto/main_test.go @@ -13,6 +13,7 @@ func TestGenerate(t *testing.T) { repoRoot := filepath.Clean(filepath.Join("..", "..")) input := filepath.Join(repoRoot, "tsc", "internal", "api", "proto.go") output := filepath.Join(t.TempDir(), "proto.generated.ts") + batchOutput := filepath.Join(t.TempDir(), "batch_decoder_generated.go") err := generate(input, output) if err != nil { @@ -49,7 +50,6 @@ export interface InitializeResponse`, export interface CompilerOptions`, `projectReferences?: ProjectReference[] | undefined;`, `errors: DiagnosticResponse[];`, - `getSymbolsAtPositions: APIMethod;`, `getContextualType: APIMethod;`, `getTypePredicateOfSignature: APIMethod;`, `getTypeParametersOfType: APIMethod;`, @@ -96,4 +96,30 @@ export interface CompilerOptions`, if string(first) != string(second) { t.Error("generation is not deterministic") } + + err = generateBatchDecoders(input, batchOutput) + if err != nil { + t.Fatal(err) + } + batchFirst, err := os.ReadFile(batchOutput) + if err != nil { + t.Fatal(err) + } + if strings.Contains(string(batchFirst), "reflect") { + t.Error("generated batch decoders must not use reflection") + } + if !strings.Contains(string(batchFirst), "newBatchDecoderGetSymbolAtPositionParams") { + t.Error("generated batch decoders do not include getSymbolAtPosition params") + } + err = generateBatchDecoders(input, batchOutput) + if err != nil { + t.Fatal(err) + } + batchSecond, err := os.ReadFile(batchOutput) + if err != nil { + t.Fatal(err) + } + if string(batchFirst) != string(batchSecond) { + t.Error("batch decoder generation is not deterministic") + } } diff --git a/tsc/internal/api/batch_decoder_generated.go b/tsc/internal/api/batch_decoder_generated.go new file mode 100644 index 0000000000000..ed379cca6adfc --- /dev/null +++ b/tsc/internal/api/batch_decoder_generated.go @@ -0,0 +1,2420 @@ +// Code generated by tools/gen-proto. DO NOT EDIT. + +package api + +import ( + requestfilesystem "github.com/microsoft/TypeScript/tsc/internal/api/requestfilesystem" + core "github.com/microsoft/TypeScript/tsc/internal/core" + "github.com/microsoft/TypeScript/tsc/internal/json" + packagejson "github.com/microsoft/TypeScript/tsc/internal/packagejson" +) + +type batchColumnsCheckerNodeParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Location []NodeHandle `json:"location,omitempty"` +} + +func newBatchDecoderCheckerNodeParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsCheckerNodeParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[CheckerNodeParams](base, func(params *CheckerNodeParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Location != nil { + params.Location = columns.Location[index] + } + }) +} + +type batchColumnsCheckerSignatureParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Signature []SignatureID `json:"signature,omitempty"` +} + +func newBatchDecoderCheckerSignatureParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsCheckerSignatureParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("signature", len(columns.Signature), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[CheckerSignatureParams](base, func(params *CheckerSignatureParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Signature != nil { + params.Signature = columns.Signature[index] + } + }) +} + +type batchColumnsCheckerSymbolParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Symbol []SymbolID `json:"symbol,omitempty"` +} + +func newBatchDecoderCheckerSymbolParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsCheckerSymbolParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("symbol", len(columns.Symbol), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[CheckerSymbolParams](base, func(params *CheckerSymbolParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Symbol != nil { + params.Symbol = columns.Symbol[index] + } + }) +} + +type batchColumnsCheckerTypeParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Type []TypeID `json:"type,omitempty"` +} + +func newBatchDecoderCheckerTypeParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsCheckerTypeParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("type", len(columns.Type), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[CheckerTypeParams](base, func(params *CheckerTypeParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Type != nil { + params.Type = columns.Type[index] + } + }) +} + +type batchColumnsCreateProgramParams struct { + RootFiles [][]DocumentIdentifier `json:"rootFiles,omitempty"` + CreateProgramOptions []CreateProgramOptions `json:"createProgramOptions,omitempty"` + OldProgram []*CreateProgramOldProgramParams `json:"oldProgram,omitempty"` + FileChanges []*APIFileChanges `json:"fileChanges,omitempty"` +} + +func newBatchDecoderCreateProgramParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsCreateProgramParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("rootFiles", len(columns.RootFiles), count); err != nil { + return nil, err + } + if err := validateBatchColumn("createProgramOptions", len(columns.CreateProgramOptions), count); err != nil { + return nil, err + } + if err := validateBatchColumn("oldProgram", len(columns.OldProgram), count); err != nil { + return nil, err + } + if err := validateBatchColumn("fileChanges", len(columns.FileChanges), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[CreateProgramParams](base, func(params *CreateProgramParams, index int) { + if columns.RootFiles != nil { + params.RootFiles = columns.RootFiles[index] + } + if columns.CreateProgramOptions != nil { + params.CreateProgramOptions = columns.CreateProgramOptions[index] + } + if columns.OldProgram != nil { + params.OldProgram = columns.OldProgram[index] + } + if columns.FileChanges != nil { + params.FileChanges = columns.FileChanges[index] + } + }) +} + +type batchColumnsEmitParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + EmitOnly []*uint32 `json:"emitOnly,omitempty"` +} + +func newBatchDecoderEmitParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsEmitParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("emitOnly", len(columns.EmitOnly), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[EmitParams](base, func(params *EmitParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.EmitOnly != nil { + params.EmitOnly = columns.EmitOnly[index] + } + }) +} + +type batchColumnsFormatNodeForInsertionParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + File []DocumentIdentifier `json:"file,omitempty"` + Position []uint32 `json:"position,omitempty"` + Data []string `json:"data,omitempty"` +} + +func newBatchDecoderFormatNodeForInsertionParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsFormatNodeForInsertionParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } + if err := validateBatchColumn("position", len(columns.Position), count); err != nil { + return nil, err + } + if err := validateBatchColumn("data", len(columns.Data), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[FormatNodeForInsertionParams](base, func(params *FormatNodeForInsertionParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.File != nil { + params.File = columns.File[index] + } + if columns.Position != nil { + params.Position = columns.Position[index] + } + if columns.Data != nil { + params.Data = columns.Data[index] + } + }) +} + +type batchColumnsGetBaseTypeOfLiteralTypeParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Type []TypeID `json:"type,omitempty"` +} + +func newBatchDecoderGetBaseTypeOfLiteralTypeParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetBaseTypeOfLiteralTypeParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("type", len(columns.Type), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetBaseTypeOfLiteralTypeParams](base, func(params *GetBaseTypeOfLiteralTypeParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Type != nil { + params.Type = columns.Type[index] + } + }) +} + +type batchColumnsGetCompletionsAtPositionParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + File []DocumentIdentifier `json:"file,omitempty"` + Position []uint32 `json:"position,omitempty"` + TriggerCharacter []*string `json:"triggerCharacter,omitempty"` + IncludeSymbol []bool `json:"includeSymbol,omitempty"` +} + +func newBatchDecoderGetCompletionsAtPositionParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetCompletionsAtPositionParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } + if err := validateBatchColumn("position", len(columns.Position), count); err != nil { + return nil, err + } + if err := validateBatchColumn("triggerCharacter", len(columns.TriggerCharacter), count); err != nil { + return nil, err + } + if err := validateBatchColumn("includeSymbol", len(columns.IncludeSymbol), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetCompletionsAtPositionParams](base, func(params *GetCompletionsAtPositionParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.File != nil { + params.File = columns.File[index] + } + if columns.Position != nil { + params.Position = columns.Position[index] + } + if columns.TriggerCharacter != nil { + params.TriggerCharacter = columns.TriggerCharacter[index] + } + if columns.IncludeSymbol != nil { + params.IncludeSymbol = columns.IncludeSymbol[index] + } + }) +} + +type batchColumnsGetContextualTypeParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Location []NodeHandle `json:"location,omitempty"` +} + +func newBatchDecoderGetContextualTypeParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetContextualTypeParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetContextualTypeParams](base, func(params *GetContextualTypeParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Location != nil { + params.Location = columns.Location[index] + } + }) +} + +type batchColumnsGetDefaultProjectForFileParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + File []DocumentIdentifier `json:"file,omitempty"` +} + +func newBatchDecoderGetDefaultProjectForFileParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetDefaultProjectForFileParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetDefaultProjectForFileParams](base, func(params *GetDefaultProjectForFileParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.File != nil { + params.File = columns.File[index] + } + }) +} + +type batchColumnsGetDiagnosticsParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Files [][]DocumentIdentifier `json:"files,omitempty"` +} + +func newBatchDecoderGetDiagnosticsParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetDiagnosticsParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("files", len(columns.Files), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetDiagnosticsParams](base, func(params *GetDiagnosticsParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Files != nil { + params.Files = columns.Files[index] + } + }) +} + +type batchColumnsGetImportAdderEditsParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + File []DocumentIdentifier `json:"file,omitempty"` + Actions [][]ImportAdderAction `json:"actions,omitempty"` +} + +func newBatchDecoderGetImportAdderEditsParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetImportAdderEditsParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } + if err := validateBatchColumn("actions", len(columns.Actions), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetImportAdderEditsParams](base, func(params *GetImportAdderEditsParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.File != nil { + params.File = columns.File[index] + } + if columns.Actions != nil { + params.Actions = columns.Actions[index] + } + }) +} + +type batchColumnsGetIntrinsicTypeParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` +} + +func newBatchDecoderGetIntrinsicTypeParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetIntrinsicTypeParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetIntrinsicTypeParams](base, func(params *GetIntrinsicTypeParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + }) +} + +type batchColumnsGetMemberInModuleExportsParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Symbol []SymbolID `json:"symbol,omitempty"` + Name []string `json:"name,omitempty"` +} + +func newBatchDecoderGetMemberInModuleExportsParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetMemberInModuleExportsParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("symbol", len(columns.Symbol), count); err != nil { + return nil, err + } + if err := validateBatchColumn("name", len(columns.Name), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetMemberInModuleExportsParams](base, func(params *GetMemberInModuleExportsParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Symbol != nil { + params.Symbol = columns.Symbol[index] + } + if columns.Name != nil { + params.Name = columns.Name[index] + } + }) +} + +type batchColumnsGetParameterTypeParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Signature []SignatureID `json:"signature,omitempty"` + Index []int32 `json:"index,omitempty"` +} + +func newBatchDecoderGetParameterTypeParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetParameterTypeParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("signature", len(columns.Signature), count); err != nil { + return nil, err + } + if err := validateBatchColumn("index", len(columns.Index), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetParameterTypeParams](base, func(params *GetParameterTypeParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Signature != nil { + params.Signature = columns.Signature[index] + } + if columns.Index != nil { + params.Index = columns.Index[index] + } + }) +} + +type batchColumnsGetProjectDiagnosticsParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` +} + +func newBatchDecoderGetProjectDiagnosticsParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetProjectDiagnosticsParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetProjectDiagnosticsParams](base, func(params *GetProjectDiagnosticsParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + }) +} + +type batchColumnsGetPropertyOfTypeParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Type []TypeID `json:"type,omitempty"` + Name []string `json:"name,omitempty"` +} + +func newBatchDecoderGetPropertyOfTypeParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetPropertyOfTypeParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("type", len(columns.Type), count); err != nil { + return nil, err + } + if err := validateBatchColumn("name", len(columns.Name), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetPropertyOfTypeParams](base, func(params *GetPropertyOfTypeParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Type != nil { + params.Type = columns.Type[index] + } + if columns.Name != nil { + params.Name = columns.Name[index] + } + }) +} + +type batchColumnsGetReferencedSymbolsForNodeParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Node []NodeHandle `json:"node,omitempty"` + Position []int `json:"position,omitempty"` +} + +func newBatchDecoderGetReferencedSymbolsForNodeParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetReferencedSymbolsForNodeParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("node", len(columns.Node), count); err != nil { + return nil, err + } + if err := validateBatchColumn("position", len(columns.Position), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetReferencedSymbolsForNodeParams](base, func(params *GetReferencedSymbolsForNodeParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Node != nil { + params.Node = columns.Node[index] + } + if columns.Position != nil { + params.Position = columns.Position[index] + } + }) +} + +type batchColumnsGetReferencesToSymbolInFileParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + File []DocumentIdentifier `json:"file,omitempty"` + Symbol []SymbolID `json:"symbol,omitempty"` +} + +func newBatchDecoderGetReferencesToSymbolInFileParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetReferencesToSymbolInFileParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } + if err := validateBatchColumn("symbol", len(columns.Symbol), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetReferencesToSymbolInFileParams](base, func(params *GetReferencesToSymbolInFileParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.File != nil { + params.File = columns.File[index] + } + if columns.Symbol != nil { + params.Symbol = columns.Symbol[index] + } + }) +} + +type batchColumnsGetResolvedModuleFromModuleSpecifierParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + ModuleSpecifier []NodeHandle `json:"moduleSpecifier,omitempty"` + SourceFile []*DocumentIdentifier `json:"sourceFile,omitempty"` +} + +func newBatchDecoderGetResolvedModuleFromModuleSpecifierParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetResolvedModuleFromModuleSpecifierParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("moduleSpecifier", len(columns.ModuleSpecifier), count); err != nil { + return nil, err + } + if err := validateBatchColumn("sourceFile", len(columns.SourceFile), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetResolvedModuleFromModuleSpecifierParams](base, func(params *GetResolvedModuleFromModuleSpecifierParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.ModuleSpecifier != nil { + params.ModuleSpecifier = columns.ModuleSpecifier[index] + } + if columns.SourceFile != nil { + params.SourceFile = columns.SourceFile[index] + } + }) +} + +type batchColumnsGetResolvedModuleParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + File []DocumentIdentifier `json:"file,omitempty"` + ModuleName []string `json:"moduleName,omitempty"` + Mode []core.ResolutionMode `json:"mode,omitempty"` +} + +func newBatchDecoderGetResolvedModuleParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetResolvedModuleParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } + if err := validateBatchColumn("moduleName", len(columns.ModuleName), count); err != nil { + return nil, err + } + if err := validateBatchColumn("mode", len(columns.Mode), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetResolvedModuleParams](base, func(params *GetResolvedModuleParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.File != nil { + params.File = columns.File[index] + } + if columns.ModuleName != nil { + params.ModuleName = columns.ModuleName[index] + } + if columns.Mode != nil { + params.Mode = columns.Mode[index] + } + }) +} + +type batchColumnsGetResolvedSignatureParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Location []NodeHandle `json:"location,omitempty"` +} + +func newBatchDecoderGetResolvedSignatureParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetResolvedSignatureParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetResolvedSignatureParams](base, func(params *GetResolvedSignatureParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Location != nil { + params.Location = columns.Location[index] + } + }) +} + +type batchColumnsGetResolvedTypeReferenceDirectiveFromReferenceParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + SourceFile []DocumentIdentifier `json:"sourceFile,omitempty"` + TypeDirectiveName []string `json:"typeDirectiveName,omitempty"` + ResolutionMode []core.ResolutionMode `json:"resolutionMode,omitempty"` +} + +func newBatchDecoderGetResolvedTypeReferenceDirectiveFromReferenceParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetResolvedTypeReferenceDirectiveFromReferenceParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("sourceFile", len(columns.SourceFile), count); err != nil { + return nil, err + } + if err := validateBatchColumn("typeDirectiveName", len(columns.TypeDirectiveName), count); err != nil { + return nil, err + } + if err := validateBatchColumn("resolutionMode", len(columns.ResolutionMode), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetResolvedTypeReferenceDirectiveFromReferenceParams](base, func(params *GetResolvedTypeReferenceDirectiveFromReferenceParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.SourceFile != nil { + params.SourceFile = columns.SourceFile[index] + } + if columns.TypeDirectiveName != nil { + params.TypeDirectiveName = columns.TypeDirectiveName[index] + } + if columns.ResolutionMode != nil { + params.ResolutionMode = columns.ResolutionMode[index] + } + }) +} + +type batchColumnsGetResolvedTypeReferenceDirectiveParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + File []DocumentIdentifier `json:"file,omitempty"` + TypeDirectiveName []string `json:"typeDirectiveName,omitempty"` + Mode []core.ResolutionMode `json:"mode,omitempty"` +} + +func newBatchDecoderGetResolvedTypeReferenceDirectiveParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetResolvedTypeReferenceDirectiveParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } + if err := validateBatchColumn("typeDirectiveName", len(columns.TypeDirectiveName), count); err != nil { + return nil, err + } + if err := validateBatchColumn("mode", len(columns.Mode), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetResolvedTypeReferenceDirectiveParams](base, func(params *GetResolvedTypeReferenceDirectiveParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.File != nil { + params.File = columns.File[index] + } + if columns.TypeDirectiveName != nil { + params.TypeDirectiveName = columns.TypeDirectiveName[index] + } + if columns.Mode != nil { + params.Mode = columns.Mode[index] + } + }) +} + +type batchColumnsGetSignaturePropertyParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Signature []SignatureID `json:"objectId,omitempty"` +} + +func newBatchDecoderGetSignaturePropertyParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetSignaturePropertyParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("objectId", len(columns.Signature), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetSignaturePropertyParams](base, func(params *GetSignaturePropertyParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Signature != nil { + params.Signature = columns.Signature[index] + } + }) +} + +type batchColumnsGetSignatureUsagesParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + SignatureDecl []NodeHandle `json:"signatureDecl,omitempty"` +} + +func newBatchDecoderGetSignatureUsagesParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetSignatureUsagesParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("signatureDecl", len(columns.SignatureDecl), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetSignatureUsagesParams](base, func(params *GetSignatureUsagesParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.SignatureDecl != nil { + params.SignatureDecl = columns.SignatureDecl[index] + } + }) +} + +type batchColumnsGetSignaturesOfTypeParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Type []TypeID `json:"type,omitempty"` + Kind []int32 `json:"kind,omitempty"` +} + +func newBatchDecoderGetSignaturesOfTypeParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetSignaturesOfTypeParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("type", len(columns.Type), count); err != nil { + return nil, err + } + if err := validateBatchColumn("kind", len(columns.Kind), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetSignaturesOfTypeParams](base, func(params *GetSignaturesOfTypeParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Type != nil { + params.Type = columns.Type[index] + } + if columns.Kind != nil { + params.Kind = columns.Kind[index] + } + }) +} + +type batchColumnsGetSourceFileNamesParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` +} + +func newBatchDecoderGetSourceFileNamesParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetSourceFileNamesParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetSourceFileNamesParams](base, func(params *GetSourceFileNamesParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + }) +} + +type batchColumnsGetSourceFileParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + File []DocumentIdentifier `json:"file,omitempty"` +} + +func newBatchDecoderGetSourceFileParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetSourceFileParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetSourceFileParams](base, func(params *GetSourceFileParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.File != nil { + params.File = columns.File[index] + } + }) +} + +type batchColumnsGetSymbolAtLocationParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Location []NodeHandle `json:"location,omitempty"` +} + +func newBatchDecoderGetSymbolAtLocationParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetSymbolAtLocationParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetSymbolAtLocationParams](base, func(params *GetSymbolAtLocationParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Location != nil { + params.Location = columns.Location[index] + } + }) +} + +type batchColumnsGetSymbolAtPositionParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + File []DocumentIdentifier `json:"file,omitempty"` + Position []uint32 `json:"position,omitempty"` +} + +func newBatchDecoderGetSymbolAtPositionParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetSymbolAtPositionParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } + if err := validateBatchColumn("position", len(columns.Position), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetSymbolAtPositionParams](base, func(params *GetSymbolAtPositionParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.File != nil { + params.File = columns.File[index] + } + if columns.Position != nil { + params.Position = columns.Position[index] + } + }) +} + +type batchColumnsGetSymbolOfSourceFileParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + File []DocumentIdentifier `json:"file,omitempty"` +} + +func newBatchDecoderGetSymbolOfSourceFileParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetSymbolOfSourceFileParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetSymbolOfSourceFileParams](base, func(params *GetSymbolOfSourceFileParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.File != nil { + params.File = columns.File[index] + } + }) +} + +type batchColumnsGetSymbolPropertyParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Symbol []SymbolID `json:"objectId,omitempty"` +} + +func newBatchDecoderGetSymbolPropertyParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetSymbolPropertyParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("objectId", len(columns.Symbol), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetSymbolPropertyParams](base, func(params *GetSymbolPropertyParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Symbol != nil { + params.Symbol = columns.Symbol[index] + } + }) +} + +type batchColumnsGetSymbolsInScopeParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Location []NodeHandle `json:"location,omitempty"` + File []*DocumentIdentifier `json:"file,omitempty"` + Position []*uint32 `json:"position,omitempty"` + Meaning []uint32 `json:"meaning,omitempty"` +} + +func newBatchDecoderGetSymbolsInScopeParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetSymbolsInScopeParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } + if err := validateBatchColumn("position", len(columns.Position), count); err != nil { + return nil, err + } + if err := validateBatchColumn("meaning", len(columns.Meaning), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetSymbolsInScopeParams](base, func(params *GetSymbolsInScopeParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Location != nil { + params.Location = columns.Location[index] + } + if columns.File != nil { + params.File = columns.File[index] + } + if columns.Position != nil { + params.Position = columns.Position[index] + } + if columns.Meaning != nil { + params.Meaning = columns.Meaning[index] + } + }) +} + +type batchColumnsGetTypeAtLocationParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Location []NodeHandle `json:"location,omitempty"` +} + +func newBatchDecoderGetTypeAtLocationParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetTypeAtLocationParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetTypeAtLocationParams](base, func(params *GetTypeAtLocationParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Location != nil { + params.Location = columns.Location[index] + } + }) +} + +type batchColumnsGetTypeAtPositionParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + File []DocumentIdentifier `json:"file,omitempty"` + Position []uint32 `json:"position,omitempty"` +} + +func newBatchDecoderGetTypeAtPositionParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetTypeAtPositionParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } + if err := validateBatchColumn("position", len(columns.Position), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetTypeAtPositionParams](base, func(params *GetTypeAtPositionParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.File != nil { + params.File = columns.File[index] + } + if columns.Position != nil { + params.Position = columns.Position[index] + } + }) +} + +type batchColumnsGetTypeFromTypeNodeParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Location []NodeHandle `json:"location,omitempty"` +} + +func newBatchDecoderGetTypeFromTypeNodeParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetTypeFromTypeNodeParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetTypeFromTypeNodeParams](base, func(params *GetTypeFromTypeNodeParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Location != nil { + params.Location = columns.Location[index] + } + }) +} + +type batchColumnsGetTypeOfSymbolAtLocationParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Symbol []SymbolID `json:"symbol,omitempty"` + Location []NodeHandle `json:"location,omitempty"` +} + +func newBatchDecoderGetTypeOfSymbolAtLocationParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetTypeOfSymbolAtLocationParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("symbol", len(columns.Symbol), count); err != nil { + return nil, err + } + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetTypeOfSymbolAtLocationParams](base, func(params *GetTypeOfSymbolAtLocationParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Symbol != nil { + params.Symbol = columns.Symbol[index] + } + if columns.Location != nil { + params.Location = columns.Location[index] + } + }) +} + +type batchColumnsGetTypeOfSymbolParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Symbol []SymbolID `json:"symbol,omitempty"` +} + +func newBatchDecoderGetTypeOfSymbolParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetTypeOfSymbolParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("symbol", len(columns.Symbol), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetTypeOfSymbolParams](base, func(params *GetTypeOfSymbolParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Symbol != nil { + params.Symbol = columns.Symbol[index] + } + }) +} + +type batchColumnsGetTypePropertyParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Type []TypeID `json:"objectId,omitempty"` +} + +func newBatchDecoderGetTypePropertyParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetTypePropertyParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("objectId", len(columns.Type), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetTypePropertyParams](base, func(params *GetTypePropertyParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Type != nil { + params.Type = columns.Type[index] + } + }) +} + +type batchColumnsGetWidenedTypeParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Type []TypeID `json:"type,omitempty"` +} + +func newBatchDecoderGetWidenedTypeParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetWidenedTypeParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("type", len(columns.Type), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetWidenedTypeParams](base, func(params *GetWidenedTypeParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Type != nil { + params.Type = columns.Type[index] + } + }) +} + +type batchColumnsIsArrayLikeTypeParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Type []TypeID `json:"type,omitempty"` +} + +func newBatchDecoderIsArrayLikeTypeParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsIsArrayLikeTypeParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("type", len(columns.Type), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[IsArrayLikeTypeParams](base, func(params *IsArrayLikeTypeParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Type != nil { + params.Type = columns.Type[index] + } + }) +} + +type batchColumnsIsTypeAssignableToParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Source []TypeID `json:"source,omitempty"` + Target []TypeID `json:"target,omitempty"` +} + +func newBatchDecoderIsTypeAssignableToParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsIsTypeAssignableToParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("source", len(columns.Source), count); err != nil { + return nil, err + } + if err := validateBatchColumn("target", len(columns.Target), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[IsTypeAssignableToParams](base, func(params *IsTypeAssignableToParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Source != nil { + params.Source = columns.Source[index] + } + if columns.Target != nil { + params.Target = columns.Target[index] + } + }) +} + +type batchColumnsParseCommandLineParams struct { + CommandLine [][]string `json:"commandLine,omitempty"` +} + +func newBatchDecoderParseCommandLineParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsParseCommandLineParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("commandLine", len(columns.CommandLine), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[ParseCommandLineParams](base, func(params *ParseCommandLineParams, index int) { + if columns.CommandLine != nil { + params.CommandLine = columns.CommandLine[index] + } + }) +} + +type batchColumnsParseConfigFileParams struct { + File []DocumentIdentifier `json:"file,omitempty"` +} + +func newBatchDecoderParseConfigFileParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsParseConfigFileParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[ParseConfigFileParams](base, func(params *ParseConfigFileParams, index int) { + if columns.File != nil { + params.File = columns.File[index] + } + }) +} + +type batchColumnsParseJsonConfigFileContentParams struct { + JSON []packagejson.JSONValue `json:"json,omitempty"` + ConfigDirectory []*string `json:"configDirectory,omitempty"` + ConfigFileName []*DocumentIdentifier `json:"configFileName,omitempty"` +} + +func newBatchDecoderParseJsonConfigFileContentParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsParseJsonConfigFileContentParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("json", len(columns.JSON), count); err != nil { + return nil, err + } + if err := validateBatchColumn("configDirectory", len(columns.ConfigDirectory), count); err != nil { + return nil, err + } + if err := validateBatchColumn("configFileName", len(columns.ConfigFileName), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[ParseJsonConfigFileContentParams](base, func(params *ParseJsonConfigFileContentParams, index int) { + if columns.JSON != nil { + params.JSON = columns.JSON[index] + } + if columns.ConfigDirectory != nil { + params.ConfigDirectory = columns.ConfigDirectory[index] + } + if columns.ConfigFileName != nil { + params.ConfigFileName = columns.ConfigFileName[index] + } + }) +} + +type batchColumnsPrintNodeParams struct { + Data []string `json:"data,omitempty"` + PreserveSourceNewlines []bool `json:"preserveSourceNewlines,omitempty"` + NeverAsciiEscape []bool `json:"neverAsciiEscape,omitempty"` + TerminateUnterminatedLiterals []bool `json:"terminateUnterminatedLiterals,omitempty"` +} + +func newBatchDecoderPrintNodeParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsPrintNodeParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("data", len(columns.Data), count); err != nil { + return nil, err + } + if err := validateBatchColumn("preserveSourceNewlines", len(columns.PreserveSourceNewlines), count); err != nil { + return nil, err + } + if err := validateBatchColumn("neverAsciiEscape", len(columns.NeverAsciiEscape), count); err != nil { + return nil, err + } + if err := validateBatchColumn("terminateUnterminatedLiterals", len(columns.TerminateUnterminatedLiterals), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[PrintNodeParams](base, func(params *PrintNodeParams, index int) { + if columns.Data != nil { + params.Data = columns.Data[index] + } + if columns.PreserveSourceNewlines != nil { + params.PreserveSourceNewlines = columns.PreserveSourceNewlines[index] + } + if columns.NeverAsciiEscape != nil { + params.NeverAsciiEscape = columns.NeverAsciiEscape[index] + } + if columns.TerminateUnterminatedLiterals != nil { + params.TerminateUnterminatedLiterals = columns.TerminateUnterminatedLiterals[index] + } + }) +} + +type batchColumnsProfileParams struct { + Dir []string `json:"dir,omitempty"` +} + +func newBatchDecoderProfileParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsProfileParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("dir", len(columns.Dir), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[ProfileParams](base, func(params *ProfileParams, index int) { + if columns.Dir != nil { + params.Dir = columns.Dir[index] + } + }) +} + +type batchColumnsReadConfigFileParams struct { + File []DocumentIdentifier `json:"file,omitempty"` +} + +func newBatchDecoderReadConfigFileParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsReadConfigFileParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[ReadConfigFileParams](base, func(params *ReadConfigFileParams, index int) { + if columns.File != nil { + params.File = columns.File[index] + } + }) +} + +type batchColumnsReleaseParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` +} + +func newBatchDecoderReleaseParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsReleaseParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[ReleaseParams](base, func(params *ReleaseParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + }) +} + +type batchColumnsResolveNameParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Name []string `json:"name,omitempty"` + Location []NodeHandle `json:"location,omitempty"` + File []*DocumentIdentifier `json:"file,omitempty"` + Position []*uint32 `json:"position,omitempty"` + Meaning []uint32 `json:"meaning,omitempty"` + ExcludeGlobals []bool `json:"excludeGlobals,omitempty"` +} + +func newBatchDecoderResolveNameParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsResolveNameParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("name", len(columns.Name), count); err != nil { + return nil, err + } + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } + if err := validateBatchColumn("position", len(columns.Position), count); err != nil { + return nil, err + } + if err := validateBatchColumn("meaning", len(columns.Meaning), count); err != nil { + return nil, err + } + if err := validateBatchColumn("excludeGlobals", len(columns.ExcludeGlobals), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[ResolveNameParams](base, func(params *ResolveNameParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Name != nil { + params.Name = columns.Name[index] + } + if columns.Location != nil { + params.Location = columns.Location[index] + } + if columns.File != nil { + params.File = columns.File[index] + } + if columns.Position != nil { + params.Position = columns.Position[index] + } + if columns.Meaning != nil { + params.Meaning = columns.Meaning[index] + } + if columns.ExcludeGlobals != nil { + params.ExcludeGlobals = columns.ExcludeGlobals[index] + } + }) +} + +type batchColumnsSelectedFilesEmitParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Files [][]DocumentIdentifier `json:"files,omitempty"` +} + +func newBatchDecoderSelectedFilesEmitParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsSelectedFilesEmitParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("files", len(columns.Files), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[SelectedFilesEmitParams](base, func(params *SelectedFilesEmitParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Files != nil { + params.Files = columns.Files[index] + } + }) +} + +type batchColumnsSignatureToSignatureDeclarationParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Signature []SignatureID `json:"signature,omitempty"` + Kind []int32 `json:"kind,omitempty"` + Location []NodeHandle `json:"location,omitempty"` + Flags []int32 `json:"flags,omitempty"` +} + +func newBatchDecoderSignatureToSignatureDeclarationParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsSignatureToSignatureDeclarationParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("signature", len(columns.Signature), count); err != nil { + return nil, err + } + if err := validateBatchColumn("kind", len(columns.Kind), count); err != nil { + return nil, err + } + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } + if err := validateBatchColumn("flags", len(columns.Flags), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[SignatureToSignatureDeclarationParams](base, func(params *SignatureToSignatureDeclarationParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Signature != nil { + params.Signature = columns.Signature[index] + } + if columns.Kind != nil { + params.Kind = columns.Kind[index] + } + if columns.Location != nil { + params.Location = columns.Location[index] + } + if columns.Flags != nil { + params.Flags = columns.Flags[index] + } + }) +} + +type batchColumnsTranspileFromFileParams struct { + FileName []string `json:"fileName,omitempty"` + Options []TranspileOptions `json:"options,omitempty"` +} + +func newBatchDecoderTranspileFromFileParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsTranspileFromFileParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("fileName", len(columns.FileName), count); err != nil { + return nil, err + } + if err := validateBatchColumn("options", len(columns.Options), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[TranspileFromFileParams](base, func(params *TranspileFromFileParams, index int) { + if columns.FileName != nil { + params.FileName = columns.FileName[index] + } + if columns.Options != nil { + params.Options = columns.Options[index] + } + }) +} + +type batchColumnsTranspileParams struct { + Input []string `json:"input,omitempty"` + Options []TranspileOptions `json:"options,omitempty"` +} + +func newBatchDecoderTranspileParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsTranspileParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("input", len(columns.Input), count); err != nil { + return nil, err + } + if err := validateBatchColumn("options", len(columns.Options), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[TranspileParams](base, func(params *TranspileParams, index int) { + if columns.Input != nil { + params.Input = columns.Input[index] + } + if columns.Options != nil { + params.Options = columns.Options[index] + } + }) +} + +type batchColumnsTypeToTypeNodeParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Type []TypeID `json:"type,omitempty"` + Location []NodeHandle `json:"location,omitempty"` + Flags []int32 `json:"flags,omitempty"` +} + +func newBatchDecoderTypeToTypeNodeParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsTypeToTypeNodeParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("type", len(columns.Type), count); err != nil { + return nil, err + } + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } + if err := validateBatchColumn("flags", len(columns.Flags), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[TypeToTypeNodeParams](base, func(params *TypeToTypeNodeParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Type != nil { + params.Type = columns.Type[index] + } + if columns.Location != nil { + params.Location = columns.Location[index] + } + if columns.Flags != nil { + params.Flags = columns.Flags[index] + } + }) +} + +type batchColumnsUpdateSnapshotParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + OpenProjects [][]DocumentIdentifier `json:"openProjects,omitempty"` + CloseProjects [][]DocumentIdentifier `json:"closeProjects,omitempty"` + FileChanges []*APIFileChanges `json:"fileChanges,omitempty"` + FileSystem []*requestfilesystem.RequestFileSystem `json:"fileSystem,omitempty"` + OpenFiles [][]DocumentIdentifier `json:"openFiles,omitempty"` + CloseFiles [][]DocumentIdentifier `json:"closeFiles,omitempty"` +} + +func newBatchDecoderUpdateSnapshotParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsUpdateSnapshotParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("openProjects", len(columns.OpenProjects), count); err != nil { + return nil, err + } + if err := validateBatchColumn("closeProjects", len(columns.CloseProjects), count); err != nil { + return nil, err + } + if err := validateBatchColumn("fileChanges", len(columns.FileChanges), count); err != nil { + return nil, err + } + if err := validateBatchColumn("fileSystem", len(columns.FileSystem), count); err != nil { + return nil, err + } + if err := validateBatchColumn("openFiles", len(columns.OpenFiles), count); err != nil { + return nil, err + } + if err := validateBatchColumn("closeFiles", len(columns.CloseFiles), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[UpdateSnapshotParams](base, func(params *UpdateSnapshotParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.OpenProjects != nil { + params.OpenProjects = columns.OpenProjects[index] + } + if columns.CloseProjects != nil { + params.CloseProjects = columns.CloseProjects[index] + } + if columns.FileChanges != nil { + params.FileChanges = columns.FileChanges[index] + } + if columns.FileSystem != nil { + params.FileSystem = columns.FileSystem[index] + } + if columns.OpenFiles != nil { + params.OpenFiles = columns.OpenFiles[index] + } + if columns.CloseFiles != nil { + params.CloseFiles = columns.CloseFiles[index] + } + }) +} + +type batchColumnsUpdateTemporarySnapshotParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + File []DocumentIdentifier `json:"file,omitempty"` + NewText []string `json:"newText,omitempty"` +} + +func newBatchDecoderUpdateTemporarySnapshotParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsUpdateTemporarySnapshotParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } + if err := validateBatchColumn("newText", len(columns.NewText), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[UpdateTemporarySnapshotParams](base, func(params *UpdateTemporarySnapshotParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.File != nil { + params.File = columns.File[index] + } + if columns.NewText != nil { + params.NewText = columns.NewText[index] + } + }) +} + +func newGeneratedBatchRequestDecoder(method Method, base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + switch method { + case MethodRelease: + return newBatchDecoderReleaseParams(base, fields, count) + case MethodUpdateSnapshot: + return newBatchDecoderUpdateSnapshotParams(base, fields, count) + case MethodUpdateTemporarySnapshot: + return newBatchDecoderUpdateTemporarySnapshotParams(base, fields, count) + case MethodCreateProgram: + return newBatchDecoderCreateProgramParams(base, fields, count) + case MethodParseCommandLine: + return newBatchDecoderParseCommandLineParams(base, fields, count) + case MethodReadConfigFile: + return newBatchDecoderReadConfigFileParams(base, fields, count) + case MethodParseJsonConfigFile: + return newBatchDecoderParseJsonConfigFileContentParams(base, fields, count) + case MethodParseConfigFile: + return newBatchDecoderParseConfigFileParams(base, fields, count) + case MethodTranspileModule: + return newBatchDecoderTranspileParams(base, fields, count) + case MethodTranspileModuleFromFile: + return newBatchDecoderTranspileFromFileParams(base, fields, count) + case MethodTranspileDeclaration: + return newBatchDecoderTranspileParams(base, fields, count) + case MethodTranspileDeclarationFromFile: + return newBatchDecoderTranspileFromFileParams(base, fields, count) + case MethodGetDefaultProjectForFile: + return newBatchDecoderGetDefaultProjectForFileParams(base, fields, count) + case MethodGetSymbolAtPosition: + return newBatchDecoderGetSymbolAtPositionParams(base, fields, count) + case MethodGetSymbolAtLocation: + return newBatchDecoderGetSymbolAtLocationParams(base, fields, count) + case MethodGetSymbolOfSourceFile: + return newBatchDecoderGetSymbolOfSourceFileParams(base, fields, count) + case MethodGetTypeOfSymbol: + return newBatchDecoderGetTypeOfSymbolParams(base, fields, count) + case MethodGetDeclaredTypeOfSymbol: + return newBatchDecoderGetTypeOfSymbolParams(base, fields, count) + case MethodGetNonMissingTypeOfSymbol: + return newBatchDecoderGetTypeOfSymbolParams(base, fields, count) + case MethodGetSourceFile: + return newBatchDecoderGetSourceFileParams(base, fields, count) + case MethodGetSourceFileNames: + return newBatchDecoderGetSourceFileNamesParams(base, fields, count) + case MethodGetSourceFileMetadata: + return newBatchDecoderGetSourceFileParams(base, fields, count) + case MethodGetResolvedModule: + return newBatchDecoderGetResolvedModuleParams(base, fields, count) + case MethodGetResolvedModuleFromModuleSpecifier: + return newBatchDecoderGetResolvedModuleFromModuleSpecifierParams(base, fields, count) + case MethodGetResolvedTypeReferenceDirective: + return newBatchDecoderGetResolvedTypeReferenceDirectiveParams(base, fields, count) + case MethodGetResolvedTypeReferenceDirectiveFromReference: + return newBatchDecoderGetResolvedTypeReferenceDirectiveFromReferenceParams(base, fields, count) + case MethodGetConfigFileNames: + return newBatchDecoderGetProjectDiagnosticsParams(base, fields, count) + case MethodGetConfigSourceFile: + return newBatchDecoderGetSourceFileParams(base, fields, count) + case MethodResolveName: + return newBatchDecoderResolveNameParams(base, fields, count) + case MethodGetSymbolsInScope: + return newBatchDecoderGetSymbolsInScopeParams(base, fields, count) + case MethodGetSignaturesOfType: + return newBatchDecoderGetSignaturesOfTypeParams(base, fields, count) + case MethodGetResolvedSignature: + return newBatchDecoderGetResolvedSignatureParams(base, fields, count) + case MethodGetTypeAtLocation: + return newBatchDecoderGetTypeAtLocationParams(base, fields, count) + case MethodGetTypeAtPosition: + return newBatchDecoderGetTypeAtPositionParams(base, fields, count) + case MethodGetParentOfSymbol: + return newBatchDecoderGetSymbolPropertyParams(base, fields, count) + case MethodGetMembersOfSymbol: + return newBatchDecoderGetSymbolPropertyParams(base, fields, count) + case MethodGetExportsOfSymbol: + return newBatchDecoderGetSymbolPropertyParams(base, fields, count) + case MethodGetExportSymbolOfSymbol: + return newBatchDecoderGetSymbolPropertyParams(base, fields, count) + case MethodGetSymbolOfType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetTargetOfType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetFreshTypeOfType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetRegularTypeOfType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetTypesOfType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetTypeParametersOfType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetOuterTypeParametersOfType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetLocalTypeParametersOfType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetAliasTypeArgumentsOfType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetAliasSymbolOfType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetObjectTypeOfType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetIndexTypeOfType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetCheckTypeOfType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetExtendsTypeOfType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetBaseTypeOfType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetConstraintOfType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetTypeParametersOfSignature: + return newBatchDecoderGetSignaturePropertyParams(base, fields, count) + case MethodGetParametersOfSignature: + return newBatchDecoderGetSignaturePropertyParams(base, fields, count) + case MethodGetThisParameterOfSignature: + return newBatchDecoderGetSignaturePropertyParams(base, fields, count) + case MethodGetTargetOfSignature: + return newBatchDecoderGetSignaturePropertyParams(base, fields, count) + case MethodGetContextualType: + return newBatchDecoderGetContextualTypeParams(base, fields, count) + case MethodGetBaseTypeOfLiteralType: + return newBatchDecoderGetBaseTypeOfLiteralTypeParams(base, fields, count) + case MethodGetNonNullableType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetTypeFromTypeNode: + return newBatchDecoderGetTypeFromTypeNodeParams(base, fields, count) + case MethodGetWidenedType: + return newBatchDecoderGetWidenedTypeParams(base, fields, count) + case MethodGetParameterType: + return newBatchDecoderGetParameterTypeParams(base, fields, count) + case MethodGetTypeParameterAtPosition: + return newBatchDecoderGetParameterTypeParams(base, fields, count) + case MethodIsArrayLikeType: + return newBatchDecoderIsArrayLikeTypeParams(base, fields, count) + case MethodIsTypeAssignableTo: + return newBatchDecoderIsTypeAssignableToParams(base, fields, count) + case MethodGetShorthandAssignmentValueSymbol: + return newBatchDecoderGetTypeAtLocationParams(base, fields, count) + case MethodGetTypeOfSymbolAtLocation: + return newBatchDecoderGetTypeOfSymbolAtLocationParams(base, fields, count) + case MethodTypeToTypeNode: + return newBatchDecoderTypeToTypeNodeParams(base, fields, count) + case MethodSignatureToSignatureDeclaration: + return newBatchDecoderSignatureToSignatureDeclarationParams(base, fields, count) + case MethodTypeToString: + return newBatchDecoderTypeToTypeNodeParams(base, fields, count) + case MethodIsContextSensitive: + return newBatchDecoderGetContextualTypeParams(base, fields, count) + case MethodGetReturnTypeOfSignature: + return newBatchDecoderGetSignaturePropertyParams(base, fields, count) + case MethodGetRestTypeOfSignature: + return newBatchDecoderCheckerSignatureParams(base, fields, count) + case MethodGetTypePredicateOfSignature: + return newBatchDecoderCheckerSignatureParams(base, fields, count) + case MethodGetBaseTypes: + return newBatchDecoderCheckerTypeParams(base, fields, count) + case MethodGetPropertiesOfType: + return newBatchDecoderCheckerTypeParams(base, fields, count) + case MethodGetApparentPropertiesOfType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetApparentType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetReducedType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetPropertyOfType: + return newBatchDecoderGetPropertyOfTypeParams(base, fields, count) + case MethodGetIndexInfosOfType: + return newBatchDecoderCheckerTypeParams(base, fields, count) + case MethodGetConstraintOfTypeParameter: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetDefaultFromTypeParameter: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetBaseConstraintOfType: + return newBatchDecoderCheckerTypeParams(base, fields, count) + case MethodGetTypeArguments: + return newBatchDecoderCheckerTypeParams(base, fields, count) + case MethodGetImportAdderEdits: + return newBatchDecoderGetImportAdderEditsParams(base, fields, count) + case MethodGetTrueTypeOfConditionalType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetFalseTypeOfConditionalType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetConstantValue: + return newBatchDecoderCheckerNodeParams(base, fields, count) + case MethodGetSignatureFromDeclaration: + return newBatchDecoderCheckerNodeParams(base, fields, count) + case MethodGetExportSpecifierLocalTarget: + return newBatchDecoderCheckerNodeParams(base, fields, count) + case MethodGetAliasedSymbol: + return newBatchDecoderCheckerSymbolParams(base, fields, count) + case MethodGetImmediateAliasedSymbol: + return newBatchDecoderCheckerSymbolParams(base, fields, count) + case MethodGetTargetSymbol: + return newBatchDecoderCheckerSymbolParams(base, fields, count) + case MethodGetFullyQualifiedName: + return newBatchDecoderCheckerSymbolParams(base, fields, count) + case MethodGetExportsOfModule: + return newBatchDecoderCheckerSymbolParams(base, fields, count) + case MethodGetMemberInModuleExports: + return newBatchDecoderGetMemberInModuleExportsParams(base, fields, count) + case MethodGetJSDocTags: + return newBatchDecoderCheckerSymbolParams(base, fields, count) + case MethodGetDocumentationComment: + return newBatchDecoderCheckerSymbolParams(base, fields, count) + case MethodIsArrayType: + return newBatchDecoderCheckerTypeParams(base, fields, count) + case MethodIsReadonlySymbol: + return newBatchDecoderCheckerSymbolParams(base, fields, count) + case MethodGetReferencesToSymbolInFile: + return newBatchDecoderGetReferencesToSymbolInFileParams(base, fields, count) + case MethodGetReferencedSymbolsForNode: + return newBatchDecoderGetReferencedSymbolsForNodeParams(base, fields, count) + case MethodGetSignatureUsages: + return newBatchDecoderGetSignatureUsagesParams(base, fields, count) + case MethodGetCompletionsAtPosition: + return newBatchDecoderGetCompletionsAtPositionParams(base, fields, count) + case MethodGetSyntacticDiagnostics: + return newBatchDecoderGetDiagnosticsParams(base, fields, count) + case MethodGetBindDiagnostics: + return newBatchDecoderGetDiagnosticsParams(base, fields, count) + case MethodGetSemanticDiagnostics: + return newBatchDecoderGetDiagnosticsParams(base, fields, count) + case MethodGetSuggestionDiagnostics: + return newBatchDecoderGetDiagnosticsParams(base, fields, count) + case MethodGetDeclarationDiagnostics: + return newBatchDecoderGetDiagnosticsParams(base, fields, count) + case MethodGetProgramDiagnostics: + return newBatchDecoderGetProjectDiagnosticsParams(base, fields, count) + case MethodGetGlobalDiagnostics: + return newBatchDecoderGetProjectDiagnosticsParams(base, fields, count) + case MethodGetConfigFileParsingDiagnostics: + return newBatchDecoderGetProjectDiagnosticsParams(base, fields, count) + case MethodPrintNode: + return newBatchDecoderPrintNodeParams(base, fields, count) + case MethodFormatNodeForInsertion: + return newBatchDecoderFormatNodeForInsertionParams(base, fields, count) + case MethodEmit: + return newBatchDecoderEmitParams(base, fields, count) + case MethodEmitToString: + return newBatchDecoderEmitParams(base, fields, count) + case MethodGetJavaScriptEmit: + return newBatchDecoderSelectedFilesEmitParams(base, fields, count) + case MethodGetDeclarationEmit: + return newBatchDecoderSelectedFilesEmitParams(base, fields, count) + case MethodGetAnyType: + return newBatchDecoderGetIntrinsicTypeParams(base, fields, count) + case MethodGetStringType: + return newBatchDecoderGetIntrinsicTypeParams(base, fields, count) + case MethodGetNumberType: + return newBatchDecoderGetIntrinsicTypeParams(base, fields, count) + case MethodGetBooleanType: + return newBatchDecoderGetIntrinsicTypeParams(base, fields, count) + case MethodGetVoidType: + return newBatchDecoderGetIntrinsicTypeParams(base, fields, count) + case MethodGetUndefinedType: + return newBatchDecoderGetIntrinsicTypeParams(base, fields, count) + case MethodGetNullType: + return newBatchDecoderGetIntrinsicTypeParams(base, fields, count) + case MethodGetNeverType: + return newBatchDecoderGetIntrinsicTypeParams(base, fields, count) + case MethodGetUnknownType: + return newBatchDecoderGetIntrinsicTypeParams(base, fields, count) + case MethodGetBigIntType: + return newBatchDecoderGetIntrinsicTypeParams(base, fields, count) + case MethodGetESSymbolType: + return newBatchDecoderGetIntrinsicTypeParams(base, fields, count) + case MethodGetNonPrimitiveType: + return newBatchDecoderGetIntrinsicTypeParams(base, fields, count) + case MethodGetWellKnownSymbols: + return newBatchDecoderGetIntrinsicTypeParams(base, fields, count) + case MethodGetWellKnownSignatures: + return newBatchDecoderGetIntrinsicTypeParams(base, fields, count) + case MethodStartCPUProfile: + return newBatchDecoderProfileParams(base, fields, count) + case MethodSaveHeapProfile: + return newBatchDecoderProfileParams(base, fields, count) + default: + return nil, nil + } +} diff --git a/tsc/internal/api/proto.go b/tsc/internal/api/proto.go index b67f21129e528..61b062d0ee702 100644 --- a/tsc/internal/api/proto.go +++ b/tsc/internal/api/proto.go @@ -81,13 +81,9 @@ const ( MethodTranspileDeclarationFromFile Method = "transpileDeclarationFromFile" MethodGetDefaultProjectForFile Method = "getDefaultProjectForFile" MethodGetSymbolAtPosition Method = "getSymbolAtPosition" - MethodGetSymbolsAtPositions Method = "getSymbolsAtPositions" MethodGetSymbolAtLocation Method = "getSymbolAtLocation" - MethodGetSymbolsAtLocations Method = "getSymbolsAtLocations" MethodGetSymbolOfSourceFile Method = "getSymbolOfSourceFile" - MethodGetSymbolsOfSourceFiles Method = "getSymbolsOfSourceFiles" MethodGetTypeOfSymbol Method = "getTypeOfSymbol" - MethodGetTypesOfSymbols Method = "getTypesOfSymbols" MethodGetDeclaredTypeOfSymbol Method = "getDeclaredTypeOfSymbol" MethodGetNonMissingTypeOfSymbol Method = "getNonMissingTypeOfSymbol" MethodGetSourceFile Method = "getSourceFile" @@ -104,9 +100,7 @@ const ( MethodGetSignaturesOfType Method = "getSignaturesOfType" MethodGetResolvedSignature Method = "getResolvedSignature" MethodGetTypeAtLocation Method = "getTypeAtLocation" - MethodGetTypeAtLocations Method = "getTypeAtLocations" MethodGetTypeAtPosition Method = "getTypeAtPosition" - MethodGetTypesAtPositions Method = "getTypesAtPositions" // Symbol sub-property methods MethodGetParentOfSymbol Method = "getParentOfSymbol" @@ -468,13 +462,9 @@ var unmarshalers = map[Method]func([]byte) (any, error){ MethodGetConfigFileNames: unmarshallerFor[GetProjectDiagnosticsParams], MethodGetConfigSourceFile: unmarshallerFor[GetSourceFileParams], MethodGetSymbolAtPosition: unmarshallerFor[GetSymbolAtPositionParams], - MethodGetSymbolsAtPositions: unmarshallerFor[GetSymbolsAtPositionsParams], MethodGetSymbolAtLocation: unmarshallerFor[GetSymbolAtLocationParams], - MethodGetSymbolsAtLocations: unmarshallerFor[GetSymbolsAtLocationsParams], MethodGetSymbolOfSourceFile: unmarshallerFor[GetSymbolOfSourceFileParams], - MethodGetSymbolsOfSourceFiles: unmarshallerFor[GetSymbolsOfSourceFilesParams], MethodGetTypeOfSymbol: unmarshallerFor[GetTypeOfSymbolParams], - MethodGetTypesOfSymbols: unmarshallerFor[GetTypesOfSymbolsParams], MethodGetDeclaredTypeOfSymbol: unmarshallerFor[GetTypeOfSymbolParams], MethodGetNonMissingTypeOfSymbol: unmarshallerFor[GetTypeOfSymbolParams], MethodResolveName: unmarshallerFor[ResolveNameParams], @@ -482,9 +472,7 @@ var unmarshalers = map[Method]func([]byte) (any, error){ MethodGetSignaturesOfType: unmarshallerFor[GetSignaturesOfTypeParams], MethodGetResolvedSignature: unmarshallerFor[GetResolvedSignatureParams], MethodGetTypeAtLocation: unmarshallerFor[GetTypeAtLocationParams], - MethodGetTypeAtLocations: unmarshallerFor[GetTypeAtLocationsParams], MethodGetTypeAtPosition: unmarshallerFor[GetTypeAtPositionParams], - MethodGetTypesAtPositions: unmarshallerFor[GetTypesAtPositionsParams], MethodGetParentOfSymbol: unmarshallerFor[GetSymbolPropertyParams], MethodGetMembersOfSymbol: unmarshallerFor[GetSymbolPropertyParams], @@ -661,9 +649,11 @@ type TranspileOutputResponse struct { } type BatchRequestsParams struct { - Requests []BatchRequest `json:"requests"` - ContinuationToken string `json:"continuationToken,omitempty"` - MaxResponseBytesPerPage int `json:"maxResponseBytesPerPage,omitempty"` + Requests []BatchRequest `json:"requests,omitempty"` + Groups []BatchRequestGroup `json:"groups,omitempty"` + GroupOrder []uint32 `json:"groupOrder,omitempty"` + ContinuationToken string `json:"continuationToken,omitempty"` + MaxResponseBytesPerPage int `json:"maxResponseBytesPerPage,omitempty"` } type BatchRequest struct { @@ -671,10 +661,20 @@ type BatchRequest struct { Params json.Value `json:"params,omitempty"` } +type BatchRequestGroup struct { + Method Method `json:"method"` + Base json.Value `json:"base,omitempty"` + Count int `json:"count"` + Fields json.Value `json:"fields,omitempty"` + Requests []json.Value `json:"requests,omitempty"` +} + type BatchRequestsResponse struct { - Responses []BatchResponse `json:"responses" nonnil:"true"` - ContinuationToken string `json:"continuationToken,omitempty"` + Results []any `json:"results" nonnil:"true"` + Errors map[int]string `json:"errors,omitempty"` + ContinuationToken string `json:"continuationToken,omitempty"` encodedResponses []json.Value + encodedArray json.Value } var _ json.MarshalerTo = (*BatchRequestsResponse)(nil) @@ -683,27 +683,33 @@ func (r *BatchRequestsResponse) MarshalJSONTo(enc *json.Encoder) error { if err := enc.WriteToken(json.BeginObject); err != nil { return err } - if err := enc.WriteValue(json.Value(`"responses"`)); err != nil { + if err := enc.WriteValue(json.Value(`"results"`)); err != nil { return err } - if err := enc.WriteToken(json.BeginArray); err != nil { - return err - } - if r.encodedResponses != nil { - for _, response := range r.encodedResponses { - if err := enc.WriteValue(response); err != nil { - return err - } + if r.encodedArray != nil { + if err := enc.WriteValue(r.encodedArray); err != nil { + return err } } else { - for i := range r.Responses { - if err := json.MarshalEncode(enc, &r.Responses[i]); err != nil { - return err + if err := enc.WriteToken(json.BeginArray); err != nil { + return err + } + if r.encodedResponses != nil { + for _, response := range r.encodedResponses { + if err := enc.WriteValue(response); err != nil { + return err + } + } + } else { + for i := range r.Results { + if err := json.MarshalEncode(enc, &r.Results[i]); err != nil { + return err + } } } - } - if err := enc.WriteToken(json.EndArray); err != nil { - return err + if err := enc.WriteToken(json.EndArray); err != nil { + return err + } } if r.ContinuationToken != "" { if err := enc.WriteValue(json.Value(`"continuationToken"`)); err != nil { @@ -713,11 +719,18 @@ func (r *BatchRequestsResponse) MarshalJSONTo(enc *json.Encoder) error { return err } } + if len(r.Errors) > 0 { + if err := enc.WriteValue(json.Value(`"errors"`)); err != nil { + return err + } + if err := json.MarshalEncode(enc, r.Errors); err != nil { + return err + } + } return enc.WriteToken(json.EndObject) } type BatchResponse struct { - Method Method `json:"method"` Result any `json:"result"` Error string `json:"error,omitempty"` } @@ -840,37 +853,18 @@ type GetSymbolAtPositionParams struct { Position uint32 `json:"position"` } -type GetSymbolsAtPositionsParams struct { - Snapshot SnapshotID `json:"snapshot"` - Project ProjectID `json:"project"` - File DocumentIdentifier `json:"file"` - Positions []uint32 `json:"positions"` -} - type GetSymbolOfSourceFileParams struct { Snapshot SnapshotID `json:"snapshot"` Project ProjectID `json:"project"` File DocumentIdentifier `json:"file"` } -type GetSymbolsOfSourceFilesParams struct { - Snapshot SnapshotID `json:"snapshot"` - Project ProjectID `json:"project"` - Files []DocumentIdentifier `json:"files"` -} - type GetSymbolAtLocationParams struct { Snapshot SnapshotID `json:"snapshot"` Project ProjectID `json:"project"` Location NodeHandle `json:"location"` } -type GetSymbolsAtLocationsParams struct { - Snapshot SnapshotID `json:"snapshot"` - Project ProjectID `json:"project"` - Locations []NodeHandle `json:"locations"` -} - type SymbolResponse struct { Id SymbolID `json:"id"` // Project is the project in which the symbol was first observed. It is the @@ -902,12 +896,6 @@ type GetTypeOfSymbolParams struct { Symbol SymbolID `json:"symbol"` } -type GetTypesOfSymbolsParams struct { - Snapshot SnapshotID `json:"snapshot"` - Project ProjectID `json:"project"` - Symbols []SymbolID `json:"symbols"` -} - type TypeResponse struct { Id TypeID `json:"id"` Flags uint32 `json:"flags"` @@ -1396,12 +1384,6 @@ type GetTypeAtLocationParams struct { Location NodeHandle `json:"location"` } -type GetTypeAtLocationsParams struct { - Snapshot SnapshotID `json:"snapshot"` - Project ProjectID `json:"project"` - Locations []NodeHandle `json:"locations"` -} - type GetTypeAtPositionParams struct { Snapshot SnapshotID `json:"snapshot"` Project ProjectID `json:"project"` @@ -1409,13 +1391,6 @@ type GetTypeAtPositionParams struct { Position uint32 `json:"position"` } -type GetTypesAtPositionsParams struct { - Snapshot SnapshotID `json:"snapshot"` - Project ProjectID `json:"project"` - File DocumentIdentifier `json:"file"` - Positions []uint32 `json:"positions"` -} - type ImportAdderActionKind string const ( diff --git a/tsc/internal/api/session.go b/tsc/internal/api/session.go index a276cc51ce59a..1dd6025cf541e 100644 --- a/tsc/internal/api/session.go +++ b/tsc/internal/api/session.go @@ -1,6 +1,7 @@ package api import ( + "bytes" "context" "encoding/base64" "errors" @@ -439,6 +440,83 @@ type Session struct { type batchResponsePage struct { encodedResponses []json.Value + errors []string +} + +type batchCheckerKey struct { + snapshot SnapshotID + project ProjectID +} + +type batchCheckerCache struct { + setups map[batchCheckerKey]checkerSetup + leases map[*compiler.Program]batchCheckerLease +} + +type batchCheckerLease struct { + checker *checker.Checker + done func() +} + +type batchCheckerCacheContextKey struct{} + +type batchRequestDecoder interface { + request(index int) any +} + +type typedBatchRequestDecoder[T any] struct { + base T + params T + apply func(params *T, index int) +} + +func newTypedBatchRequestDecoder[T any](base json.Value, apply func(params *T, index int)) (batchRequestDecoder, error) { + var params T + if err := json.Unmarshal(base, ¶ms); err != nil { + return nil, err + } + return &typedBatchRequestDecoder[T]{base: params, apply: apply}, nil +} + +func (d *typedBatchRequestDecoder[T]) request(index int) any { + d.params = d.base + d.apply(&d.params, index) + return &d.params +} + +func validateBatchColumn(name string, length int, count int) error { + if length != 0 && length != count { + return fmt.Errorf("parameter %q has %d values, expected %d", name, length, count) + } + return nil +} + +type parsedRequestContext struct { + method string + params any +} + +type parsedRequestContextKey struct{} + +type batchGroupState struct { + checkerCache batchCheckerCache + parsedRequest parsedRequestContext + ctx context.Context +} + +func newBatchGroupState(ctx context.Context, method Method) *batchGroupState { + state := &batchGroupState{parsedRequest: parsedRequestContext{method: string(method)}} + ctx = context.WithValue(ctx, batchCheckerCacheContextKey{}, &state.checkerCache) + state.ctx = context.WithValue(ctx, parsedRequestContextKey{}, &state.parsedRequest) + return state +} + +func (c *batchCheckerCache) release() { + for program, lease := range c.leases { + lease.done() + delete(c.leases, program) + } + clear(c.setups) } // Ensure Session implements Handler @@ -673,6 +751,14 @@ func (setup checkerSetup) resolveLocation(handle NodeHandle, file *DocumentIdent // setupChecker resolves snapshot, program, and type checker for a project. // Callers must defer setup.done() to release the checker. func (s *Session) setupChecker(ctx context.Context, snapshot SnapshotID, projectHandle ProjectID) (checkerSetup, error) { + key := batchCheckerKey{snapshot: snapshot, project: projectHandle} + cache, _ := ctx.Value(batchCheckerCacheContextKey{}).(*batchCheckerCache) + if cache != nil { + if setup, ok := cache.setups[key]; ok { + return setup, nil + } + } + sd, err := s.getSnapshotData(snapshot) if err != nil { return checkerSetup{}, err @@ -683,14 +769,43 @@ func (s *Session) setupChecker(ctx context.Context, snapshot SnapshotID, project return checkerSetup{}, err } + if cache != nil && cache.leases != nil { + if lease, ok := cache.leases[program]; ok { + setup := checkerSetup{ + sd: sd, + program: program, + checker: lease.checker, + done: func() {}, + projectID: projectHandle, + } + if cache.setups == nil { + cache.setups = make(map[batchCheckerKey]checkerSetup) + } + cache.setups[key] = setup + return setup, nil + } + } + c, done := program.GetTypeChecker(core.WithCheckerLifetime(ctx, core.CheckerLifetimeAPI)) - return checkerSetup{ + setup := checkerSetup{ sd: sd, program: program, checker: c, done: done, projectID: projectHandle, - }, nil + } + if cache != nil { + if cache.setups == nil { + cache.setups = make(map[batchCheckerKey]checkerSetup) + } + if cache.leases == nil { + cache.leases = make(map[*compiler.Program]batchCheckerLease) + } + cache.leases[program] = batchCheckerLease{checker: c, done: done} + setup.done = func() {} + cache.setups[key] = setup + } + return setup, nil } // setupLanguageService creates a LanguageService for the given snapshot/project. @@ -730,9 +845,15 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json. return "pong", nil } - parsed, err := unmarshalPayload(method, params) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrInvalidRequest, err) + var parsed any + if request, ok := ctx.Value(parsedRequestContextKey{}).(*parsedRequestContext); ok && request.method == method { + parsed = request.params + } else { + var err error + parsed, err = unmarshalPayload(method, params) + if err != nil { + return nil, fmt.Errorf("%w: %w", ErrInvalidRequest, err) + } } switch method { @@ -786,20 +907,12 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json. return s.handleGetConfigSourceFile(ctx, parsed.(*GetSourceFileParams)) case string(MethodGetSymbolAtPosition): return s.handleGetSymbolAtPosition(ctx, parsed.(*GetSymbolAtPositionParams)) - case string(MethodGetSymbolsAtPositions): - return s.handleGetSymbolsAtPositions(ctx, parsed.(*GetSymbolsAtPositionsParams)) case string(MethodGetSymbolAtLocation): return s.handleGetSymbolAtLocation(ctx, parsed.(*GetSymbolAtLocationParams)) - case string(MethodGetSymbolsAtLocations): - return s.handleGetSymbolsAtLocations(ctx, parsed.(*GetSymbolsAtLocationsParams)) case string(MethodGetSymbolOfSourceFile): return s.handleGetSymbolOfSourceFile(ctx, parsed.(*GetSymbolOfSourceFileParams)) - case string(MethodGetSymbolsOfSourceFiles): - return s.handleGetSymbolsOfSourceFiles(ctx, parsed.(*GetSymbolsOfSourceFilesParams)) case string(MethodGetTypeOfSymbol): return s.handleGetTypeOfSymbol(ctx, parsed.(*GetTypeOfSymbolParams)) - case string(MethodGetTypesOfSymbols): - return s.handleGetTypesOfSymbols(ctx, parsed.(*GetTypesOfSymbolsParams)) case string(MethodGetDeclaredTypeOfSymbol): return s.handleGetDeclaredTypeOfSymbol(ctx, parsed.(*GetTypeOfSymbolParams)) case string(MethodGetNonMissingTypeOfSymbol): @@ -814,12 +927,8 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json. return s.handleGetResolvedSignature(ctx, parsed.(*GetResolvedSignatureParams)) case string(MethodGetTypeAtLocation): return s.handleGetTypeAtLocation(ctx, parsed.(*GetTypeAtLocationParams)) - case string(MethodGetTypeAtLocations): - return s.handleGetTypeAtLocations(ctx, parsed.(*GetTypeAtLocationsParams)) case string(MethodGetTypeAtPosition): return s.handleGetTypeAtPosition(ctx, parsed.(*GetTypeAtPositionParams)) - case string(MethodGetTypesAtPositions): - return s.handleGetTypesAtPositions(ctx, parsed.(*GetTypesAtPositionsParams)) case string(MethodGetParentOfSymbol): return s.handleGetParentOfSymbol(ctx, parsed.(*GetSymbolPropertyParams)) case string(MethodGetMembersOfSymbol): @@ -1043,61 +1152,200 @@ func (s *Session) handleBatchRequests(ctx context.Context, params *BatchRequests return s.paginateBatchResponses(page, nil, params.MaxResponseBytesPerPage) } - responses := make([]BatchResponse, len(params.Requests)) - for i, request := range params.Requests { - responses[i] = s.handleBatchRequest(ctx, request) + var responses []BatchResponse + if params.Groups != nil { + var err error + responses, err = s.handleBatchRequestGroups(ctx, params.Groups, params.GroupOrder) + if err != nil { + return nil, err + } + } else { + responses = make([]BatchResponse, len(params.Requests)) + for i, request := range params.Requests { + responses[i] = s.handleBatchRequest(ctx, request) + } + } + return s.finishBatchResponses(responses, params.MaxResponseBytesPerPage) +} + +func (s *Session) handleBatchRequestGroups(ctx context.Context, groups []BatchRequestGroup, order []uint32) ([]BatchResponse, error) { + if len(order) == 0 { + return nil, errors.New("api: invalid request: grouped batch requires groupOrder") + } + states := core.Map(groups, func(group BatchRequestGroup) *batchGroupState { + return newBatchGroupState(ctx, group.Method) + }) + for _, state := range states { + defer state.checkerCache.release() + } + cursors := make([]int, len(groups)) + decoders := make([]batchRequestDecoder, len(groups)) + for i, group := range groups { + if group.Count < 0 { + return nil, fmt.Errorf("%w: invalid request count for group %d", ErrInvalidRequest, i) + } + if group.Requests == nil { + base := group.Base + if len(bytes.TrimSpace(base)) == 0 { + base = json.Value(`{}`) + } + fields := group.Fields + if len(bytes.TrimSpace(fields)) == 0 { + fields = json.Value(`{}`) + } + decoder, err := newGeneratedBatchRequestDecoder(group.Method, base, fields, group.Count) + if err != nil { + return nil, fmt.Errorf("%w: invalid fields for group %d: %w", ErrInvalidRequest, i, err) + } + if decoder == nil { + return nil, fmt.Errorf("%w: method %q does not support columnar requests", ErrInvalidRequest, group.Method) + } + decoders[i] = decoder + } + } + responses := make([]BatchResponse, len(order)) + previousGroupIndex := -1 + for i, rawGroupIndex := range order { + groupIndex := int(rawGroupIndex) + if groupIndex >= len(groups) { + return nil, fmt.Errorf("%w: invalid group index %d", ErrInvalidRequest, groupIndex) + } + if previousGroupIndex != -1 && previousGroupIndex != groupIndex { + states[previousGroupIndex].checkerCache.release() + } + previousGroupIndex = groupIndex + group := groups[groupIndex] + requestIndex := cursors[groupIndex] + if requestIndex >= group.Count { + return nil, fmt.Errorf("%w: too many requests for group %d", ErrInvalidRequest, groupIndex) + } + if decoder := decoders[groupIndex]; decoder != nil { + state := states[groupIndex] + state.parsedRequest.params = decoder.request(requestIndex) + responses[i] = s.handleParsedBatchRequest(state.ctx, group.Method) + } else { + if requestIndex >= len(group.Requests) { + return nil, fmt.Errorf("%w: missing request parameters for group %d", ErrInvalidRequest, groupIndex) + } + params, err := mergeBatchRequestParams(group.Base, group.Requests[requestIndex]) + if err != nil { + return nil, fmt.Errorf("%w: invalid grouped request %d for %q: %w", ErrInvalidRequest, requestIndex, group.Method, err) + } + responses[i] = s.handleBatchRequest(states[groupIndex].ctx, BatchRequest{Method: group.Method, Params: params}) + } + cursors[groupIndex]++ + } + for groupIndex, group := range groups { + if cursors[groupIndex] != group.Count { + return nil, fmt.Errorf("%w: unused requests in group %d", ErrInvalidRequest, groupIndex) + } + } + return responses, nil +} + +func mergeBatchRequestParams(base, delta json.Value) (json.Value, error) { + base = bytes.TrimSpace(base) + delta = bytes.TrimSpace(delta) + if len(base) == 0 { + return delta, nil + } + if len(base) < 2 || base[0] != '{' || base[len(base)-1] != '}' { + return nil, errors.New("base must be an object") + } + if len(delta) < 2 || delta[0] != '{' || delta[len(delta)-1] != '}' { + return nil, errors.New("request parameters must be an object") + } + if len(base) == 2 { + return delta, nil + } + if len(delta) == 2 { + return base, nil + } + result := make([]byte, 0, len(base)+len(delta)) + result = append(result, base[:len(base)-1]...) + result = append(result, ',') + result = append(result, delta[1:]...) + return result, nil +} + +func (s *Session) finishBatchResponses(responses []BatchResponse, maxResponseBytesPerPage int) (*BatchRequestsResponse, error) { + results := make([]any, len(responses)) + var errorsByIndex map[int]string + for i := range responses { + results[i] = responses[i].Result + if responses[i].Error != "" { + if errorsByIndex == nil { + errorsByIndex = make(map[int]string) + } + errorsByIndex[i] = responses[i].Error + } + } + encoded, err := json.Marshal(results) + if err != nil { + return nil, err + } + errorsLength := 0 + if len(errorsByIndex) > 0 { + encodedErrors, errorsErr := json.Marshal(errorsByIndex) + if errorsErr != nil { + return nil, errorsErr + } + errorsLength = len(`,"errors":`) + len(encodedErrors) + } + pageLimit := maxResponseBytesPerPage + if pageLimit <= 0 { + pageLimit = DefaultMaxResponseBytesPerPage + } + if len(encoded)+len(`{"results":}`)+errorsLength <= pageLimit { + return &BatchRequestsResponse{Results: results, Errors: errorsByIndex, encodedArray: encoded}, nil } if s == nil { - return &BatchRequestsResponse{Responses: responses}, nil + return &BatchRequestsResponse{Results: results, Errors: errorsByIndex}, nil } page, err := newBatchResponsePage(responses) if err != nil { return nil, err } - return s.paginateBatchResponses(page, responses, params.MaxResponseBytesPerPage) + return s.paginateBatchResponses(page, responses, maxResponseBytesPerPage) } func newBatchResponsePage(responses []BatchResponse) (batchResponsePage, error) { encodedResponses := make([]json.Value, len(responses)) for i := range responses { - encoded, err := json.Marshal(&responses[i]) + encoded, err := json.Marshal(responses[i].Result) if err != nil { return batchResponsePage{}, err } encodedResponses[i] = encoded } - return batchResponsePage{encodedResponses: encodedResponses}, nil + responseErrors := make([]string, len(responses)) + for i := range responses { + responseErrors[i] = responses[i].Error + } + return batchResponsePage{encodedResponses: encodedResponses, errors: responseErrors}, nil } func (s *Session) paginateBatchResponses(page batchResponsePage, responses []BatchResponse, maxResponseBytesPerPage int) (*BatchRequestsResponse, error) { if maxResponseBytesPerPage <= 0 { maxResponseBytesPerPage = DefaultMaxResponseBytesPerPage } - encodedLength := len(`{"responses":[]}`) - pageLength := 0 - for _, encoded := range page.encodedResponses { - additionalLength := len(encoded) - if pageLength > 0 { - additionalLength++ - } - if pageLength > 0 && encodedLength+additionalLength > maxResponseBytesPerPage { - break - } - encodedLength += additionalLength - pageLength++ + pageLength, err := batchResponsePrefixLength(page, maxResponseBytesPerPage, "") + if err != nil { + return nil, err } if pageLength == len(page.encodedResponses) { - return &BatchRequestsResponse{ - Responses: responses, + response := &BatchRequestsResponse{ + Results: responsesToResults(responses), encodedResponses: page.encodedResponses, - }, nil + } + response.Errors = batchResponseErrors(page.errors[:pageLength]) + return response, nil } continuationToken := fmt.Sprintf("%s-%d", s.id, s.nextBatchResponsePageID.Add(1)) - continuationLength := len(`,"continuationToken":""`) + len(continuationToken) - for pageLength > 1 && encodedLength+continuationLength > maxResponseBytesPerPage { - encodedLength -= len(page.encodedResponses[pageLength-1]) + 1 - pageLength-- + pageLength, err = batchResponsePrefixLength(page, maxResponseBytesPerPage, continuationToken) + if err != nil { + return nil, err } currentResponses := page.encodedResponses[:pageLength] remainingResponses := page.encodedResponses[pageLength:] @@ -1105,17 +1353,76 @@ func (s *Session) paginateBatchResponses(page batchResponsePage, responses []Bat ContinuationToken: continuationToken, encodedResponses: currentResponses, } + response.Errors = batchResponseErrors(page.errors[:pageLength]) if responses != nil { - response.Responses = responses[:pageLength] + response.Results = responsesToResults(responses[:pageLength]) } - s.batchResponsePages.Store(continuationToken, batchResponsePage{ + storedPage := batchResponsePage{ encodedResponses: remainingResponses, - }) + errors: page.errors[pageLength:], + } + s.batchResponsePages.Store(continuationToken, storedPage) return response, nil } +func batchResponsePrefixLength(page batchResponsePage, maxResponseBytes int, continuationToken string) (int, error) { + encodedLength := len(`{"results":[]}`) + if continuationToken != "" { + encodedLength += len(`,"continuationToken":""`) + len(continuationToken) + } + errorLength := 0 + pageLength := 0 + for i, encoded := range page.encodedResponses { + resultLength := len(encoded) + if i > 0 { + resultLength++ + } + additionalErrorLength := 0 + if message := page.errors[i]; message != "" { + encodedMessage, err := json.Marshal(message) + if err != nil { + return 0, err + } + entryLength := len(strconv.Itoa(i)) + len(encodedMessage) + len(`"":`) + if errorLength == 0 { + additionalErrorLength = len(`,"errors":{}`) + entryLength + } else { + additionalErrorLength = 1 + entryLength + } + } + if i > 0 && encodedLength+errorLength+resultLength+additionalErrorLength > maxResponseBytes { + break + } + encodedLength += resultLength + errorLength += additionalErrorLength + pageLength++ + } + return pageLength, nil +} + +func responsesToResults(responses []BatchResponse) []any { + results := make([]any, len(responses)) + for i := range responses { + results[i] = responses[i].Result + } + return results +} + +func batchResponseErrors(errors []string) map[int]string { + var result map[int]string + for i, message := range errors { + if message == "" { + continue + } + if result == nil { + result = make(map[int]string) + } + result[i] = message + } + return result +} + func (s *Session) handleBatchRequest(ctx context.Context, request BatchRequest) (response BatchResponse) { - response.Method = request.Method if request.Method == MethodBatchRequests { response.Error = fmt.Sprintf("%s: batchRequests cannot be nested", ErrInvalidRequest) return response @@ -1134,6 +1441,21 @@ func (s *Session) handleBatchRequest(ctx context.Context, request BatchRequest) return response } +func (s *Session) handleParsedBatchRequest(ctx context.Context, method Method) (response BatchResponse) { + defer func() { + if recovered := recover(); recovered != nil { + response.Result = nil + response.Error = fmt.Sprintf("panic: %v\n%s", recovered, debug.Stack()) + } + }() + var err error + response.Result, err = s.HandleRequest(ctx, string(method), nil) + if err != nil { + response.Error = err.Error() + } + return response +} + func (s *Session) handleStartCPUProfile(_ context.Context, params *ProfileParams) (any, error) { if params == nil || params.Dir == "" { return nil, fmt.Errorf("%w: dir is required", ErrClientError) @@ -1916,57 +2238,6 @@ func (s *Session) handleGetSymbolOfSourceFile(ctx context.Context, params *GetSy return setup.newSymbolResponse(symbol), nil } -// handleGetSymbolsOfSourceFiles returns the module symbols for multiple source files. -func (s *Session) handleGetSymbolsOfSourceFiles(ctx context.Context, params *GetSymbolsOfSourceFilesParams) ([]*SymbolResponse, error) { - setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) - if err != nil { - return nil, err - } - defer setup.done() - - results := make([]*SymbolResponse, len(params.Files)) - for i, file := range params.Files { - sourceFile := setup.program.GetSourceFile(file.ToFileName()) - if sourceFile == nil { - return nil, fmt.Errorf("%w: source file not found: %v", ErrClientError, file) - } - symbol := setup.checker.GetSymbolAtLocation(sourceFile.AsNode()) - if symbol != nil { - results[i] = setup.newSymbolResponse(symbol) - } - } - return results, nil -} - -// handleGetSymbolsAtPositions returns symbols at multiple positions in a file. -func (s *Session) handleGetSymbolsAtPositions(ctx context.Context, params *GetSymbolsAtPositionsParams) ([]*SymbolResponse, error) { - setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) - if err != nil { - return nil, err - } - defer setup.done() - - sourceFile := setup.program.GetSourceFile(params.File.ToFileName()) - if sourceFile == nil { - return nil, fmt.Errorf("%w: source file not found: %v", ErrClientError, params.File) - } - - positionMap := sourceFile.GetPositionMap() - results := make([]*SymbolResponse, len(params.Positions)) - for i, pos := range params.Positions { - node := astnav.GetTouchingPropertyName(sourceFile, positionMap.UTF16ToUTF8(int(pos))) - if node == nil { - continue - } - symbol := setup.checker.GetSymbolAtLocation(node) - if symbol != nil { - results[i] = setup.newSymbolResponse(symbol) - } - } - - return results, nil -} - // handleGetSymbolAtLocation returns the symbol at a node location. // @gen-proto-nullable func (s *Session) handleGetSymbolAtLocation(ctx context.Context, params *GetSymbolAtLocationParams) (*SymbolResponse, error) { @@ -1992,32 +2263,6 @@ func (s *Session) handleGetSymbolAtLocation(ctx context.Context, params *GetSymb return setup.newSymbolResponse(symbol), nil } -// handleGetSymbolsAtLocations returns symbols at multiple node locations. -func (s *Session) handleGetSymbolsAtLocations(ctx context.Context, params *GetSymbolsAtLocationsParams) ([]*SymbolResponse, error) { - setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) - if err != nil { - return nil, err - } - defer setup.done() - - results := make([]*SymbolResponse, len(params.Locations)) - for i, loc := range params.Locations { - node, err := setup.sd.resolveNodeHandle(setup.program, loc) - if err != nil { - return nil, err - } - if node == nil { - continue - } - symbol := setup.checker.GetSymbolAtLocation(node) - if symbol != nil { - results[i] = setup.newSymbolResponse(symbol) - } - } - - return results, nil -} - // handleGetTypeOfSymbol returns the type of a symbol. func (s *Session) handleGetTypeOfSymbol(ctx context.Context, params *GetTypeOfSymbolParams) (*TypeResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) @@ -2034,28 +2279,6 @@ func (s *Session) handleGetTypeOfSymbol(ctx context.Context, params *GetTypeOfSy return setup.newTypeResponse(setup.checker.GetTypeOfSymbol(symbol)), nil } -// handleGetTypesOfSymbols returns the types of multiple symbols. -func (s *Session) handleGetTypesOfSymbols(ctx context.Context, params *GetTypesOfSymbolsParams) ([]*TypeResponse, error) { - setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) - if err != nil { - return nil, err - } - defer setup.done() - - results := make([]*TypeResponse, len(params.Symbols)) - for i, symHandle := range params.Symbols { - symbol, err := setup.resolveSymbolHandle(symHandle) - if err != nil { - return nil, err - } - // resolveSymbolHandle errors on an unresolvable handle and GetTypeOfSymbol - // never returns nil, so every element resolves to a type (error type at worst). - results[i] = setup.newTypeResponse(setup.checker.GetTypeOfSymbol(symbol)) - } - - return results, nil -} - // handleGetDeclaredTypeOfSymbol returns the declared type of a symbol (e.g. the type alias body for type alias symbols). func (s *Session) handleGetDeclaredTypeOfSymbol(ctx context.Context, params *GetTypeOfSymbolParams) (*TypeResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) @@ -2190,28 +2413,6 @@ func (s *Session) handleGetTypeAtLocation(ctx context.Context, params *GetTypeAt return setup.newTypeResponse(setup.checker.GetTypeAtLocation(node)), nil } -// handleGetTypeAtLocations returns types at multiple node locations. -func (s *Session) handleGetTypeAtLocations(ctx context.Context, params *GetTypeAtLocationsParams) ([]*TypeResponse, error) { - setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) - if err != nil { - return nil, err - } - defer setup.done() - - results := make([]*TypeResponse, len(params.Locations)) - for i, loc := range params.Locations { - node, err := setup.sd.resolveNodeHandle(setup.program, loc) - if err != nil { - return nil, err - } - // resolveNodeHandle errors on an unresolvable handle and GetTypeAtLocation - // never returns nil, so every element resolves to a type (error type at worst). - results[i] = setup.newTypeResponse(setup.checker.GetTypeAtLocation(node)) - } - - return results, nil -} - // handleGetTypeAtPosition returns the type at a position in a file. // @gen-proto-nullable func (s *Session) handleGetTypeAtPosition(ctx context.Context, params *GetTypeAtPositionParams) (*TypeResponse, error) { @@ -2240,35 +2441,6 @@ func (s *Session) handleGetTypeAtPosition(ctx context.Context, params *GetTypeAt return setup.newTypeResponse(t), nil } -// handleGetTypesAtPositions returns types at multiple positions in a file. -func (s *Session) handleGetTypesAtPositions(ctx context.Context, params *GetTypesAtPositionsParams) ([]*TypeResponse, error) { - setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) - if err != nil { - return nil, err - } - defer setup.done() - - sourceFile := setup.program.GetSourceFile(params.File.ToFileName()) - if sourceFile == nil { - return nil, fmt.Errorf("%w: source file not found: %v", ErrClientError, params.File) - } - - positionMap := sourceFile.GetPositionMap() - results := make([]*TypeResponse, len(params.Positions)) - for i, pos := range params.Positions { - node := astnav.GetTouchingPropertyName(sourceFile, positionMap.UTF16ToUTF8(int(pos))) - if node == nil { - continue - } - t := setup.checker.GetTypeAtLocation(node) - if t != nil { - results[i] = setup.newTypeResponse(t) - } - } - - return results, nil -} - // @gen-proto-nullable func (s *Session) handleGetParentOfSymbol(_ context.Context, params *GetSymbolPropertyParams) (*SymbolResponse, error) { return s.resolveSymbolPropertyOfSymbol(params, func(sym *ast.Symbol) *ast.Symbol { return sym.Parent }) diff --git a/tsc/internal/api/session_batch_test.go b/tsc/internal/api/session_batch_test.go index d5fc1a8aa0d7d..83f2126016620 100644 --- a/tsc/internal/api/session_batch_test.go +++ b/tsc/internal/api/session_batch_test.go @@ -4,6 +4,7 @@ import ( "context" "strings" "testing" + "time" "github.com/microsoft/TypeScript/tsc/internal/json" "github.com/microsoft/TypeScript/tsc/internal/testutil/projecttestutil" @@ -22,17 +23,83 @@ func TestHandleBatchRequests(t *testing.T) { }) assert.NilError(t, err) - assert.Equal(t, len(response.Responses), 2) - assert.Equal(t, response.Responses[0].Method, Method("ping")) - assert.Equal(t, response.Responses[0].Result, "pong") - assert.Equal(t, response.Responses[0].Error, "") - assert.Equal(t, response.Responses[1].Method, Method("unknown")) - requestErr := response.Responses[1].Error + assert.Equal(t, len(response.Results), 2) + assert.Equal(t, response.Results[0], "pong") + requestErr := response.Errors[1] assert.Assert(t, strings.Contains(requestErr, "unknown API method")) encoded, err := json.Marshal(response) assert.NilError(t, err) - assert.Assert(t, strings.Contains(string(encoded), `"error":"api: invalid request: unknown API method \"unknown\""`)) + assert.Assert(t, strings.Contains(string(encoded), `"errors":{"1":"api: invalid request: unknown API method \"unknown\""}`)) +} + +func TestHandleGroupedBatchRequests(t *testing.T) { + t.Parallel() + + response, err := (&Session{}).HandleRequest(context.Background(), string(MethodBatchRequests), json.Value(`{ + "groups":[ + {"method":"echo","base":{"group":"a"},"count":2,"requests":[{"value":1},{"value":3}]}, + {"method":"echo","base":{"group":"b"},"count":1,"requests":[{"value":2}]} + ], + "groupOrder":[0,1,0] + }`)) + assert.NilError(t, err) + encoded, err := json.Marshal(response) + assert.NilError(t, err) + assert.Equal(t, string(encoded), `{"results":[{"group":"a","value":1},{"group":"b","value":2},{"group":"a","value":3}]}`) +} + +func TestBatchCheckerCacheReusesProgramAcrossSnapshots(t *testing.T) { + t.Parallel() + + projectSession, _ := projecttestutil.Setup(map[string]any{ + "/tsconfig.json": `{ "compilerOptions": { "noLib": true }, "files": ["index.ts"] }`, + "/index.ts": `export const value = true;`, + }) + defer projectSession.Close() + session := NewLSPSession(projectSession, nil) + defer session.Close() + + base, err := session.handleUpdateSnapshot(t.Context(), &UpdateSnapshotParams{ + OpenProjects: []DocumentIdentifier{{FileName: "/tsconfig.json"}}, + }) + assert.NilError(t, err) + updated, err := session.handleUpdateSnapshot(t.Context(), &UpdateSnapshotParams{Snapshot: base.Snapshot}) + assert.NilError(t, err) + projectID := base.Projects[0].Id + baseProgram, err := session.snapshots[base.Snapshot].getProgram(projectID) + assert.NilError(t, err) + updatedProgram, err := session.snapshots[updated.Snapshot].getProgram(projectID) + assert.NilError(t, err) + assert.Assert(t, baseProgram == updatedProgram) + + cache := &batchCheckerCache{} + ctx := context.WithValue(t.Context(), batchCheckerCacheContextKey{}, cache) + first, err := session.setupChecker(ctx, base.Snapshot, projectID) + assert.NilError(t, err) + defer cache.release() + + type setupResult struct { + setup checkerSetup + err error + } + result := make(chan setupResult, 1) + go func() { + setup, setupErr := session.setupChecker(ctx, updated.Snapshot, projectID) + result <- setupResult{setup: setup, err: setupErr} + }() + + select { + case second := <-result: + assert.NilError(t, second.err) + assert.Assert(t, first.checker == second.setup.checker) + assert.Assert(t, first.sd != second.setup.sd) + case <-time.After(time.Second): + cache.release() + <-result + cache.release() + t.Fatal("setupChecker blocked acquiring a checker already leased by the batch") + } } func TestHandleBatchRequestsRecoversPerRequestPanics(t *testing.T) { @@ -48,17 +115,9 @@ func TestHandleBatchRequestsRecoversPerRequestPanics(t *testing.T) { }) assert.NilError(t, err) - assert.Equal(t, response.Responses[0].Result, "pong") - assert.Assert(t, strings.Contains(response.Responses[1].Error, "panic:")) - assert.Equal(t, response.Responses[2].Result, "pong") -} - -func TestBatchResponseEncodesEmptyResult(t *testing.T) { - t.Parallel() - - encoded, err := json.Marshal(BatchResponse{Method: MethodGetSignaturesOfType, Result: []any{}}) - assert.NilError(t, err) - assert.Equal(t, string(encoded), `{"method":"getSignaturesOfType","result":[]}`) + assert.Equal(t, response.Results[0], "pong") + assert.Assert(t, strings.Contains(response.Errors[1], "panic:")) + assert.Equal(t, response.Results[2], "pong") } func TestHandleBatchRequestsPaginatesResponses(t *testing.T) { @@ -79,14 +138,14 @@ func TestHandleBatchRequestsPaginatesResponses(t *testing.T) { MaxResponseBytesPerPage: maxResponseBytesPerPage, }) assert.NilError(t, err) - var responses []BatchResponse + var results []any for { encoded, err := json.Marshal(response) assert.NilError(t, err) assert.Assert(t, len(encoded) <= maxResponseBytesPerPage) var wireResponse BatchRequestsResponse assert.NilError(t, json.Unmarshal(encoded, &wireResponse)) - responses = append(responses, wireResponse.Responses...) + results = append(results, wireResponse.Results...) if wireResponse.ContinuationToken == "" { break } @@ -97,10 +156,50 @@ func TestHandleBatchRequestsPaginatesResponses(t *testing.T) { assert.NilError(t, err) } - assert.Equal(t, len(responses), len(requests)) - for _, response := range responses { - assert.Equal(t, response.Method, Method("ping")) - assert.Equal(t, response.Result, "pong") + assert.Equal(t, len(results), len(requests)) + for _, result := range results { + assert.Equal(t, result, "pong") + } +} + +func TestHandleBatchRequestsPaginatesErrorsWithinLimit(t *testing.T) { + t.Parallel() + projectSession, _ := projecttestutil.Setup(map[string]any{}) + defer projectSession.Close() + session := NewLSPSession(projectSession, nil) + defer session.Close() + session.id = "s" + + for maxResponseBytesPerPage := 127; maxResponseBytesPerPage <= 400; maxResponseBytesPerPage++ { + session.nextBatchResponsePageID.Store(0) + requests := make([]BatchRequest, 10) + for i := range requests { + requests[i] = BatchRequest{Method: "unknown", Params: json.Value{}} + } + + response, err := session.handleBatchRequests(context.Background(), &BatchRequestsParams{ + Requests: requests, + MaxResponseBytesPerPage: maxResponseBytesPerPage, + }) + assert.NilError(t, err) + resultCount := 0 + for { + encoded, err := json.Marshal(response) + assert.NilError(t, err) + assert.Assert(t, len(encoded) <= maxResponseBytesPerPage, "encoded page has %d bytes with limit %d", len(encoded), maxResponseBytesPerPage) + var wireResponse BatchRequestsResponse + assert.NilError(t, json.Unmarshal(encoded, &wireResponse)) + resultCount += len(wireResponse.Results) + if wireResponse.ContinuationToken == "" { + break + } + response, err = session.handleBatchRequests(context.Background(), &BatchRequestsParams{ + ContinuationToken: wireResponse.ContinuationToken, + MaxResponseBytesPerPage: maxResponseBytesPerPage, + }) + assert.NilError(t, err) + } + assert.Equal(t, resultCount, len(requests)) } } @@ -117,7 +216,7 @@ func TestHandleBatchRequestsAllowsOversizedSingleResponse(t *testing.T) { MaxResponseBytesPerPage: 1, }) assert.NilError(t, err) - assert.Equal(t, len(response.Responses), 1) + assert.Equal(t, len(response.Results), 1) assert.Equal(t, response.ContinuationToken, "") } @@ -139,12 +238,12 @@ func TestHandleBatchRequestsPageLimitIsRequestScoped(t *testing.T) { MaxResponseBytesPerPage: 1, }) assert.NilError(t, err) - assert.Equal(t, len(limited.Responses), 1) + assert.Equal(t, len(limited.Results), 1) assert.Assert(t, limited.ContinuationToken != "") unlimited, err := session.handleBatchRequests(context.Background(), &BatchRequestsParams{Requests: requests}) assert.NilError(t, err) - assert.Equal(t, len(unlimited.Responses), len(requests)) + assert.Equal(t, len(unlimited.Results), len(requests)) assert.Equal(t, unlimited.ContinuationToken, "") } @@ -172,6 +271,6 @@ func TestHandleBatchRequestsRejectsNestedBatch(t *testing.T) { Requests: []BatchRequest{{Method: MethodBatchRequests, Params: json.Value(`{"requests":[]}`)}}, }) assert.NilError(t, err) - assert.Equal(t, len(response.Responses), 1) - assert.Assert(t, strings.Contains(response.Responses[0].Error, "batchRequests cannot be nested")) + assert.Equal(t, len(response.Results), 1) + assert.Assert(t, strings.Contains(response.Errors[0], "batchRequests cannot be nested")) } From 610bdace66aff9dda58e2e31f5270c4c6c5baa08 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 15 Sep 2026 10:12:44 -0700 Subject: [PATCH 2/5] Posiiton lookup optimization --- packages/typescript/src/api/async/client.ts | 10 +-- packages/typescript/src/api/batch.ts | 3 +- packages/typescript/test/async/api.bench.ts | 6 ++ tsc/internal/api/session.go | 75 +++++++++++++++------ tsc/internal/api/session_batch_test.go | 49 ++++++++++++++ 5 files changed, 118 insertions(+), 25 deletions(-) diff --git a/packages/typescript/src/api/async/client.ts b/packages/typescript/src/api/async/client.ts index 2efec00b15c03..371b1e7a68ff5 100644 --- a/packages/typescript/src/api/async/client.ts +++ b/packages/typescript/src/api/async/client.ts @@ -49,11 +49,12 @@ interface GroupedBatchRequest { function groupBatchRequests(requests: readonly T[]): { groups: GroupedBatchRequest[]; - groupOrder: number[]; + groupOrder?: number[]; } | undefined { const groupIndexes = new Map(); const requestsByMethod: T[][] = []; const groupOrder: number[] = []; + let hasBatchableGroup = false; for (const request of requests) { let groupIndex = groupIndexes.get(request.method); if (groupIndex === undefined) { @@ -62,9 +63,10 @@ function groupBatchRequests group.length >= 4)) return undefined; + if (!hasBatchableGroup) return undefined; const groups: GroupedBatchRequest[] = []; for (const [method, groupIndex] of groupIndexes) { @@ -86,7 +88,7 @@ function groupBatchRequests[]): Record | undefined { @@ -295,7 +297,7 @@ export class Client { // Paired benchmarks show grouping pays for its construction cost at four requests. const grouped = requests.length >= 4 ? groupBatchRequests(requests) : undefined; - const params: BatchRequestsParams | { groups: GroupedBatchRequest[]; groupOrder: number[]; maxResponseBytesPerPage?: number | undefined; } = grouped + const params: BatchRequestsParams | { groups: GroupedBatchRequest[]; groupOrder?: number[]; maxResponseBytesPerPage?: number | undefined; } = grouped ? { groups: grouped.groups, groupOrder: grouped.groupOrder, maxResponseBytesPerPage: this.options.maxResponseBytesPerPage } : { requests: requests.map(request => ({ method: request.method, params: request.params })), maxResponseBytesPerPage: this.options.maxResponseBytesPerPage }; const response = await this.batchRequest(params as BatchRequestsParams); diff --git a/packages/typescript/src/api/batch.ts b/packages/typescript/src/api/batch.ts index 60fb96ad1b446..2a8b56e5e7e69 100644 --- a/packages/typescript/src/api/batch.ts +++ b/packages/typescript/src/api/batch.ts @@ -23,8 +23,7 @@ export function createGroupedBatchRequest( }; } return { - groups: [{ method, base, count, fields: Object.fromEntries(entries.map(([key, values]) => [key, [...values]])) }], - groupOrder: Array(count).fill(0), + groups: [{ method, base, count, fields: Object.fromEntries(entries) }], }; } diff --git a/packages/typescript/test/async/api.bench.ts b/packages/typescript/test/async/api.bench.ts index 48c2380a76d05..0a29c4e2109b5 100644 --- a/packages/typescript/test/async/api.bench.ts +++ b/packages/typescript/test/async/api.bench.ts @@ -129,6 +129,12 @@ export async function runBenchmarks(options?: { filter?: string; singleIteration } // @generators-skip-block-end }, { async: isAsync, beforeAll: all(spawnAPI, loadSnapshot, createChecker, getProgramTS) }) + // @sync-skip-block-start + .add(`getSymbolAtPosition - ${programIdentifierCount} identifiers (concurrent)`, async () => { + const positions = collectIdentifiers(file).map(node => node.pos); + await Promise.all(positions.map(position => project.checker.getSymbolAtPosition("program.ts", position))); + }, { async: isAsync, beforeAll: all(spawnAPI, loadSnapshot, createChecker, getProgramTS) }) + // @sync-skip-block-end .add(`getSymbolAtPosition - ${programIdentifierCount} identifiers (batched)`, async () => { const positions = collectIdentifiers(file).map(node => node.pos); await project.checker.getSymbolAtPosition("program.ts", positions); // @generators: api.batch(project.checker.getSymbolAtPosition.gen("program.ts", positions)); diff --git a/tsc/internal/api/session.go b/tsc/internal/api/session.go index 1dd6025cf541e..ce93fd115e728 100644 --- a/tsc/internal/api/session.go +++ b/tsc/internal/api/session.go @@ -449,8 +449,16 @@ type batchCheckerKey struct { } type batchCheckerCache struct { - setups map[batchCheckerKey]checkerSetup - leases map[*compiler.Program]batchCheckerLease + setups map[batchCheckerKey]checkerSetup + leases map[*compiler.Program]batchCheckerLease + positionLookup batchPositionLookup +} + +type batchPositionLookup struct { + program *compiler.Program + file DocumentIdentifier + sourceFile *ast.SourceFile + positionMap *ast.PositionMap } type batchCheckerLease struct { @@ -517,6 +525,7 @@ func (c *batchCheckerCache) release() { delete(c.leases, program) } clear(c.setups) + c.positionLookup = batchPositionLookup{} } // Ensure Session implements Handler @@ -748,6 +757,31 @@ func (setup checkerSetup) resolveLocation(handle NodeHandle, file *DocumentIdent return nil, nil } +func (setup checkerSetup) resolvePosition(ctx context.Context, file DocumentIdentifier, position uint32) (*ast.Node, error) { + cache, _ := ctx.Value(batchCheckerCacheContextKey{}).(*batchCheckerCache) + var sourceFile *ast.SourceFile + var positionMap *ast.PositionMap + if cache != nil && cache.positionLookup.program == setup.program && cache.positionLookup.file == file { + sourceFile = cache.positionLookup.sourceFile + positionMap = cache.positionLookup.positionMap + } else { + sourceFile = setup.program.GetSourceFile(file.ToFileName()) + if sourceFile == nil { + return nil, fmt.Errorf("%w: source file not found: %v", ErrClientError, file) + } + positionMap = sourceFile.GetPositionMap() + if cache != nil { + cache.positionLookup = batchPositionLookup{ + program: setup.program, + file: file, + sourceFile: sourceFile, + positionMap: positionMap, + } + } + } + return astnav.GetTouchingPropertyName(sourceFile, positionMap.UTF16ToUTF8(int(position))), nil +} + // setupChecker resolves snapshot, program, and type checker for a project. // Callers must defer setup.done() to release the checker. func (s *Session) setupChecker(ctx context.Context, snapshot SnapshotID, projectHandle ProjectID) (checkerSetup, error) { @@ -1169,7 +1203,8 @@ func (s *Session) handleBatchRequests(ctx context.Context, params *BatchRequests } func (s *Session) handleBatchRequestGroups(ctx context.Context, groups []BatchRequestGroup, order []uint32) ([]BatchResponse, error) { - if len(order) == 0 { + implicitSingleGroupOrder := len(order) == 0 && len(groups) == 1 + if len(order) == 0 && !implicitSingleGroupOrder { return nil, errors.New("api: invalid request: grouped batch requires groupOrder") } states := core.Map(groups, func(group BatchRequestGroup) *batchGroupState { @@ -1178,7 +1213,6 @@ func (s *Session) handleBatchRequestGroups(ctx context.Context, groups []BatchRe for _, state := range states { defer state.checkerCache.release() } - cursors := make([]int, len(groups)) decoders := make([]batchRequestDecoder, len(groups)) for i, group := range groups { if group.Count < 0 { @@ -1203,10 +1237,19 @@ func (s *Session) handleBatchRequestGroups(ctx context.Context, groups []BatchRe decoders[i] = decoder } } - responses := make([]BatchResponse, len(order)) + responseCount := len(order) + if implicitSingleGroupOrder { + responseCount = groups[0].Count + } + responses := make([]BatchResponse, responseCount) + + cursors := make([]int, len(groups)) previousGroupIndex := -1 - for i, rawGroupIndex := range order { - groupIndex := int(rawGroupIndex) + for i := range responseCount { + groupIndex := 0 + if !implicitSingleGroupOrder { + groupIndex = int(order[i]) + } if groupIndex >= len(groups) { return nil, fmt.Errorf("%w: invalid group index %d", ErrInvalidRequest, groupIndex) } @@ -2197,13 +2240,10 @@ func (s *Session) handleGetSymbolAtPosition(ctx context.Context, params *GetSymb } defer setup.done() - sourceFile := setup.program.GetSourceFile(params.File.ToFileName()) - if sourceFile == nil { - return nil, fmt.Errorf("%w: source file not found: %v", ErrClientError, params.File) + node, err := setup.resolvePosition(ctx, params.File, params.Position) + if err != nil { + return nil, err } - - positionMap := sourceFile.GetPositionMap() - node := astnav.GetTouchingPropertyName(sourceFile, positionMap.UTF16ToUTF8(int(params.Position))) if node == nil { return nil, nil } @@ -2422,13 +2462,10 @@ func (s *Session) handleGetTypeAtPosition(ctx context.Context, params *GetTypeAt } defer setup.done() - sourceFile := setup.program.GetSourceFile(params.File.ToFileName()) - if sourceFile == nil { - return nil, fmt.Errorf("%w: source file not found: %v", ErrClientError, params.File) + node, err := setup.resolvePosition(ctx, params.File, params.Position) + if err != nil { + return nil, err } - - positionMap := sourceFile.GetPositionMap() - node := astnav.GetTouchingPropertyName(sourceFile, positionMap.UTF16ToUTF8(int(params.Position))) if node == nil { return nil, nil } diff --git a/tsc/internal/api/session_batch_test.go b/tsc/internal/api/session_batch_test.go index 83f2126016620..d53f383b2a436 100644 --- a/tsc/internal/api/session_batch_test.go +++ b/tsc/internal/api/session_batch_test.go @@ -49,6 +49,55 @@ func TestHandleGroupedBatchRequests(t *testing.T) { assert.Equal(t, string(encoded), `{"results":[{"group":"a","value":1},{"group":"b","value":2},{"group":"a","value":3}]}`) } +func TestHandleSingleGroupedBatchRequestsInfersOrder(t *testing.T) { + t.Parallel() + + response, err := (&Session{}).HandleRequest(context.Background(), string(MethodBatchRequests), json.Value(`{ + "groups":[ + {"method":"echo","base":{"group":"a"},"count":3,"requests":[{"value":1},{"value":2},{"value":3}]} + ] + }`)) + assert.NilError(t, err) + encoded, err := json.Marshal(response) + assert.NilError(t, err) + assert.Equal(t, string(encoded), `{"results":[{"group":"a","value":1},{"group":"a","value":2},{"group":"a","value":3}]}`) +} + +func TestHandleGroupedBatchRequestsReacquiresCheckerAfterInterleaving(t *testing.T) { + t.Parallel() + + projectSession, _ := projecttestutil.Setup(map[string]any{ + "/tsconfig.json": `{ "compilerOptions": { "noLib": true }, "files": ["index.ts"] }`, + "/index.ts": `export const value = true;`, + }) + defer projectSession.Close() + session := NewLSPSession(projectSession, nil) + defer session.Close() + + snapshot, err := session.handleUpdateSnapshot(t.Context(), &UpdateSnapshotParams{ + OpenProjects: []DocumentIdentifier{{FileName: "/tsconfig.json"}}, + }) + assert.NilError(t, err) + base, err := json.Marshal(map[string]any{ + "snapshot": snapshot.Snapshot, + "project": snapshot.Projects[0].Id, + "file": "/index.ts", + }) + assert.NilError(t, err) + + responses, err := session.handleBatchRequestGroups(t.Context(), []BatchRequestGroup{ + {Method: MethodGetSymbolAtPosition, Base: base, Count: 2, Fields: json.Value(`{"position":[13,13]}`)}, + {Method: "echo", Count: 1, Requests: []json.Value{json.Value(`{"value":1}`)}}, + }, []uint32{0, 1, 0}) + assert.NilError(t, err) + assert.Equal(t, responses[0].Error, "") + assert.Equal(t, responses[1].Error, "") + assert.Equal(t, responses[2].Error, "") + first := responses[0].Result.(*SymbolResponse) + last := responses[2].Result.(*SymbolResponse) + assert.Equal(t, first.Id, last.Id) +} + func TestBatchCheckerCacheReusesProgramAcrossSnapshots(t *testing.T) { t.Parallel() From e30f56c74ac1538849e3931f045f5023729cbc64 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 15 Sep 2026 11:38:40 -0700 Subject: [PATCH 3/5] Fix generator bug, move helper into support library --- packages/typescript/src/api/async/client.ts | 80 +------------------ packages/typescript/src/api/batch.ts | 79 ++++++++++++++++++ packages/typescript/src/api/sync/client.ts | 4 +- .../test/sync/api-generators.test.ts | 38 +++++++++ tools/gen-proto/main.go | 2 +- tsc/internal/api/session.go | 55 +++++-------- tsc/internal/api/session_batch_test.go | 13 +++ 7 files changed, 158 insertions(+), 113 deletions(-) diff --git a/packages/typescript/src/api/async/client.ts b/packages/typescript/src/api/async/client.ts index 371b1e7a68ff5..f92b2f46dbc77 100644 --- a/packages/typescript/src/api/async/client.ts +++ b/packages/typescript/src/api/async/client.ts @@ -9,6 +9,7 @@ import { } from "#vscode-jsonrpc/node"; import type { ChildProcess } from "node:child_process"; import type { Socket } from "node:net"; +import { groupBatchRequests } from "../batch.ts"; import { type FileSystem, fsCallbackNames, @@ -39,81 +40,6 @@ import { export type { ClientOptions, ClientSocketOptions, ClientSpawnOptions }; -interface GroupedBatchRequest { - method: APIRequest["method"]; - base?: Record; - count: number; - fields?: Record; - requests?: unknown[]; -} - -function groupBatchRequests(requests: readonly T[]): { - groups: GroupedBatchRequest[]; - groupOrder?: number[]; -} | undefined { - const groupIndexes = new Map(); - const requestsByMethod: T[][] = []; - const groupOrder: number[] = []; - let hasBatchableGroup = false; - for (const request of requests) { - let groupIndex = groupIndexes.get(request.method); - if (groupIndex === undefined) { - groupIndex = requestsByMethod.length; - groupIndexes.set(request.method, groupIndex); - requestsByMethod.push([]); - } - requestsByMethod[groupIndex].push(request); - if (requestsByMethod[groupIndex].length === 4) hasBatchableGroup = true; - groupOrder.push(groupIndex); - } - if (!hasBatchableGroup) return undefined; - - const groups: GroupedBatchRequest[] = []; - for (const [method, groupIndex] of groupIndexes) { - const groupedRequests = requestsByMethod[groupIndex]; - const params = groupedRequests.map(request => request.params); - if (params.every(param => typeof param === "object" && param !== null && !Array.isArray(param))) { - const records = params as Record[]; - const base = commonParams(records); - const commonKeys = new Set(Object.keys(base)); - const deltas = records.map(param => Object.fromEntries(Object.entries(param).filter(([key]) => !commonKeys.has(key)))); - const fields = parameterColumns(deltas); - groups.push( - fields - ? { method, base, count: records.length, fields } - : { method, base, count: records.length, requests: deltas }, - ); - } - else { - groups.push({ method, count: params.length, requests: params }); - } - } - return groups.length === 1 ? { groups } : { groups, groupOrder }; -} - -function parameterColumns(params: readonly Record[]): Record | undefined { - const keys = Object.keys(params[0]); - if ( - !params.every(param => { - const paramKeys = Object.keys(param); - return paramKeys.length === keys.length - && keys.every(key => Object.hasOwn(param, key) && param[key] !== undefined); - }) - ) return undefined; - return Object.fromEntries(keys.map(key => [key, params.map(param => param[key])])) as Record; -} - -function commonParams(params: readonly Record[]): Record { - const first = params[0]; - return Object.fromEntries( - Object.entries(first).filter(([key, value]) => { - const type = typeof value; - return (value === null || type !== "object" && type !== "undefined") - && params.every(param => Object.hasOwn(param, key) && param[key] === value); - }), - ); -} - /** * Client handles communication with the TypeScript API server * over STDIO (spawned process) or a Unix domain socket using JSON-RPC. @@ -297,10 +223,10 @@ export class Client { // Paired benchmarks show grouping pays for its construction cost at four requests. const grouped = requests.length >= 4 ? groupBatchRequests(requests) : undefined; - const params: BatchRequestsParams | { groups: GroupedBatchRequest[]; groupOrder?: number[]; maxResponseBytesPerPage?: number | undefined; } = grouped + const params: BatchRequestsParams = grouped ? { groups: grouped.groups, groupOrder: grouped.groupOrder, maxResponseBytesPerPage: this.options.maxResponseBytesPerPage } : { requests: requests.map(request => ({ method: request.method, params: request.params })), maxResponseBytesPerPage: this.options.maxResponseBytesPerPage }; - const response = await this.batchRequest(params as BatchRequestsParams); + const response = await this.batchRequest(params); for (let i = 0; i < requests.length; i++) { const { resolve, reject } = requests[i]; const error = response.errors?.[i]; diff --git a/packages/typescript/src/api/batch.ts b/packages/typescript/src/api/batch.ts index 2a8b56e5e7e69..d0d6843e134e3 100644 --- a/packages/typescript/src/api/batch.ts +++ b/packages/typescript/src/api/batch.ts @@ -4,6 +4,85 @@ import type { BatchRequestsResponse, } from "./proto.ts"; +interface GroupedBatchRequest { + method: APIRequest["method"]; + base?: Record; + count: number; + fields?: Record; + requests?: unknown[]; +} + +export function groupBatchRequests(requests: readonly T[]): { + groups: GroupedBatchRequest[]; + groupOrder?: number[]; +} | undefined { + const groupIndexes = new Map(); + const requestsByMethod: T[][] = []; + const groupOrder: number[] = []; + let hasBatchableGroup = false; + for (const request of requests) { + let groupIndex = groupIndexes.get(request.method); + if (groupIndex === undefined) { + groupIndex = requestsByMethod.length; + groupIndexes.set(request.method, groupIndex); + requestsByMethod.push([]); + } + requestsByMethod[groupIndex].push(request); + if (requestsByMethod[groupIndex].length === 4) hasBatchableGroup = true; + groupOrder.push(groupIndex); + } + if (!hasBatchableGroup) return undefined; + + const groups: GroupedBatchRequest[] = []; + for (const [method, groupIndex] of groupIndexes) { + const groupedRequests = requestsByMethod[groupIndex]; + const params = groupedRequests.map(request => request.params); + if (groupedRequests.length < 4) { + groups.push({ method, count: params.length, requests: params }); + continue; + } + if (params.every(param => typeof param === "object" && param !== null && !Array.isArray(param))) { + const records = params as Record[]; + const base = commonParams(records); + const commonKeys = new Set(Object.keys(base)); + const deltas = records.map(param => Object.fromEntries(Object.entries(param).filter(([key]) => !commonKeys.has(key)))); + const fields = parameterColumns(deltas); + groups.push( + fields + ? { method, base, count: records.length, fields } + : { method, base, count: records.length, requests: deltas }, + ); + } + else { + groups.push({ method, count: params.length, requests: params }); + } + } + return groups.length === 1 ? { groups } : { groups, groupOrder }; +} + +function parameterColumns(params: readonly Record[]): Record | undefined { + const keys = Object.keys(params[0]); + if ( + !params.every(param => { + const paramKeys = Object.keys(param); + return paramKeys.length === keys.length + && keys.every(key => Object.hasOwn(param, key) && param[key] !== undefined); + }) + ) return undefined; + return Object.fromEntries(keys.map(key => [key, params.map(param => param[key])])) as Record; +} + +function commonParams(params: readonly Record[]): Record { + const first = params[0]; + return Object.fromEntries( + Object.entries(first).filter(([key, value]) => { + const type = typeof value; + return (value === null || type !== "object" && type !== "undefined") + && params.every(param => Object.hasOwn(param, key) && param[key] === value); + }), + ); +} + export function createGroupedBatchRequest( method: APIRequest["method"], base: Record, diff --git a/packages/typescript/src/api/sync/client.ts b/packages/typescript/src/api/sync/client.ts index bdcce7b1392c6..a16bcc53b2f23 100644 --- a/packages/typescript/src/api/sync/client.ts +++ b/packages/typescript/src/api/sync/client.ts @@ -1,3 +1,4 @@ +import { groupBatchRequests } from "../batch.ts"; import { fsCallbackNames } from "../fs.ts"; import { type ClientOptions, @@ -102,7 +103,8 @@ export class Client { } batchRequests(requests: readonly APIRequest[]): { result: unknown; error?: string | undefined; }[] { - const response = this.batchRequest({ requests }); + const grouped = requests.length >= 4 ? groupBatchRequests(requests) : undefined; + const response = this.batchRequest(grouped ?? { requests }); return response.results.map((result, index) => { const error = response.errors?.[index]; return error === undefined ? { result } : { result, error }; diff --git a/packages/typescript/test/sync/api-generators.test.ts b/packages/typescript/test/sync/api-generators.test.ts index 52ee6e89080a3..c941f6538d9ec 100644 --- a/packages/typescript/test/sync/api-generators.test.ts +++ b/packages/typescript/test/sync/api-generators.test.ts @@ -24,6 +24,7 @@ import { import type { APIRequest, APIResponse, + BatchRequestsParams, } from "@typescript/typescript/unstable/proto"; import { all, @@ -970,6 +971,43 @@ describe("API - generator batching", () => { } }); + test("groups singular generators like an array request", context => { + const source = `export const value = true; value; value; value;`; + const api = spawnAPI({ + "/tsconfig.json": `{ "compilerOptions": { "noLib": true }, "files": ["index.ts"] }`, + "/index.ts": source, + }, { collectTiming: true }); + try { + const batchParams: unknown[] = []; + const client = api["client"]; + const batchRequest = client.batchRequest.bind(client); + context.mock.method(client, "batchRequest", (params: BatchRequestsParams) => { + batchParams.push(structuredClone(params)); + return batchRequest(params); + }); + const [snapshot] = api.batch(api.updateSnapshot.gen({ openProject: "/tsconfig.json" })); + const project = snapshot.getProjects()[0]; + const positions = Array.from(source.matchAll(/value/g), match => match.index); + batchParams.length = 0; + + api.resetTimingInfo(); + const singular = api.batch(...positions.map(position => project.checker.getSymbolAtPosition.gen("/index.ts", position))); + const singularTiming = api.getTimingInfo(); + + api.resetTimingInfo(); + const [array] = api.batch(project.checker.getSymbolAtPosition.gen("/index.ts", positions)); + const arrayTiming = api.getTimingInfo(); + + assert.deepEqual(singular.map(symbol => symbol?.id), array.map(symbol => symbol?.id)); + assert.equal(singularTiming.totals.requestCount, 1); + assert.equal(singularTiming.totals.requestCount, arrayTiming.totals.requestCount); + assert.deepEqual(batchParams[0], batchParams[1]); + } + finally { + api.close(); + } + }); + test("transparently paginates responses at the default batch size limit", () => { const largeConfigValue = "x".repeat(5_000_000); const requestCount = 64; diff --git a/tools/gen-proto/main.go b/tools/gen-proto/main.go index f7b6e1196c744..d017ec61a328f 100644 --- a/tools/gen-proto/main.go +++ b/tools/gen-proto/main.go @@ -382,7 +382,7 @@ func discoverSessionMethods(pkg *packages.Package, methodObjects map[types.Objec for _, file := range pkg.Syntax { for _, decl := range file.Decls { fn, isFuncDecl := decl.(*ast.FuncDecl) - if !isFuncDecl || fn.Name.Name != "HandleRequest" || fn.Recv == nil || fn.Body == nil { + if !isFuncDecl || fn.Recv == nil || fn.Body == nil || fn.Name.Name != "HandleRequest" && fn.Name.Name != "handleParsedRequest" { continue } ast.Inspect(fn.Body, func(node ast.Node) bool { diff --git a/tsc/internal/api/session.go b/tsc/internal/api/session.go index ce93fd115e728..1e8fbd00c8038 100644 --- a/tsc/internal/api/session.go +++ b/tsc/internal/api/session.go @@ -499,24 +499,8 @@ func validateBatchColumn(name string, length int, count int) error { return nil } -type parsedRequestContext struct { - method string - params any -} - -type parsedRequestContextKey struct{} - type batchGroupState struct { - checkerCache batchCheckerCache - parsedRequest parsedRequestContext - ctx context.Context -} - -func newBatchGroupState(ctx context.Context, method Method) *batchGroupState { - state := &batchGroupState{parsedRequest: parsedRequestContext{method: string(method)}} - ctx = context.WithValue(ctx, batchCheckerCacheContextKey{}, &state.checkerCache) - state.ctx = context.WithValue(ctx, parsedRequestContextKey{}, &state.parsedRequest) - return state + checkerCache batchCheckerCache } func (c *batchCheckerCache) release() { @@ -879,18 +863,15 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json. return "pong", nil } - var parsed any - if request, ok := ctx.Value(parsedRequestContextKey{}).(*parsedRequestContext); ok && request.method == method { - parsed = request.params - } else { - var err error - parsed, err = unmarshalPayload(method, params) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrInvalidRequest, err) - } + parsed, err := unmarshalPayload(method, params) + if err != nil { + return nil, fmt.Errorf("%w: %w", ErrInvalidRequest, err) } + return s.handleParsedRequest(ctx, Method(method), parsed) +} - switch method { +func (s *Session) handleParsedRequest(ctx context.Context, method Method, parsed any) (any, error) { + switch string(method) { case string(MethodBatchRequests): return s.handleBatchRequests(ctx, parsed.(*BatchRequestsParams)) case string(MethodRelease): @@ -1208,7 +1189,7 @@ func (s *Session) handleBatchRequestGroups(ctx context.Context, groups []BatchRe return nil, errors.New("api: invalid request: grouped batch requires groupOrder") } states := core.Map(groups, func(group BatchRequestGroup) *batchGroupState { - return newBatchGroupState(ctx, group.Method) + return &batchGroupState{} }) for _, state := range states { defer state.checkerCache.release() @@ -1242,6 +1223,10 @@ func (s *Session) handleBatchRequestGroups(ctx context.Context, groups []BatchRe responseCount = groups[0].Count } responses := make([]BatchResponse, responseCount) + var singleGroupCtx context.Context + if implicitSingleGroupOrder { + singleGroupCtx = context.WithValue(ctx, batchCheckerCacheContextKey{}, &states[0].checkerCache) + } cursors := make([]int, len(groups)) previousGroupIndex := -1 @@ -1257,15 +1242,17 @@ func (s *Session) handleBatchRequestGroups(ctx context.Context, groups []BatchRe states[previousGroupIndex].checkerCache.release() } previousGroupIndex = groupIndex + groupCtx := singleGroupCtx + if groupCtx == nil { + groupCtx = context.WithValue(ctx, batchCheckerCacheContextKey{}, &states[groupIndex].checkerCache) + } group := groups[groupIndex] requestIndex := cursors[groupIndex] if requestIndex >= group.Count { return nil, fmt.Errorf("%w: too many requests for group %d", ErrInvalidRequest, groupIndex) } if decoder := decoders[groupIndex]; decoder != nil { - state := states[groupIndex] - state.parsedRequest.params = decoder.request(requestIndex) - responses[i] = s.handleParsedBatchRequest(state.ctx, group.Method) + responses[i] = s.handleParsedBatchRequest(groupCtx, group.Method, decoder.request(requestIndex)) } else { if requestIndex >= len(group.Requests) { return nil, fmt.Errorf("%w: missing request parameters for group %d", ErrInvalidRequest, groupIndex) @@ -1274,7 +1261,7 @@ func (s *Session) handleBatchRequestGroups(ctx context.Context, groups []BatchRe if err != nil { return nil, fmt.Errorf("%w: invalid grouped request %d for %q: %w", ErrInvalidRequest, requestIndex, group.Method, err) } - responses[i] = s.handleBatchRequest(states[groupIndex].ctx, BatchRequest{Method: group.Method, Params: params}) + responses[i] = s.handleBatchRequest(groupCtx, BatchRequest{Method: group.Method, Params: params}) } cursors[groupIndex]++ } @@ -1484,7 +1471,7 @@ func (s *Session) handleBatchRequest(ctx context.Context, request BatchRequest) return response } -func (s *Session) handleParsedBatchRequest(ctx context.Context, method Method) (response BatchResponse) { +func (s *Session) handleParsedBatchRequest(ctx context.Context, method Method, params any) (response BatchResponse) { defer func() { if recovered := recover(); recovered != nil { response.Result = nil @@ -1492,7 +1479,7 @@ func (s *Session) handleParsedBatchRequest(ctx context.Context, method Method) ( } }() var err error - response.Result, err = s.HandleRequest(ctx, string(method), nil) + response.Result, err = s.handleParsedRequest(ctx, method, params) if err != nil { response.Error = err.Error() } diff --git a/tsc/internal/api/session_batch_test.go b/tsc/internal/api/session_batch_test.go index d53f383b2a436..2a6bbf1434b07 100644 --- a/tsc/internal/api/session_batch_test.go +++ b/tsc/internal/api/session_batch_test.go @@ -63,6 +63,19 @@ func TestHandleSingleGroupedBatchRequestsInfersOrder(t *testing.T) { assert.Equal(t, string(encoded), `{"results":[{"group":"a","value":1},{"group":"a","value":2},{"group":"a","value":3}]}`) } +func TestHandleGroupedBatchRequestsDecodesRowParams(t *testing.T) { + t.Parallel() + + response, err := (&Session{}).HandleRequest(context.Background(), string(MethodBatchRequests), json.Value(`{ + "groups":[ + {"method":"transpileModule","count":1,"requests":[{"input":"const value: number = 1;","options":{}}]} + ] + }`)) + assert.NilError(t, err) + assert.Equal(t, len(response.(*BatchRequestsResponse).Results), 1) + assert.Equal(t, len(response.(*BatchRequestsResponse).Errors), 0) +} + func TestHandleGroupedBatchRequestsReacquiresCheckerAfterInterleaving(t *testing.T) { t.Parallel() From 0122aed26a5fbfb4fb517871a46ce75b7a65096a Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 15 Sep 2026 11:47:25 -0700 Subject: [PATCH 4/5] Generate post-merge APIs --- tsc/internal/api/batch_decoder_generated.go | 94 +++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/tsc/internal/api/batch_decoder_generated.go b/tsc/internal/api/batch_decoder_generated.go index ed379cca6adfc..b0cb6da1fa03d 100644 --- a/tsc/internal/api/batch_decoder_generated.go +++ b/tsc/internal/api/batch_decoder_generated.go @@ -348,6 +348,46 @@ func newBatchDecoderGetCompletionsAtPositionParams(base json.Value, fields json. }) } +type batchColumnsGetContextualTypeForArgumentParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Location []NodeHandle `json:"location,omitempty"` + Index []int32 `json:"index,omitempty"` +} + +func newBatchDecoderGetContextualTypeForArgumentParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetContextualTypeForArgumentParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } + if err := validateBatchColumn("index", len(columns.Index), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetContextualTypeForArgumentParams](base, func(params *GetContextualTypeForArgumentParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Location != nil { + params.Location = columns.Location[index] + } + if columns.Index != nil { + params.Index = columns.Index[index] + } + }) +} + type batchColumnsGetContextualTypeParams struct { Snapshot []SnapshotID `json:"snapshot,omitempty"` Project []ProjectID `json:"project,omitempty"` @@ -480,6 +520,46 @@ func newBatchDecoderGetImportAdderEditsParams(base json.Value, fields json.Value }) } +type batchColumnsGetIndexInfoOfTypeParams struct { + Snapshot []SnapshotID `json:"snapshot,omitempty"` + Project []ProjectID `json:"project,omitempty"` + Type []TypeID `json:"type,omitempty"` + Kind []int32 `json:"kind,omitempty"` +} + +func newBatchDecoderGetIndexInfoOfTypeParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { + var columns batchColumnsGetIndexInfoOfTypeParams + if err := json.Unmarshal(fields, &columns); err != nil { + return nil, err + } + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } + if err := validateBatchColumn("type", len(columns.Type), count); err != nil { + return nil, err + } + if err := validateBatchColumn("kind", len(columns.Kind), count); err != nil { + return nil, err + } + return newTypedBatchRequestDecoder[GetIndexInfoOfTypeParams](base, func(params *GetIndexInfoOfTypeParams, index int) { + if columns.Snapshot != nil { + params.Snapshot = columns.Snapshot[index] + } + if columns.Project != nil { + params.Project = columns.Project[index] + } + if columns.Type != nil { + params.Type = columns.Type[index] + } + if columns.Kind != nil { + params.Kind = columns.Kind[index] + } + }) +} + type batchColumnsGetIntrinsicTypeParams struct { Snapshot []SnapshotID `json:"snapshot,omitempty"` Project []ProjectID `json:"project,omitempty"` @@ -2232,6 +2312,8 @@ func newGeneratedBatchRequestDecoder(method Method, base json.Value, fields json return newBatchDecoderGetTypePropertyParams(base, fields, count) case MethodGetLocalTypeParametersOfType: return newBatchDecoderGetTypePropertyParams(base, fields, count) + case MethodGetThisTypeOfType: + return newBatchDecoderGetTypePropertyParams(base, fields, count) case MethodGetAliasTypeArgumentsOfType: return newBatchDecoderGetTypePropertyParams(base, fields, count) case MethodGetAliasSymbolOfType: @@ -2258,6 +2340,10 @@ func newGeneratedBatchRequestDecoder(method Method, base json.Value, fields json return newBatchDecoderGetSignaturePropertyParams(base, fields, count) case MethodGetContextualType: return newBatchDecoderGetContextualTypeParams(base, fields, count) + case MethodGetContextualTypeForArgument: + return newBatchDecoderGetContextualTypeForArgumentParams(base, fields, count) + case MethodGetAwaitedType: + return newBatchDecoderCheckerTypeParams(base, fields, count) case MethodGetBaseTypeOfLiteralType: return newBatchDecoderGetBaseTypeOfLiteralTypeParams(base, fields, count) case MethodGetNonNullableType: @@ -2304,6 +2390,12 @@ func newGeneratedBatchRequestDecoder(method Method, base json.Value, fields json return newBatchDecoderGetTypePropertyParams(base, fields, count) case MethodGetPropertyOfType: return newBatchDecoderGetPropertyOfTypeParams(base, fields, count) + case MethodGetTypeOfPropertyOfType: + return newBatchDecoderGetPropertyOfTypeParams(base, fields, count) + case MethodGetIndexInfoOfType: + return newBatchDecoderGetIndexInfoOfTypeParams(base, fields, count) + case MethodGetIndexTypeOfTypeByKind: + return newBatchDecoderGetIndexInfoOfTypeParams(base, fields, count) case MethodGetIndexInfosOfType: return newBatchDecoderCheckerTypeParams(base, fields, count) case MethodGetConstraintOfTypeParameter: @@ -2332,6 +2424,8 @@ func newGeneratedBatchRequestDecoder(method Method, base json.Value, fields json return newBatchDecoderCheckerSymbolParams(base, fields, count) case MethodGetTargetSymbol: return newBatchDecoderCheckerSymbolParams(base, fields, count) + case MethodGetExportSymbolOfSymbolForChecker: + return newBatchDecoderCheckerSymbolParams(base, fields, count) case MethodGetFullyQualifiedName: return newBatchDecoderCheckerSymbolParams(base, fields, count) case MethodGetExportsOfModule: From 1a72980a65520f8f3d2b0616da5969b806c356cf Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 24 Sep 2026 10:09:04 -0700 Subject: [PATCH 5/5] PR feedback --- tools/gen-proto/main.go | 127 +- tools/gen-proto/main_test.go | 103 ++ tsc/internal/api/batch_decoder_generated.go | 1432 +++++++++++++------ tsc/internal/api/session.go | 21 +- tsc/internal/api/session_batch_test.go | 142 ++ 5 files changed, 1345 insertions(+), 480 deletions(-) diff --git a/tools/gen-proto/main.go b/tools/gen-proto/main.go index 43b96cf955100..2c37ce41c495e 100644 --- a/tools/gen-proto/main.go +++ b/tools/gen-proto/main.go @@ -207,22 +207,7 @@ func generateBatchDecoders(inputPath string, outputPath string) error { } decoder := decoderTypes[named] if decoder == nil { - decoder = &decoderType{name: named.Obj().Name(), named: named} - for i := range structType.NumFields() { - field := structType.Field(i) - if !field.Exported() || field.Embedded() { - continue - } - jsonName, _, _ := strings.Cut(reflect.StructTag(structType.Tag(i)).Get("json"), ",") - if jsonName == "" || jsonName == "-" { - continue - } - decoder.fields = append(decoder.fields, batchField{ - goName: field.Name(), - jsonName: jsonName, - typeName: types.TypeString(field.Type(), qualifier), - }) - } + decoder = &decoderType{name: named.Obj().Name(), named: named, fields: batchFields(structType, qualifier)} decoderTypes[named] = decoder } methodTypes[method] = decoder @@ -241,11 +226,29 @@ func generateBatchDecoders(inputPath string, outputPath string) error { fmt.Fprintf(&body, "\tvar columns batchColumns%s\n", decoder.name) body.WriteString("\tif err := json.Unmarshal(fields, &columns); err != nil {\n\t\treturn nil, err\n\t}\n") for _, field := range decoder.fields { - fmt.Fprintf(&body, "\tif err := validateBatchColumn(\"%s\", len(columns.%s), count); err != nil { return nil, err }\n", field.jsonName, field.goName) + fmt.Fprintf(&body, "\tif columns.%s != nil {\n\t\tif err := validateBatchColumn(\"%s\", len(columns.%s), count); err != nil { return nil, err }\n\t}\n", field.goName, field.jsonName, field.goName) } fmt.Fprintf(&body, "\treturn newTypedBatchRequestDecoder[%s](base, func(params *%s, index int) {\n", decoder.name, decoder.name) + initialized := make(map[string]bool) + for _, field := range decoder.fields { + for _, pointer := range field.pointers { + if initialized[pointer.selector] { + continue + } + initialized[pointer.selector] = true + var conditions []string + for _, other := range decoder.fields { + if strings.HasPrefix(other.selector, pointer.selector+".") { + conditions = append(conditions, "columns."+other.goName+" != nil") + } + } + fmt.Fprintf(&body, "\t\tif %s {\n", strings.Join(conditions, " || ")) + fmt.Fprintf(&body, "\t\t\tif params.%s == nil { params.%s = new(%s) } else { value := *params.%s; params.%s = &value }\n", pointer.selector, pointer.selector, pointer.typeName, pointer.selector, pointer.selector) + body.WriteString("\t\t}\n") + } + } for _, field := range decoder.fields { - fmt.Fprintf(&body, "\t\tif columns.%s != nil { params.%s = columns.%s[index] }\n", field.goName, field.goName, field.goName) + fmt.Fprintf(&body, "\t\tif columns.%s != nil { params.%s = columns.%s[index] }\n", field.goName, field.selector, field.goName) } body.WriteString("\t})\n}\n\n") } @@ -280,6 +283,94 @@ type batchField struct { goName string jsonName string typeName string + typ types.Type + selector string + pointers []batchPointer + depth int + tagged bool +} + +type batchPointer struct { + selector string + typeName string + typ types.Type +} + +func batchFields(structType *types.Struct, qualifier types.Qualifier) []batchField { + var candidates []batchField + visiting := make(map[*types.Struct]bool) + var collect func(*types.Struct, []string, []batchPointer) + collect = func(current *types.Struct, path []string, pointers []batchPointer) { + if visiting[current] { + return + } + visiting[current] = true + defer delete(visiting, current) + for index := range current.NumFields() { + field := current.Field(index) + if !field.Exported() { + continue + } + tag := reflect.StructTag(current.Tag(index)).Get("json") + if tag == "-" { + continue + } + jsonName, options, _ := strings.Cut(tag, ",") + fieldPath := append(slices.Clone(path), field.Name()) + selector := strings.Join(fieldPath, ".") + if jsonName == "" && (field.Embedded() || slices.Contains(strings.Split(options, ","), "embed")) { + embedded := types.Unalias(field.Type()) + fieldPointers := pointers + if pointer, ok := embedded.(*types.Pointer); ok { + embedded = types.Unalias(pointer.Elem()) + fieldPointers = append(slices.Clone(pointers), batchPointer{selector: selector, typ: embedded}) + } + if embeddedStruct, ok := embedded.Underlying().(*types.Struct); ok { + collect(embeddedStruct, fieldPath, fieldPointers) + continue + } + } + tagged := jsonName != "" + if jsonName == "" { + jsonName = field.Name() + } + candidates = append(candidates, batchField{ + goName: field.Name(), jsonName: jsonName, + typ: field.Type(), + selector: selector, pointers: pointers, depth: len(path), tagged: tagged, + }) + } + } + collect(structType, nil, nil) + + byName := make(map[string][]batchField) + for _, field := range candidates { + previous := byName[field.jsonName] + if len(previous) == 0 || field.depth < previous[0].depth || field.depth == previous[0].depth && field.tagged && !previous[0].tagged { + byName[field.jsonName] = []batchField{field} + } else if field.depth == previous[0].depth && field.tagged == previous[0].tagged { + byName[field.jsonName] = append(previous, field) + } + } + var fields []batchField + usedNames := make(map[string]bool) + for _, field := range candidates { + matches := byName[field.jsonName] + if len(matches) != 1 || matches[0].selector != field.selector { + continue + } + name := field.goName + for suffix := 2; usedNames[field.goName]; suffix++ { + field.goName = name + strconv.Itoa(suffix) + } + usedNames[field.goName] = true + field.typeName = types.TypeString(field.typ, qualifier) + for index := range field.pointers { + field.pointers[index].typeName = types.TypeString(field.pointers[index].typ, qualifier) + } + fields = append(fields, field) + } + return fields } func loadAPIPackage(absInput string) (*packages.Package, *ast.File, error) { diff --git a/tools/gen-proto/main_test.go b/tools/gen-proto/main_test.go index c93a59400868c..b8fe1dcfa9138 100644 --- a/tools/gen-proto/main_test.go +++ b/tools/gen-proto/main_test.go @@ -1,12 +1,115 @@ package main import ( + "go/ast" + "go/parser" + "go/token" + "go/types" "os" "path/filepath" + "reflect" "strings" "testing" ) +func TestBatchFields(t *testing.T) { + t.Parallel() + + for _, test := range []struct { + name string + source string + fields map[string]string + pointers map[string]string + }{ + { + name: "nested promotion and default names", + source: "type Inner struct { Value string `json:\"value\"`; Default int; Ignored int `json:\"-\"`; hidden int }; type Outer struct { Inner }; type Params struct { Outer; Own bool `json:\"own\"` }", + fields: map[string]string{"value": "Outer.Inner.Value", "Default": "Outer.Inner.Default", "own": "Own"}, + }, + { + name: "pointer promotion", + source: "type Inner struct { Value string `json:\"value\"` }; type Outer struct { *Inner }; type Params struct { *Outer }", + fields: map[string]string{"value": "Outer.Inner.Value"}, + pointers: map[string]string{"Outer": "Outer", "Outer.Inner": "Inner"}, + }, + { + name: "named embedding is not promoted", + source: "type Inner struct { Value string `json:\"value\"` }; type Params struct { *Inner `json:\"inner\"` }", + fields: map[string]string{"inner": "Inner"}, + }, + { + name: "ignored embedding", + source: "type Inner struct { Value string }; type Params struct { Inner `json:\"-\"`; Own bool }", + fields: map[string]string{"Own": "Own"}, + }, + { + name: "explicit JSON embedding", + source: "type Inner struct { Value string }; type Params struct { Fields Inner `json:\",embed\"` }", + fields: map[string]string{"Value": "Fields.Value"}, + }, + { + name: "shallower field wins", + source: "type Inner struct { Value string `json:\"Value\"` }; type Params struct { Inner; Value int }", + fields: map[string]string{"Value": "Value"}, + }, + { + name: "tagged field wins at equal depth", + source: "type Left struct { Value string }; type Right struct { Renamed int `json:\"Value\"` }; type Params struct { Left; Right }", + fields: map[string]string{"Value": "Right.Renamed"}, + }, + { + name: "ambiguous names are not promoted", + source: "type Left struct { Value string }; type Right struct { Value int }; type Params struct { Left; Right; Own bool }", + fields: map[string]string{"Own": "Own"}, + }, + { + name: "duplicate Go names with distinct JSON names", + source: "type Left struct { Value string `json:\"left\"` }; type Right struct { Value int `json:\"right\"` }; type Params struct { Left; Right }", + fields: map[string]string{"left": "Left.Value", "right": "Right.Value"}, + }, + { + name: "recursive embedding", + source: "type Params struct { *Params; Value string }", + fields: map[string]string{"Value": "Value"}, + }, + } { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + fileSet := token.NewFileSet() + file, err := parser.ParseFile(fileSet, "params.go", "package api\n"+test.source, 0) + if err != nil { + t.Fatal(err) + } + var config types.Config + pkg, err := config.Check("api", fileSet, []*ast.File{file}, nil) + if err != nil { + t.Fatal(err) + } + fields := batchFields(pkg.Scope().Lookup("Params").Type().Underlying().(*types.Struct), func(*types.Package) string { return "" }) + gotFields := make(map[string]string) + gotPointers := make(map[string]string) + columnNames := make(map[string]bool) + for _, field := range fields { + gotFields[field.jsonName] = field.selector + if columnNames[field.goName] { + t.Errorf("duplicate generated column name %s", field.goName) + } + columnNames[field.goName] = true + for _, pointer := range field.pointers { + gotPointers[pointer.selector] = pointer.typeName + } + } + if !reflect.DeepEqual(gotFields, test.fields) { + t.Errorf("fields = %v, want %v", gotFields, test.fields) + } + if len(gotPointers) != len(test.pointers) || len(gotPointers) > 0 && !reflect.DeepEqual(gotPointers, test.pointers) { + t.Errorf("pointers = %v, want %v", gotPointers, test.pointers) + } + }) + } +} + func TestGenerate(t *testing.T) { t.Parallel() diff --git a/tsc/internal/api/batch_decoder_generated.go b/tsc/internal/api/batch_decoder_generated.go index f3de847337192..899ec492ed531 100644 --- a/tsc/internal/api/batch_decoder_generated.go +++ b/tsc/internal/api/batch_decoder_generated.go @@ -21,14 +21,20 @@ func newBatchDecoderCheckerNodeParams(base json.Value, fields json.Value, count if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("location", len(columns.Location), count); err != nil { - return nil, err + if columns.Location != nil { + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[CheckerNodeParams](base, func(params *CheckerNodeParams, index int) { if columns.Snapshot != nil { @@ -54,14 +60,20 @@ func newBatchDecoderCheckerSignatureParams(base json.Value, fields json.Value, c if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("signature", len(columns.Signature), count); err != nil { - return nil, err + if columns.Signature != nil { + if err := validateBatchColumn("signature", len(columns.Signature), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[CheckerSignatureParams](base, func(params *CheckerSignatureParams, index int) { if columns.Snapshot != nil { @@ -87,14 +99,20 @@ func newBatchDecoderCheckerSymbolParams(base json.Value, fields json.Value, coun if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("symbol", len(columns.Symbol), count); err != nil { - return nil, err + if columns.Symbol != nil { + if err := validateBatchColumn("symbol", len(columns.Symbol), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[CheckerSymbolParams](base, func(params *CheckerSymbolParams, index int) { if columns.Snapshot != nil { @@ -120,14 +138,20 @@ func newBatchDecoderCheckerTypeParams(base json.Value, fields json.Value, count if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("type", len(columns.Type), count); err != nil { - return nil, err + if columns.Type != nil { + if err := validateBatchColumn("type", len(columns.Type), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[CheckerTypeParams](base, func(params *CheckerTypeParams, index int) { if columns.Snapshot != nil { @@ -153,14 +177,20 @@ func newBatchDecoderCreateModuleResolverParams(base json.Value, fields json.Valu if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("compilerOptions", len(columns.CompilerOptions), count); err != nil { - return nil, err + if columns.CompilerOptions != nil { + if err := validateBatchColumn("compilerOptions", len(columns.CompilerOptions), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("moduleResolutions", len(columns.ModuleResolutions), count); err != nil { - return nil, err + if columns.ModuleResolutions != nil { + if err := validateBatchColumn("moduleResolutions", len(columns.ModuleResolutions), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("resolveModuleNameCallback", len(columns.ResolveModuleNameCallback), count); err != nil { - return nil, err + if columns.ResolveModuleNameCallback != nil { + if err := validateBatchColumn("resolveModuleNameCallback", len(columns.ResolveModuleNameCallback), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[CreateModuleResolverParams](base, func(params *CreateModuleResolverParams, index int) { if columns.CompilerOptions != nil { @@ -176,8 +206,16 @@ func newBatchDecoderCreateModuleResolverParams(base json.Value, fields json.Valu } type batchColumnsCreateSnapshotParams struct { - FileNotifications []*FileNotifications `json:"fileNotifications,omitempty"` - FileSystem []*requestfilesystem.RequestFileSystem `json:"fileSystem,omitempty"` + OpenProjects [][]DocumentIdentifier `json:"openProjects,omitempty"` + CloseProjects [][]DocumentIdentifier `json:"closeProjects,omitempty"` + OpenFiles [][]DocumentIdentifier `json:"openFiles,omitempty"` + CloseFiles [][]DocumentIdentifier `json:"closeFiles,omitempty"` + CreatePrograms [][]*CreateSnapshotProgramParams `json:"createPrograms,omitempty"` + ReconfigurePrograms [][]*ReconfigureSnapshotProgramParams `json:"reconfigurePrograms,omitempty"` + RemovePrograms [][]project.SyntheticProjectID `json:"removePrograms,omitempty"` + EnsurePrograms []*EnsurePrograms `json:"ensurePrograms,omitempty"` + FileNotifications []*FileNotifications `json:"fileNotifications,omitempty"` + FileSystem []*requestfilesystem.RequestFileSystem `json:"fileSystem,omitempty"` } func newBatchDecoderCreateSnapshotParams(base json.Value, fields json.Value, count int) (batchRequestDecoder, error) { @@ -185,13 +223,81 @@ func newBatchDecoderCreateSnapshotParams(base json.Value, fields json.Value, cou if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("fileNotifications", len(columns.FileNotifications), count); err != nil { - return nil, err + if columns.OpenProjects != nil { + if err := validateBatchColumn("openProjects", len(columns.OpenProjects), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("fileSystem", len(columns.FileSystem), count); err != nil { - return nil, err + if columns.CloseProjects != nil { + if err := validateBatchColumn("closeProjects", len(columns.CloseProjects), count); err != nil { + return nil, err + } + } + if columns.OpenFiles != nil { + if err := validateBatchColumn("openFiles", len(columns.OpenFiles), count); err != nil { + return nil, err + } + } + if columns.CloseFiles != nil { + if err := validateBatchColumn("closeFiles", len(columns.CloseFiles), count); err != nil { + return nil, err + } + } + if columns.CreatePrograms != nil { + if err := validateBatchColumn("createPrograms", len(columns.CreatePrograms), count); err != nil { + return nil, err + } + } + if columns.ReconfigurePrograms != nil { + if err := validateBatchColumn("reconfigurePrograms", len(columns.ReconfigurePrograms), count); err != nil { + return nil, err + } + } + if columns.RemovePrograms != nil { + if err := validateBatchColumn("removePrograms", len(columns.RemovePrograms), count); err != nil { + return nil, err + } + } + if columns.EnsurePrograms != nil { + if err := validateBatchColumn("ensurePrograms", len(columns.EnsurePrograms), count); err != nil { + return nil, err + } + } + if columns.FileNotifications != nil { + if err := validateBatchColumn("fileNotifications", len(columns.FileNotifications), count); err != nil { + return nil, err + } + } + if columns.FileSystem != nil { + if err := validateBatchColumn("fileSystem", len(columns.FileSystem), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[CreateSnapshotParams](base, func(params *CreateSnapshotParams, index int) { + if columns.OpenProjects != nil { + params.SnapshotRequestChangesParams.OpenProjects = columns.OpenProjects[index] + } + if columns.CloseProjects != nil { + params.SnapshotRequestChangesParams.CloseProjects = columns.CloseProjects[index] + } + if columns.OpenFiles != nil { + params.SnapshotRequestChangesParams.OpenFiles = columns.OpenFiles[index] + } + if columns.CloseFiles != nil { + params.SnapshotRequestChangesParams.CloseFiles = columns.CloseFiles[index] + } + if columns.CreatePrograms != nil { + params.SnapshotRequestChangesParams.CreatePrograms = columns.CreatePrograms[index] + } + if columns.ReconfigurePrograms != nil { + params.SnapshotRequestChangesParams.ReconfigurePrograms = columns.ReconfigurePrograms[index] + } + if columns.RemovePrograms != nil { + params.SnapshotRequestChangesParams.RemovePrograms = columns.RemovePrograms[index] + } + if columns.EnsurePrograms != nil { + params.SnapshotRequestChangesParams.EnsurePrograms = columns.EnsurePrograms[index] + } if columns.FileNotifications != nil { params.FileNotifications = columns.FileNotifications[index] } @@ -211,11 +317,15 @@ func newBatchDecoderCreateSourceFileFromFileParams(base json.Value, fields json. if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("fileName", len(columns.FileName), count); err != nil { - return nil, err + if columns.FileName != nil { + if err := validateBatchColumn("fileName", len(columns.FileName), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("options", len(columns.Options), count); err != nil { - return nil, err + if columns.Options != nil { + if err := validateBatchColumn("options", len(columns.Options), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[CreateSourceFileFromFileParams](base, func(params *CreateSourceFileFromFileParams, index int) { if columns.FileName != nil { @@ -238,14 +348,20 @@ func newBatchDecoderCreateSourceFileParams(base json.Value, fields json.Value, c if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("fileName", len(columns.FileName), count); err != nil { - return nil, err + if columns.FileName != nil { + if err := validateBatchColumn("fileName", len(columns.FileName), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("sourceText", len(columns.SourceText), count); err != nil { - return nil, err + if columns.SourceText != nil { + if err := validateBatchColumn("sourceText", len(columns.SourceText), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("options", len(columns.Options), count); err != nil { - return nil, err + if columns.Options != nil { + if err := validateBatchColumn("options", len(columns.Options), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[CreateSourceFileParams](base, func(params *CreateSourceFileParams, index int) { if columns.FileName != nil { @@ -271,14 +387,20 @@ func newBatchDecoderEmitParams(base json.Value, fields json.Value, count int) (b if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("emitOnly", len(columns.EmitOnly), count); err != nil { - return nil, err + if columns.EmitOnly != nil { + if err := validateBatchColumn("emitOnly", len(columns.EmitOnly), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[EmitParams](base, func(params *EmitParams, index int) { if columns.Snapshot != nil { @@ -306,20 +428,30 @@ func newBatchDecoderFormatNodeForInsertionParams(base json.Value, fields json.Va if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("file", len(columns.File), count); err != nil { - return nil, err + if columns.File != nil { + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("position", len(columns.Position), count); err != nil { - return nil, err + if columns.Position != nil { + if err := validateBatchColumn("position", len(columns.Position), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("data", len(columns.Data), count); err != nil { - return nil, err + if columns.Data != nil { + if err := validateBatchColumn("data", len(columns.Data), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[FormatNodeForInsertionParams](base, func(params *FormatNodeForInsertionParams, index int) { if columns.Snapshot != nil { @@ -351,14 +483,20 @@ func newBatchDecoderGetBaseTypeOfLiteralTypeParams(base json.Value, fields json. if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("type", len(columns.Type), count); err != nil { - return nil, err + if columns.Type != nil { + if err := validateBatchColumn("type", len(columns.Type), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetBaseTypeOfLiteralTypeParams](base, func(params *GetBaseTypeOfLiteralTypeParams, index int) { if columns.Snapshot != nil { @@ -387,23 +525,35 @@ func newBatchDecoderGetCompletionsAtPositionParams(base json.Value, fields json. if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("file", len(columns.File), count); err != nil { - return nil, err + if columns.File != nil { + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("position", len(columns.Position), count); err != nil { - return nil, err + if columns.Position != nil { + if err := validateBatchColumn("position", len(columns.Position), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("triggerCharacter", len(columns.TriggerCharacter), count); err != nil { - return nil, err + if columns.TriggerCharacter != nil { + if err := validateBatchColumn("triggerCharacter", len(columns.TriggerCharacter), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("includeSymbol", len(columns.IncludeSymbol), count); err != nil { - return nil, err + if columns.IncludeSymbol != nil { + if err := validateBatchColumn("includeSymbol", len(columns.IncludeSymbol), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetCompletionsAtPositionParams](base, func(params *GetCompletionsAtPositionParams, index int) { if columns.Snapshot != nil { @@ -439,17 +589,25 @@ func newBatchDecoderGetContextualTypeForArgumentParams(base json.Value, fields j if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("location", len(columns.Location), count); err != nil { - return nil, err + if columns.Location != nil { + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("index", len(columns.Index), count); err != nil { - return nil, err + if columns.Index != nil { + if err := validateBatchColumn("index", len(columns.Index), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetContextualTypeForArgumentParams](base, func(params *GetContextualTypeForArgumentParams, index int) { if columns.Snapshot != nil { @@ -478,14 +636,20 @@ func newBatchDecoderGetContextualTypeParams(base json.Value, fields json.Value, if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("location", len(columns.Location), count); err != nil { - return nil, err + if columns.Location != nil { + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetContextualTypeParams](base, func(params *GetContextualTypeParams, index int) { if columns.Snapshot != nil { @@ -510,11 +674,15 @@ func newBatchDecoderGetCurrentLanguageServerSnapshotParams(base json.Value, fiel if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("baseSnapshot", len(columns.BaseSnapshot), count); err != nil { - return nil, err + if columns.BaseSnapshot != nil { + if err := validateBatchColumn("baseSnapshot", len(columns.BaseSnapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("changes", len(columns.Changes), count); err != nil { - return nil, err + if columns.Changes != nil { + if err := validateBatchColumn("changes", len(columns.Changes), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetCurrentLanguageServerSnapshotParams](base, func(params *GetCurrentLanguageServerSnapshotParams, index int) { if columns.BaseSnapshot != nil { @@ -536,11 +704,15 @@ func newBatchDecoderGetDefaultProjectForFileParams(base json.Value, fields json. if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("file", len(columns.File), count); err != nil { - return nil, err + if columns.File != nil { + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetDefaultProjectForFileParams](base, func(params *GetDefaultProjectForFileParams, index int) { if columns.Snapshot != nil { @@ -563,14 +735,20 @@ func newBatchDecoderGetDiagnosticsParams(base json.Value, fields json.Value, cou if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("files", len(columns.Files), count); err != nil { - return nil, err + if columns.Files != nil { + if err := validateBatchColumn("files", len(columns.Files), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetDiagnosticsParams](base, func(params *GetDiagnosticsParams, index int) { if columns.Snapshot != nil { @@ -597,17 +775,25 @@ func newBatchDecoderGetImportAdderEditsParams(base json.Value, fields json.Value if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("file", len(columns.File), count); err != nil { - return nil, err + if columns.File != nil { + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("actions", len(columns.Actions), count); err != nil { - return nil, err + if columns.Actions != nil { + if err := validateBatchColumn("actions", len(columns.Actions), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetImportAdderEditsParams](base, func(params *GetImportAdderEditsParams, index int) { if columns.Snapshot != nil { @@ -637,17 +823,25 @@ func newBatchDecoderGetIndexInfoOfTypeParams(base json.Value, fields json.Value, if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("type", len(columns.Type), count); err != nil { - return nil, err + if columns.Type != nil { + if err := validateBatchColumn("type", len(columns.Type), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("kind", len(columns.Kind), count); err != nil { - return nil, err + if columns.Kind != nil { + if err := validateBatchColumn("kind", len(columns.Kind), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetIndexInfoOfTypeParams](base, func(params *GetIndexInfoOfTypeParams, index int) { if columns.Snapshot != nil { @@ -675,11 +869,15 @@ func newBatchDecoderGetIntrinsicTypeParams(base json.Value, fields json.Value, c if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetIntrinsicTypeParams](base, func(params *GetIntrinsicTypeParams, index int) { if columns.Snapshot != nil { @@ -703,17 +901,25 @@ func newBatchDecoderGetMemberInModuleExportsParams(base json.Value, fields json. if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("symbol", len(columns.Symbol), count); err != nil { - return nil, err + if columns.Symbol != nil { + if err := validateBatchColumn("symbol", len(columns.Symbol), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("name", len(columns.Name), count); err != nil { - return nil, err + if columns.Name != nil { + if err := validateBatchColumn("name", len(columns.Name), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetMemberInModuleExportsParams](base, func(params *GetMemberInModuleExportsParams, index int) { if columns.Snapshot != nil { @@ -743,17 +949,25 @@ func newBatchDecoderGetModeForResolutionAtIndexParams(base json.Value, fields js if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("file", len(columns.File), count); err != nil { - return nil, err + if columns.File != nil { + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("index", len(columns.Index), count); err != nil { - return nil, err + if columns.Index != nil { + if err := validateBatchColumn("index", len(columns.Index), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetModeForResolutionAtIndexParams](base, func(params *GetModeForResolutionAtIndexParams, index int) { if columns.Snapshot != nil { @@ -783,17 +997,25 @@ func newBatchDecoderGetModeForUsageLocationParams(base json.Value, fields json.V if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("file", len(columns.File), count); err != nil { - return nil, err + if columns.File != nil { + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("usage", len(columns.Usage), count); err != nil { - return nil, err + if columns.Usage != nil { + if err := validateBatchColumn("usage", len(columns.Usage), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetModeForUsageLocationParams](base, func(params *GetModeForUsageLocationParams, index int) { if columns.Snapshot != nil { @@ -823,17 +1045,25 @@ func newBatchDecoderGetParameterTypeParams(base json.Value, fields json.Value, c if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("signature", len(columns.Signature), count); err != nil { - return nil, err + if columns.Signature != nil { + if err := validateBatchColumn("signature", len(columns.Signature), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("index", len(columns.Index), count); err != nil { - return nil, err + if columns.Index != nil { + if err := validateBatchColumn("index", len(columns.Index), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetParameterTypeParams](base, func(params *GetParameterTypeParams, index int) { if columns.Snapshot != nil { @@ -861,11 +1091,15 @@ func newBatchDecoderGetProjectDiagnosticsParams(base json.Value, fields json.Val if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetProjectDiagnosticsParams](base, func(params *GetProjectDiagnosticsParams, index int) { if columns.Snapshot != nil { @@ -889,17 +1123,25 @@ func newBatchDecoderGetPropertyOfTypeParams(base json.Value, fields json.Value, if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("type", len(columns.Type), count); err != nil { - return nil, err + if columns.Type != nil { + if err := validateBatchColumn("type", len(columns.Type), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("name", len(columns.Name), count); err != nil { - return nil, err + if columns.Name != nil { + if err := validateBatchColumn("name", len(columns.Name), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetPropertyOfTypeParams](base, func(params *GetPropertyOfTypeParams, index int) { if columns.Snapshot != nil { @@ -929,17 +1171,25 @@ func newBatchDecoderGetReferencedSymbolsForNodeParams(base json.Value, fields js if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("node", len(columns.Node), count); err != nil { - return nil, err + if columns.Node != nil { + if err := validateBatchColumn("node", len(columns.Node), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("position", len(columns.Position), count); err != nil { - return nil, err + if columns.Position != nil { + if err := validateBatchColumn("position", len(columns.Position), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetReferencedSymbolsForNodeParams](base, func(params *GetReferencedSymbolsForNodeParams, index int) { if columns.Snapshot != nil { @@ -969,17 +1219,25 @@ func newBatchDecoderGetReferencesToSymbolInFileParams(base json.Value, fields js if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("file", len(columns.File), count); err != nil { - return nil, err + if columns.File != nil { + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("symbol", len(columns.Symbol), count); err != nil { - return nil, err + if columns.Symbol != nil { + if err := validateBatchColumn("symbol", len(columns.Symbol), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetReferencesToSymbolInFileParams](base, func(params *GetReferencesToSymbolInFileParams, index int) { if columns.Snapshot != nil { @@ -1009,17 +1267,25 @@ func newBatchDecoderGetResolvedModuleFromModuleSpecifierParams(base json.Value, if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("moduleSpecifier", len(columns.ModuleSpecifier), count); err != nil { - return nil, err + if columns.ModuleSpecifier != nil { + if err := validateBatchColumn("moduleSpecifier", len(columns.ModuleSpecifier), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("sourceFile", len(columns.SourceFile), count); err != nil { - return nil, err + if columns.SourceFile != nil { + if err := validateBatchColumn("sourceFile", len(columns.SourceFile), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetResolvedModuleFromModuleSpecifierParams](base, func(params *GetResolvedModuleFromModuleSpecifierParams, index int) { if columns.Snapshot != nil { @@ -1050,20 +1316,30 @@ func newBatchDecoderGetResolvedModuleParams(base json.Value, fields json.Value, if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("file", len(columns.File), count); err != nil { - return nil, err + if columns.File != nil { + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("moduleName", len(columns.ModuleName), count); err != nil { - return nil, err + if columns.ModuleName != nil { + if err := validateBatchColumn("moduleName", len(columns.ModuleName), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("mode", len(columns.Mode), count); err != nil { - return nil, err + if columns.Mode != nil { + if err := validateBatchColumn("mode", len(columns.Mode), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetResolvedModuleParams](base, func(params *GetResolvedModuleParams, index int) { if columns.Snapshot != nil { @@ -1095,14 +1371,20 @@ func newBatchDecoderGetResolvedSignatureParams(base json.Value, fields json.Valu if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("location", len(columns.Location), count); err != nil { - return nil, err + if columns.Location != nil { + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetResolvedSignatureParams](base, func(params *GetResolvedSignatureParams, index int) { if columns.Snapshot != nil { @@ -1130,20 +1412,30 @@ func newBatchDecoderGetResolvedTypeReferenceDirectiveFromReferenceParams(base js if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("sourceFile", len(columns.SourceFile), count); err != nil { - return nil, err + if columns.SourceFile != nil { + if err := validateBatchColumn("sourceFile", len(columns.SourceFile), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("typeDirectiveName", len(columns.TypeDirectiveName), count); err != nil { - return nil, err + if columns.TypeDirectiveName != nil { + if err := validateBatchColumn("typeDirectiveName", len(columns.TypeDirectiveName), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("resolutionMode", len(columns.ResolutionMode), count); err != nil { - return nil, err + if columns.ResolutionMode != nil { + if err := validateBatchColumn("resolutionMode", len(columns.ResolutionMode), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetResolvedTypeReferenceDirectiveFromReferenceParams](base, func(params *GetResolvedTypeReferenceDirectiveFromReferenceParams, index int) { if columns.Snapshot != nil { @@ -1177,20 +1469,30 @@ func newBatchDecoderGetResolvedTypeReferenceDirectiveParams(base json.Value, fie if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("file", len(columns.File), count); err != nil { - return nil, err + if columns.File != nil { + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("typeDirectiveName", len(columns.TypeDirectiveName), count); err != nil { - return nil, err + if columns.TypeDirectiveName != nil { + if err := validateBatchColumn("typeDirectiveName", len(columns.TypeDirectiveName), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("mode", len(columns.Mode), count); err != nil { - return nil, err + if columns.Mode != nil { + if err := validateBatchColumn("mode", len(columns.Mode), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetResolvedTypeReferenceDirectiveParams](base, func(params *GetResolvedTypeReferenceDirectiveParams, index int) { if columns.Snapshot != nil { @@ -1222,14 +1524,20 @@ func newBatchDecoderGetSignaturePropertyParams(base json.Value, fields json.Valu if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("objectId", len(columns.Signature), count); err != nil { - return nil, err + if columns.Signature != nil { + if err := validateBatchColumn("objectId", len(columns.Signature), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetSignaturePropertyParams](base, func(params *GetSignaturePropertyParams, index int) { if columns.Snapshot != nil { @@ -1255,14 +1563,20 @@ func newBatchDecoderGetSignatureUsagesParams(base json.Value, fields json.Value, if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("signatureDecl", len(columns.SignatureDecl), count); err != nil { - return nil, err + if columns.SignatureDecl != nil { + if err := validateBatchColumn("signatureDecl", len(columns.SignatureDecl), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetSignatureUsagesParams](base, func(params *GetSignatureUsagesParams, index int) { if columns.Snapshot != nil { @@ -1289,17 +1603,25 @@ func newBatchDecoderGetSignaturesOfTypeParams(base json.Value, fields json.Value if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("type", len(columns.Type), count); err != nil { - return nil, err + if columns.Type != nil { + if err := validateBatchColumn("type", len(columns.Type), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("kind", len(columns.Kind), count); err != nil { - return nil, err + if columns.Kind != nil { + if err := validateBatchColumn("kind", len(columns.Kind), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetSignaturesOfTypeParams](base, func(params *GetSignaturesOfTypeParams, index int) { if columns.Snapshot != nil { @@ -1327,11 +1649,15 @@ func newBatchDecoderGetSourceFileNamesParams(base json.Value, fields json.Value, if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetSourceFileNamesParams](base, func(params *GetSourceFileNamesParams, index int) { if columns.Snapshot != nil { @@ -1354,14 +1680,20 @@ func newBatchDecoderGetSourceFileParams(base json.Value, fields json.Value, coun if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("file", len(columns.File), count); err != nil { - return nil, err + if columns.File != nil { + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetSourceFileParams](base, func(params *GetSourceFileParams, index int) { if columns.Snapshot != nil { @@ -1387,14 +1719,20 @@ func newBatchDecoderGetSymbolAtLocationParams(base json.Value, fields json.Value if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("location", len(columns.Location), count); err != nil { - return nil, err + if columns.Location != nil { + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetSymbolAtLocationParams](base, func(params *GetSymbolAtLocationParams, index int) { if columns.Snapshot != nil { @@ -1421,17 +1759,25 @@ func newBatchDecoderGetSymbolAtPositionParams(base json.Value, fields json.Value if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("file", len(columns.File), count); err != nil { - return nil, err + if columns.File != nil { + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("position", len(columns.Position), count); err != nil { - return nil, err + if columns.Position != nil { + if err := validateBatchColumn("position", len(columns.Position), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetSymbolAtPositionParams](base, func(params *GetSymbolAtPositionParams, index int) { if columns.Snapshot != nil { @@ -1460,14 +1806,20 @@ func newBatchDecoderGetSymbolOfSourceFileParams(base json.Value, fields json.Val if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("file", len(columns.File), count); err != nil { - return nil, err + if columns.File != nil { + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetSymbolOfSourceFileParams](base, func(params *GetSymbolOfSourceFileParams, index int) { if columns.Snapshot != nil { @@ -1493,14 +1845,20 @@ func newBatchDecoderGetSymbolPropertyParams(base json.Value, fields json.Value, if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("objectId", len(columns.Symbol), count); err != nil { - return nil, err + if columns.Symbol != nil { + if err := validateBatchColumn("objectId", len(columns.Symbol), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetSymbolPropertyParams](base, func(params *GetSymbolPropertyParams, index int) { if columns.Snapshot != nil { @@ -1529,23 +1887,35 @@ func newBatchDecoderGetSymbolsInScopeParams(base json.Value, fields json.Value, if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("location", len(columns.Location), count); err != nil { - return nil, err + if columns.Location != nil { + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("file", len(columns.File), count); err != nil { - return nil, err + if columns.File != nil { + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("position", len(columns.Position), count); err != nil { - return nil, err + if columns.Position != nil { + if err := validateBatchColumn("position", len(columns.Position), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("meaning", len(columns.Meaning), count); err != nil { - return nil, err + if columns.Meaning != nil { + if err := validateBatchColumn("meaning", len(columns.Meaning), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetSymbolsInScopeParams](base, func(params *GetSymbolsInScopeParams, index int) { if columns.Snapshot != nil { @@ -1580,14 +1950,20 @@ func newBatchDecoderGetTypeAtLocationParams(base json.Value, fields json.Value, if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("location", len(columns.Location), count); err != nil { - return nil, err + if columns.Location != nil { + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetTypeAtLocationParams](base, func(params *GetTypeAtLocationParams, index int) { if columns.Snapshot != nil { @@ -1614,17 +1990,25 @@ func newBatchDecoderGetTypeAtPositionParams(base json.Value, fields json.Value, if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("file", len(columns.File), count); err != nil { - return nil, err + if columns.File != nil { + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("position", len(columns.Position), count); err != nil { - return nil, err + if columns.Position != nil { + if err := validateBatchColumn("position", len(columns.Position), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetTypeAtPositionParams](base, func(params *GetTypeAtPositionParams, index int) { if columns.Snapshot != nil { @@ -1653,14 +2037,20 @@ func newBatchDecoderGetTypeFromTypeNodeParams(base json.Value, fields json.Value if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("location", len(columns.Location), count); err != nil { - return nil, err + if columns.Location != nil { + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetTypeFromTypeNodeParams](base, func(params *GetTypeFromTypeNodeParams, index int) { if columns.Snapshot != nil { @@ -1687,17 +2077,25 @@ func newBatchDecoderGetTypeOfSymbolAtLocationParams(base json.Value, fields json if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("symbol", len(columns.Symbol), count); err != nil { - return nil, err + if columns.Symbol != nil { + if err := validateBatchColumn("symbol", len(columns.Symbol), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("location", len(columns.Location), count); err != nil { - return nil, err + if columns.Location != nil { + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetTypeOfSymbolAtLocationParams](base, func(params *GetTypeOfSymbolAtLocationParams, index int) { if columns.Snapshot != nil { @@ -1726,14 +2124,20 @@ func newBatchDecoderGetTypeOfSymbolParams(base json.Value, fields json.Value, co if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("symbol", len(columns.Symbol), count); err != nil { - return nil, err + if columns.Symbol != nil { + if err := validateBatchColumn("symbol", len(columns.Symbol), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetTypeOfSymbolParams](base, func(params *GetTypeOfSymbolParams, index int) { if columns.Snapshot != nil { @@ -1759,14 +2163,20 @@ func newBatchDecoderGetTypePropertyParams(base json.Value, fields json.Value, co if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("objectId", len(columns.Type), count); err != nil { - return nil, err + if columns.Type != nil { + if err := validateBatchColumn("objectId", len(columns.Type), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetTypePropertyParams](base, func(params *GetTypePropertyParams, index int) { if columns.Snapshot != nil { @@ -1792,14 +2202,20 @@ func newBatchDecoderGetWidenedTypeParams(base json.Value, fields json.Value, cou if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("type", len(columns.Type), count); err != nil { - return nil, err + if columns.Type != nil { + if err := validateBatchColumn("type", len(columns.Type), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[GetWidenedTypeParams](base, func(params *GetWidenedTypeParams, index int) { if columns.Snapshot != nil { @@ -1825,14 +2241,20 @@ func newBatchDecoderIsArrayLikeTypeParams(base json.Value, fields json.Value, co if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("type", len(columns.Type), count); err != nil { - return nil, err + if columns.Type != nil { + if err := validateBatchColumn("type", len(columns.Type), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[IsArrayLikeTypeParams](base, func(params *IsArrayLikeTypeParams, index int) { if columns.Snapshot != nil { @@ -1859,17 +2281,25 @@ func newBatchDecoderIsTypeAssignableToParams(base json.Value, fields json.Value, if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("source", len(columns.Source), count); err != nil { - return nil, err + if columns.Source != nil { + if err := validateBatchColumn("source", len(columns.Source), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("target", len(columns.Target), count); err != nil { - return nil, err + if columns.Target != nil { + if err := validateBatchColumn("target", len(columns.Target), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[IsTypeAssignableToParams](base, func(params *IsTypeAssignableToParams, index int) { if columns.Snapshot != nil { @@ -1896,8 +2326,10 @@ func newBatchDecoderParseCommandLineParams(base json.Value, fields json.Value, c if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("commandLine", len(columns.CommandLine), count); err != nil { - return nil, err + if columns.CommandLine != nil { + if err := validateBatchColumn("commandLine", len(columns.CommandLine), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[ParseCommandLineParams](base, func(params *ParseCommandLineParams, index int) { if columns.CommandLine != nil { @@ -1915,8 +2347,10 @@ func newBatchDecoderParseConfigFileParams(base json.Value, fields json.Value, co if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("file", len(columns.File), count); err != nil { - return nil, err + if columns.File != nil { + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[ParseConfigFileParams](base, func(params *ParseConfigFileParams, index int) { if columns.File != nil { @@ -1936,14 +2370,20 @@ func newBatchDecoderParseJsonConfigFileContentParams(base json.Value, fields jso if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("json", len(columns.JSON), count); err != nil { - return nil, err + if columns.JSON != nil { + if err := validateBatchColumn("json", len(columns.JSON), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("configDirectory", len(columns.ConfigDirectory), count); err != nil { - return nil, err + if columns.ConfigDirectory != nil { + if err := validateBatchColumn("configDirectory", len(columns.ConfigDirectory), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("configFileName", len(columns.ConfigFileName), count); err != nil { - return nil, err + if columns.ConfigFileName != nil { + if err := validateBatchColumn("configFileName", len(columns.ConfigFileName), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[ParseJsonConfigFileContentParams](base, func(params *ParseJsonConfigFileContentParams, index int) { if columns.JSON != nil { @@ -1970,17 +2410,25 @@ func newBatchDecoderPrintNodeParams(base json.Value, fields json.Value, count in if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("data", len(columns.Data), count); err != nil { - return nil, err + if columns.Data != nil { + if err := validateBatchColumn("data", len(columns.Data), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("preserveSourceNewlines", len(columns.PreserveSourceNewlines), count); err != nil { - return nil, err + if columns.PreserveSourceNewlines != nil { + if err := validateBatchColumn("preserveSourceNewlines", len(columns.PreserveSourceNewlines), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("neverAsciiEscape", len(columns.NeverAsciiEscape), count); err != nil { - return nil, err + if columns.NeverAsciiEscape != nil { + if err := validateBatchColumn("neverAsciiEscape", len(columns.NeverAsciiEscape), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("terminateUnterminatedLiterals", len(columns.TerminateUnterminatedLiterals), count); err != nil { - return nil, err + if columns.TerminateUnterminatedLiterals != nil { + if err := validateBatchColumn("terminateUnterminatedLiterals", len(columns.TerminateUnterminatedLiterals), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[PrintNodeParams](base, func(params *PrintNodeParams, index int) { if columns.Data != nil { @@ -2007,8 +2455,10 @@ func newBatchDecoderProfileParams(base json.Value, fields json.Value, count int) if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("dir", len(columns.Dir), count); err != nil { - return nil, err + if columns.Dir != nil { + if err := validateBatchColumn("dir", len(columns.Dir), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[ProfileParams](base, func(params *ProfileParams, index int) { if columns.Dir != nil { @@ -2026,8 +2476,10 @@ func newBatchDecoderReadConfigFileParams(base json.Value, fields json.Value, cou if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("file", len(columns.File), count); err != nil { - return nil, err + if columns.File != nil { + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[ReadConfigFileParams](base, func(params *ReadConfigFileParams, index int) { if columns.File != nil { @@ -2045,8 +2497,10 @@ func newBatchDecoderReleaseModuleResolverParams(base json.Value, fields json.Val if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("resolver", len(columns.Resolver), count); err != nil { - return nil, err + if columns.Resolver != nil { + if err := validateBatchColumn("resolver", len(columns.Resolver), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[ReleaseModuleResolverParams](base, func(params *ReleaseModuleResolverParams, index int) { if columns.Resolver != nil { @@ -2064,8 +2518,10 @@ func newBatchDecoderReleaseParams(base json.Value, fields json.Value, count int) if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[ReleaseParams](base, func(params *ReleaseParams, index int) { if columns.Snapshot != nil { @@ -2088,23 +2544,35 @@ func newBatchDecoderResolveModuleNameParams(base json.Value, fields json.Value, if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("inProgressSnapshot", len(columns.InProgressSnapshot), count); err != nil { - return nil, err + if columns.InProgressSnapshot != nil { + if err := validateBatchColumn("inProgressSnapshot", len(columns.InProgressSnapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("resolver", len(columns.Resolver), count); err != nil { - return nil, err + if columns.Resolver != nil { + if err := validateBatchColumn("resolver", len(columns.Resolver), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("moduleName", len(columns.ModuleName), count); err != nil { - return nil, err + if columns.ModuleName != nil { + if err := validateBatchColumn("moduleName", len(columns.ModuleName), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("containingDirectory", len(columns.ContainingDirectory), count); err != nil { - return nil, err + if columns.ContainingDirectory != nil { + if err := validateBatchColumn("containingDirectory", len(columns.ContainingDirectory), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("resolutionMode", len(columns.ResolutionMode), count); err != nil { - return nil, err + if columns.ResolutionMode != nil { + if err := validateBatchColumn("resolutionMode", len(columns.ResolutionMode), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[ResolveModuleNameParams](base, func(params *ResolveModuleNameParams, index int) { if columns.Snapshot != nil { @@ -2144,29 +2612,45 @@ func newBatchDecoderResolveNameParams(base json.Value, fields json.Value, count if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("name", len(columns.Name), count); err != nil { - return nil, err + if columns.Name != nil { + if err := validateBatchColumn("name", len(columns.Name), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("location", len(columns.Location), count); err != nil { - return nil, err + if columns.Location != nil { + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("file", len(columns.File), count); err != nil { - return nil, err + if columns.File != nil { + if err := validateBatchColumn("file", len(columns.File), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("position", len(columns.Position), count); err != nil { - return nil, err + if columns.Position != nil { + if err := validateBatchColumn("position", len(columns.Position), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("meaning", len(columns.Meaning), count); err != nil { - return nil, err + if columns.Meaning != nil { + if err := validateBatchColumn("meaning", len(columns.Meaning), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("excludeGlobals", len(columns.ExcludeGlobals), count); err != nil { - return nil, err + if columns.ExcludeGlobals != nil { + if err := validateBatchColumn("excludeGlobals", len(columns.ExcludeGlobals), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[ResolveNameParams](base, func(params *ResolveNameParams, index int) { if columns.Snapshot != nil { @@ -2207,14 +2691,20 @@ func newBatchDecoderSelectedFilesEmitParams(base json.Value, fields json.Value, if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("files", len(columns.Files), count); err != nil { - return nil, err + if columns.Files != nil { + if err := validateBatchColumn("files", len(columns.Files), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[SelectedFilesEmitParams](base, func(params *SelectedFilesEmitParams, index int) { if columns.Snapshot != nil { @@ -2243,23 +2733,35 @@ func newBatchDecoderSignatureToSignatureDeclarationParams(base json.Value, field if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("signature", len(columns.Signature), count); err != nil { - return nil, err + if columns.Signature != nil { + if err := validateBatchColumn("signature", len(columns.Signature), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("kind", len(columns.Kind), count); err != nil { - return nil, err + if columns.Kind != nil { + if err := validateBatchColumn("kind", len(columns.Kind), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("location", len(columns.Location), count); err != nil { - return nil, err + if columns.Location != nil { + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("flags", len(columns.Flags), count); err != nil { - return nil, err + if columns.Flags != nil { + if err := validateBatchColumn("flags", len(columns.Flags), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[SignatureToSignatureDeclarationParams](base, func(params *SignatureToSignatureDeclarationParams, index int) { if columns.Snapshot != nil { @@ -2293,11 +2795,15 @@ func newBatchDecoderTranspileFromFileParams(base json.Value, fields json.Value, if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("fileName", len(columns.FileName), count); err != nil { - return nil, err + if columns.FileName != nil { + if err := validateBatchColumn("fileName", len(columns.FileName), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("options", len(columns.Options), count); err != nil { - return nil, err + if columns.Options != nil { + if err := validateBatchColumn("options", len(columns.Options), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[TranspileFromFileParams](base, func(params *TranspileFromFileParams, index int) { if columns.FileName != nil { @@ -2319,11 +2825,15 @@ func newBatchDecoderTranspileParams(base json.Value, fields json.Value, count in if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("input", len(columns.Input), count); err != nil { - return nil, err + if columns.Input != nil { + if err := validateBatchColumn("input", len(columns.Input), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("options", len(columns.Options), count); err != nil { - return nil, err + if columns.Options != nil { + if err := validateBatchColumn("options", len(columns.Options), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[TranspileParams](base, func(params *TranspileParams, index int) { if columns.Input != nil { @@ -2348,20 +2858,30 @@ func newBatchDecoderTypeToTypeNodeParams(base json.Value, fields json.Value, cou if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("project", len(columns.Project), count); err != nil { - return nil, err + if columns.Project != nil { + if err := validateBatchColumn("project", len(columns.Project), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("type", len(columns.Type), count); err != nil { - return nil, err + if columns.Type != nil { + if err := validateBatchColumn("type", len(columns.Type), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("location", len(columns.Location), count); err != nil { - return nil, err + if columns.Location != nil { + if err := validateBatchColumn("location", len(columns.Location), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("flags", len(columns.Flags), count); err != nil { - return nil, err + if columns.Flags != nil { + if err := validateBatchColumn("flags", len(columns.Flags), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[TypeToTypeNodeParams](base, func(params *TypeToTypeNodeParams, index int) { if columns.Snapshot != nil { @@ -2392,11 +2912,15 @@ func newBatchDecoderUpdateSnapshotParams(base json.Value, fields json.Value, cou if err := json.Unmarshal(fields, &columns); err != nil { return nil, err } - if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { - return nil, err + if columns.Snapshot != nil { + if err := validateBatchColumn("snapshot", len(columns.Snapshot), count); err != nil { + return nil, err + } } - if err := validateBatchColumn("changes", len(columns.Changes), count); err != nil { - return nil, err + if columns.Changes != nil { + if err := validateBatchColumn("changes", len(columns.Changes), count); err != nil { + return nil, err + } } return newTypedBatchRequestDecoder[UpdateSnapshotParams](base, func(params *UpdateSnapshotParams, index int) { if columns.Snapshot != nil { diff --git a/tsc/internal/api/session.go b/tsc/internal/api/session.go index 97b288190e6f1..5e8d05dfdb6d7 100644 --- a/tsc/internal/api/session.go +++ b/tsc/internal/api/session.go @@ -503,7 +503,7 @@ func (d *typedBatchRequestDecoder[T]) request(index int) any { } func validateBatchColumn(name string, length int, count int) error { - if length != 0 && length != count { + if length != count { return fmt.Errorf("parameter %q has %d values, expected %d", name, length, count) } return nil @@ -1461,13 +1461,7 @@ func (s *Session) handleBatchRequest(ctx context.Context, request BatchRequest) if err != nil { response.Error = err.Error() } - if data, ok := response.Result.(RawBinary); ok && isSourceFileResponseMethod(request.Method) { - if data == nil { - response.Result = nil - } else { - response.Result = &SourceFileResponse{Data: base64.StdEncoding.EncodeToString(data)} - } - } + response.Result = normalizeBatchResult(request.Method, response.Result) return response } @@ -1483,9 +1477,20 @@ func (s *Session) handleParsedBatchRequest(ctx context.Context, method Method, p if err != nil { response.Error = err.Error() } + response.Result = normalizeBatchResult(method, response.Result) return response } +func normalizeBatchResult(method Method, result any) any { + if data, ok := result.(RawBinary); ok && isSourceFileResponseMethod(method) { + if data == nil { + return nil + } + return &SourceFileResponse{Data: base64.StdEncoding.EncodeToString(data)} + } + return result +} + func isSourceFileResponseMethod(method Method) bool { switch method { case MethodCreateSourceFile, diff --git a/tsc/internal/api/session_batch_test.go b/tsc/internal/api/session_batch_test.go index ab51a600e1af3..0541fee306c25 100644 --- a/tsc/internal/api/session_batch_test.go +++ b/tsc/internal/api/session_batch_test.go @@ -2,6 +2,8 @@ package api import ( "context" + "encoding/base64" + "errors" "strings" "testing" "time" @@ -76,6 +78,146 @@ func TestHandleGroupedBatchRequestsDecodesRowParams(t *testing.T) { assert.Equal(t, len(response.(*BatchRequestsResponse).Errors), 0) } +func TestGroupedBatchSourceFileResponses(t *testing.T) { + t.Parallel() + + for _, binary := range []bool{false, true} { + name := "json" + if binary { + name = "binary" + } + t.Run(name, func(t *testing.T) { + t.Parallel() + + projectSession, _ := projecttestutil.Setup(map[string]any{}) + defer projectSession.Close() + session := NewLSPSession(projectSession, &SessionOptions{UseBinaryResponses: binary}) + defer session.Close() + + params := json.Value(`{"fileName":"/index.ts","sourceText":"export const value = 1;"}`) + regular, err := session.handleBatchRequests(t.Context(), &BatchRequestsParams{ + Requests: []BatchRequest{{Method: MethodCreateSourceFile, Params: params}}, + }) + assert.NilError(t, err) + assert.Equal(t, len(regular.Errors), 0) + expected, ok := regular.Results[0].(*SourceFileResponse) + assert.Assert(t, ok, "regular batch returned %T", regular.Results[0]) + decoded, err := base64.StdEncoding.DecodeString(expected.Data) + assert.NilError(t, err) + assert.Assert(t, len(decoded) > 0) + + grouped, err := session.handleBatchRequests(t.Context(), &BatchRequestsParams{ + Groups: []BatchRequestGroup{{ + Method: MethodCreateSourceFile, + Base: json.Value(`{"fileName":"/index.ts"}`), + Count: 4, + Fields: json.Value(`{"sourceText":["export const value = 1;","export const value = 1;","export const value = 1;","export const value = 1;"]}`), + }}, + }) + assert.NilError(t, err) + assert.Equal(t, len(grouped.Errors), 0) + assert.Equal(t, len(grouped.Results), 4) + for _, result := range grouped.Results { + actual, ok := result.(*SourceFileResponse) + assert.Assert(t, ok, "columnar batch returned %T", result) + assert.DeepEqual(t, actual, expected) + } + }) + } +} + +func TestBatchDecoderColumnLengths(t *testing.T) { + t.Parallel() + + for _, test := range []struct { + name string + fields string + count int + invalid bool + }{ + {name: "omitted", fields: `{}`, count: 4}, + {name: "empty", fields: `{"position":[]}`, count: 4, invalid: true}, + {name: "short", fields: `{"position":[1]}`, count: 4, invalid: true}, + {name: "long", fields: `{"position":[1,2,3,4,5]}`, count: 4, invalid: true}, + {name: "exact", fields: `{"position":[1,2,3,4]}`, count: 4}, + {name: "zero count empty", fields: `{"position":[]}`, count: 0}, + {name: "zero count omitted", fields: `{}`, count: 0}, + {name: "zero count nonempty", fields: `{"position":[1]}`, count: 0, invalid: true}, + } { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + decoder, err := newGeneratedBatchRequestDecoder(MethodGetSymbolAtPosition, json.Value(`{}`), json.Value(test.fields), test.count) + if test.invalid { + assert.ErrorContains(t, err, "parameter \"position\"") + responses, err := (&Session{}).handleBatchRequestGroups(t.Context(), []BatchRequestGroup{ + {Method: MethodGetSymbolAtPosition, Count: test.count, Fields: json.Value(test.fields)}, + }, nil) + assert.Assert(t, errors.Is(err, ErrInvalidRequest)) + assert.Equal(t, len(responses), 0) + return + } + assert.NilError(t, err) + for index := range test.count { + params := decoder.request(index).(*GetSymbolAtPositionParams) + expected := uint32(0) + if test.name == "exact" { + expected = uint32(index + 1) + } + assert.Equal(t, params.Position, expected) + } + }) + } +} + +func TestBatchDecoderPromotedSnapshotFields(t *testing.T) { + t.Parallel() + + rows := []json.Value{ + json.Value(`{ + "openProjects":["/first/tsconfig.json"], + "closeProjects":["/old/tsconfig.json"], + "openFiles":["/first/index.ts"], + "closeFiles":["/old/index.ts"], + "createPrograms":[{"rootFiles":["/first/index.ts"],"compilerOptions":{}}], + "reconfigurePrograms":[{"id":"/dev/null/synthetic/1","rootFiles":["/first/index.ts"],"compilerOptions":{}}], + "removePrograms":["/dev/null/synthetic/2"], + "ensurePrograms":true + }`), + json.Value(`{ + "openProjects":["/second/tsconfig.json"], + "closeProjects":null, + "openFiles":["/second/index.ts"], + "closeFiles":null, + "createPrograms":null, + "reconfigurePrograms":null, + "removePrograms":null, + "ensurePrograms":null + }`), + } + columns := make(map[string][]json.Value) + for _, row := range rows { + var fields map[string]json.Value + assert.NilError(t, json.Unmarshal(row, &fields)) + for name, value := range fields { + columns[name] = append(columns[name], value) + } + } + fields, err := json.Marshal(columns) + assert.NilError(t, err) + base := json.Value(`{"fileNotifications":{"invalidateAll":true},"closeProjects":["/base/tsconfig.json"]}`) + decoder, err := newGeneratedBatchRequestDecoder(MethodCreateSnapshot, base, fields, len(rows)) + assert.NilError(t, err) + for index, row := range rows { + var expected CreateSnapshotParams + assert.NilError(t, json.Unmarshal(base, &expected)) + assert.NilError(t, json.Unmarshal(row, &expected)) + assert.DeepEqual(t, decoder.request(index).(*CreateSnapshotParams), &expected) + } + _, err = newGeneratedBatchRequestDecoder(MethodCreateSnapshot, nil, json.Value(`{"openProjects":[[]]}`), 2) + assert.ErrorContains(t, err, "openProjects") +} + func TestHandleGroupedBatchRequestsReacquiresCheckerAfterInterleaving(t *testing.T) { t.Parallel()