diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 3ee77549724..05246284418 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -37,6 +37,12 @@ npx hereby format # Format the code ``` +If you are writing or testing TS API features (eg, code in _packages/native-preview/src/api/async/api.ts), additionally, you need to run +```sh +npx hereby test:api +``` +which is not run as part of the primary suite. + ## Compiler Features, Fixes, and Tests When fixing a bug or implementing a new feature, at least one minimal test case should always be added in advance to verify the fix. diff --git a/Herebyfile.mjs b/Herebyfile.mjs index eb170f03554..9617c589058 100644 --- a/Herebyfile.mjs +++ b/Herebyfile.mjs @@ -577,6 +577,12 @@ export const generateAST = task({ run: () => $`node --experimental-strip-types --no-warnings ./_scripts/generate.ts`, }); +export const generateAPI = task({ + name: "generate:api", + description: "Generates API files from internal/api/proto.go and internal/api/session.go.", + run: () => $`go -C ./_tools run ./gen-proto ../internal/api/proto.go ../_packages/native-preview/src/api/proto.generated.ts`, +}); + // ── Vendored npm dependencies ─────────────────────────────────── const vendorJsonrpcDir = "_packages/native-preview/vendor/vscode-jsonrpc"; @@ -834,6 +840,7 @@ export const buildAPI = task({ export const buildAPITests = task({ name: "build:api:test", description: "Builds the @typescript/native-preview JS API tests.", + dependencies: [generateEnums, generateAPI], run: async () => { await $`npm run -w @typescript/native-preview build:test`; }, diff --git a/_packages/native-preview/src/api/async/api.ts b/_packages/native-preview/src/api/async/api.ts index 797171cc11f..09f38832fe3 100644 --- a/_packages/native-preview/src/api/async/api.ts +++ b/_packages/native-preview/src/api/async/api.ts @@ -49,26 +49,28 @@ import { } from "../path.ts"; import type { CompilerOptions, - CompletionInfoResponse, + Diagnostic, DocumentIdentifier, DocumentPosition, - ImportAdderActionRequest, - ImportSymbolActionRequest, - IndexInfoResponse, - InitializeResponse, + EmitOutputResponse as ProtocolEmitOutputResponse, + ImportAdderAction, + IntrinsicTypeMethod, LSPUpdateSnapshotParams, ParsedCommandLine, - ProfileResult, ProjectReference, ProjectResponse, - ReadConfigFileResult, + ReadConfigFileResponse, + SignaturePropertyMethod, SignatureResponse, SourceFileMetadata, + SymbolPropertyMethod, SymbolResponse, + SymbolsPropertyMethod, TextEdit, TypeAcquisition, - TypePredicateResponse, + TypePropertyMethod, TypeResponse, + TypesPropertyMethod, UpdateSnapshotParams, UpdateSnapshotResponse, } from "../proto.ts"; @@ -96,14 +98,13 @@ import type { CompletionInfo, CompletionOptions, ConditionalType, - Diagnostic, EmitOutput, EmitOutputFile, EmitResult, FreshableType, GetImportEditsForSymbolsOptions, IdentifierTypePredicate, - ImportAdderAction, + ImportAdderAction as APIImportAdderAction, IndexedAccessType, IndexInfo, IndexType, @@ -132,13 +133,64 @@ import type { export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts"; export { CheckFlags, CompletionItemKind, DiagnosticCategory, ElementFlags, EmitOnly, ModifierFlags, ModuleKind, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind }; -export type { APIOptions, AssertsIdentifierTypePredicate, AssertsThisTypePredicate, BigIntLiteralType, BooleanLiteralType, ClientSocketOptions, ClientSpawnOptions, CompilerOptions, CompletionEntry, CompletionInfo, CompletionOptions, ConditionalType, Diagnostic, DocumentIdentifier, DocumentPosition, EmitOutput, EmitOutputFile, EmitResult, FreshableType, GetImportEditsForSymbolsOptions, IdentifierTypePredicate, ImportAdderAction, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, JSDocTagInfo, LiteralType, LSPConnectionOptions, NumberLiteralType, ObjectType, ParsedCommandLine, ProjectReference, ReadConfigFileResult, RequestTiming, SourceFileMetadata, StringLiteralType, StringMappingType, StructuredType, SubstitutionType, TemplateLiteralType, TextEdit, ThisTypePredicate, TimingAccumulators, TimingInfo, TupleType, Type, TypeAcquisition, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; - -interface EmitOutputResponse { - readonly emitSkipped: boolean; - readonly diagnostics: readonly Diagnostic[]; - readonly outputFiles: readonly (EmitOutputFile & { readonly fileName: string; })[]; -} +export type { + APIImportAdderAction as ImportAdderAction, + APIOptions, + AssertsIdentifierTypePredicate, + AssertsThisTypePredicate, + BigIntLiteralType, + BooleanLiteralType, + ClientSocketOptions, + ClientSpawnOptions, + CompilerOptions, + CompletionEntry, + CompletionInfo, + CompletionOptions, + ConditionalType, + Diagnostic, + DocumentIdentifier, + DocumentPosition, + EmitOutput, + EmitOutputFile, + EmitResult, + FreshableType, + GetImportEditsForSymbolsOptions, + IdentifierTypePredicate, + IndexedAccessType, + IndexInfo, + IndexType, + InterfaceType, + IntersectionType, + IntrinsicType, + JSDocTagInfo, + LiteralType, + LSPConnectionOptions, + NumberLiteralType, + ObjectType, + ParsedCommandLine, + ProjectReference, + ReadConfigFileResponse, + RequestTiming, + SourceFileMetadata, + StringLiteralType, + StringMappingType, + StructuredType, + SubstitutionType, + TemplateLiteralType, + TextEdit, + ThisTypePredicate, + TimingAccumulators, + TimingInfo, + TupleType, + Type, + TypeAcquisition, + TypeParameter, + TypePredicate, + TypePredicateBase, + TypeReference, + UnionOrIntersectionType, + UnionType, +}; export interface TranspileOptions { compilerOptions?: CompilerOptions; @@ -172,14 +224,14 @@ export class API { * Use this when connecting to an API pipe provided by an LSP server via custom/initializeAPISession. */ static async fromLSPConnection(options: LSPConnectionOptions): Promise> { - const api = new API(options); + const api = new API(options); await api.ensureInitialized(); return api; } private async ensureInitialized(): Promise { if (!this.initialized) { - const response = await this.client.apiRequest("initialize", null); + const response = await this.client.apiRequest("initialize", null); const getCanonicalFileName = createGetCanonicalFileName(response.useCaseSensitiveFileNames); const currentDirectory = response.currentDirectory; this.toPath = (fileName: string) => toPath(fileName, currentDirectory, getCanonicalFileName) as Path; @@ -189,17 +241,17 @@ export class API { async parseConfigFile(file: DocumentIdentifier): Promise { await this.ensureInitialized(); - return this.client.apiRequest("parseConfigFile", { file }); + return this.client.apiRequest("parseConfigFile", { file }); } async parseCommandLine(commandLine: readonly string[]): Promise { await this.ensureInitialized(); - return this.client.apiRequest("parseCommandLine", { commandLine }); + return this.client.apiRequest("parseCommandLine", { commandLine }); } - async readConfigFile(file: DocumentIdentifier): Promise { + async readConfigFile(file: DocumentIdentifier): Promise { await this.ensureInitialized(); - return this.client.apiRequest("readConfigFile", { file }); + return this.client.apiRequest("readConfigFile", { file }); } async parseJsonConfigFileContent( @@ -209,34 +261,34 @@ export class API { | { configFileName: DocumentIdentifier; configDirectory?: never; }, ): Promise { await this.ensureInitialized(); - return this.client.apiRequest("parseJsonConfigFileContent", { json, ...options }); + return this.client.apiRequest("parseJsonConfigFileContent", { json, ...options }); } async transpileModule(input: string, options: TranspileOptions = {}): Promise { await this.ensureInitialized(); - return this.client.apiRequest("transpileModule", { input, options }); + return this.client.apiRequest("transpileModule", { input, options }); } async transpileModuleFromFile(fileName: string, options: TranspileOptions = {}): Promise { await this.ensureInitialized(); - return this.client.apiRequest("transpileModuleFromFile", { fileName, options }); + return this.client.apiRequest("transpileModuleFromFile", { fileName, options }); } async transpileDeclaration(input: string, options: TranspileOptions = {}): Promise { await this.ensureInitialized(); - return this.client.apiRequest("transpileDeclaration", { input, options }); + return this.client.apiRequest("transpileDeclaration", { input, options }); } async transpileDeclarationFromFile(fileName: string, options: TranspileOptions = {}): Promise { await this.ensureInitialized(); - return this.client.apiRequest("transpileDeclarationFromFile", { fileName, options }); + return this.client.apiRequest("transpileDeclarationFromFile", { fileName, options }); } async updateSnapshot(params?: FromLSP extends true ? LSPUpdateSnapshotParams : UpdateSnapshotParams): Promise { await this.ensureInitialized(); const requestParams = toUpdateSnapshotRequest(params); - const data = await this.client.apiRequest("updateSnapshot", requestParams); + const data = await this.client.apiRequest("updateSnapshot", requestParams); // Retain cached source files from previous snapshot for unchanged files if (this.latestSnapshot) { @@ -288,7 +340,7 @@ export class API { if (!this.activeSnapshots.has(baseSnapshot) || baseSnapshot.isDisposed()) { throw new Error("Cannot run a temporary file update on an inactive snapshot"); } - const data = await this.client.apiRequest("updateTemporarySnapshot", { snapshot: baseSnapshot.id, file, newText }); + const data = await this.client.apiRequest("updateTemporarySnapshot", { snapshot: baseSnapshot.id, file, newText }); // Retain cached source files from the base snapshot for files unchanged by // the temporary update. The temporary snapshot is not the latest snapshot, so @@ -353,13 +405,13 @@ export class InternalAPI { async stopCPUProfile(): Promise { await this.ensureInitialized(); - const result = await this.client.apiRequest("stopCPUProfile", null); + const result = await this.client.apiRequest("stopCPUProfile", null); return result.file; } async saveHeapProfile(dir: string): Promise { await this.ensureInitialized(); - const result = await this.client.apiRequest("saveHeapProfile", { dir }); + const result = await this.client.apiRequest("saveHeapProfile", { dir }); return result.file; } } @@ -408,7 +460,7 @@ export class Snapshot { async getDefaultProjectForFile(file: DocumentIdentifier): Promise { this.ensureNotDisposed(); - const data = await this.client.apiRequest("getDefaultProjectForFile", { + const data = await this.client.apiRequest("getDefaultProjectForFile", { snapshot: this.id, file, }); @@ -477,12 +529,12 @@ class SnapshotObjectRegistry { this.symbols.clear(); } - async fetchSymbol(source: Symbol | Signature | Type, method: string, handle: number | undefined, projectId?: Path): Promise { + async fetchSymbol(source: Symbol | Signature | Type, method: SymbolPropertyMethod, handle: number | undefined, projectId: Path): Promise { if (!handle) return undefined as unknown as Symbol; const cached = this.getSymbol(handle); if (cached) return cached; - const data = await this.client.apiRequest(method, { + const data = await this.client.apiRequest(method, { snapshot: this.snapshotId, project: projectId, objectId: source.id, @@ -491,7 +543,7 @@ class SnapshotObjectRegistry { return this.getOrCreateSymbol(data); } - async fetchSymbols(source: Symbol | Signature | Type, method: string, handles?: readonly number[], projectId?: Path): Promise { + async fetchSymbols(source: Symbol | Signature | Type, method: SymbolsPropertyMethod, handles: readonly number[] | undefined, projectId: Path): Promise { if (handles) { const result = new Array(handles.length); let allCached = true; @@ -505,7 +557,7 @@ class SnapshotObjectRegistry { } if (allCached) return result; } - const symbolData = await this.client.apiRequest(method, { + const symbolData = await this.client.apiRequest(method, { snapshot: this.snapshotId, project: projectId, objectId: source.id, @@ -574,14 +626,14 @@ class ProjectObjectRegistry { this.signatures.clear(); } - async fetchOptionalType(source: Symbol | Signature | Type, method: string, handle: number | false | undefined): Promise { + async fetchOptionalType(source: Symbol | Signature | Type, method: TypePropertyMethod, handle: number | false | undefined): Promise { if (handle !== false) { if (!handle) return undefined; const cached = this.getType(handle); if (cached) return cached as unknown as T; } - const data = await this.client.apiRequest(method, { + const data = await this.client.apiRequest(method, { snapshot: this.snapshotId, project: this.project.id, objectId: source.id, @@ -590,22 +642,22 @@ class ProjectObjectRegistry { return this.getOrCreateType(data) as unknown as T; } - async fetchType(source: Symbol | Signature | Type, method: string, handle: number | false | undefined): Promise { + async fetchType(source: Symbol | Signature | Type, method: TypePropertyMethod, handle: number | false | undefined): Promise { const result = await this.fetchOptionalType(source, method, handle); if (result === undefined) throw new Error(`${method} returned no type for ${source.constructor.name} ${source.id}`); return result; } - async fetchSymbol(source: Symbol | Signature | Type, method: string, handle: number | undefined): Promise { + async fetchSymbol(source: Symbol | Signature | Type, method: SymbolPropertyMethod, handle: number | undefined): Promise { return this.snapshotRegistry.fetchSymbol(source, method, handle, this.project.id); } - async fetchSignature(source: Symbol | Signature | Type, method: string, handle: number | undefined): Promise { + async fetchSignature(source: Symbol | Signature | Type, method: SignaturePropertyMethod, handle: number | undefined): Promise { if (!handle) return undefined as unknown as Signature; const cached = this.getSignature(handle); if (cached) return cached; - const data = await this.client.apiRequest(method, { + const data = await this.client.apiRequest(method, { snapshot: this.snapshotId, project: this.project.id, objectId: source.id, @@ -614,7 +666,7 @@ class ProjectObjectRegistry { return this.getOrCreateSignature(data); } - async fetchTypes(source: Symbol | Signature | Type, method: string, handles?: readonly number[]): Promise { + async fetchTypes(source: Symbol | Signature | Type, method: TypesPropertyMethod, handles?: readonly number[]): Promise { if (handles) { const result = new Array(handles.length); let allCached = true; @@ -628,7 +680,7 @@ class ProjectObjectRegistry { } if (allCached) return result; } - const typesData = await this.client.apiRequest(method, { + const typesData = await this.client.apiRequest(method, { snapshot: this.snapshotId, project: this.project.id, objectId: source.id, @@ -637,14 +689,14 @@ class ProjectObjectRegistry { else return typesData.map(data => this.getOrCreateType(data)); } - async fetchSymbols(source: Symbol | Signature | Type, method: string, handles?: readonly number[]): Promise { + async fetchSymbols(source: Symbol | Signature | Type, method: SymbolsPropertyMethod, handles?: readonly number[]): Promise { return this.snapshotRegistry.fetchSymbols(source, method, handles, this.project.id); } // getBaseTypes is a checker-level endpoint keyed by `type` (not `objectId`), // so it cannot go through fetchTypes. This helper reuses that server method. async fetchBaseTypes(source: Type): Promise { - const typesData = await this.client.apiRequest("getBaseTypes", { + const typesData = await this.client.apiRequest("getBaseTypes", { snapshot: this.snapshotId, project: this.project.id, type: source.id, @@ -654,7 +706,7 @@ class ProjectObjectRegistry { } async fetchPropertiesOfType(source: Type): Promise { - const data = await this.client.apiRequest("getPropertiesOfType", { + const data = await this.client.apiRequest("getPropertiesOfType", { snapshot: this.snapshotId, project: this.project.id, type: source.id, @@ -663,7 +715,7 @@ class ProjectObjectRegistry { } async fetchApparentPropertiesOfType(source: Type): Promise { - const data = await this.client.apiRequest("getApparentPropertiesOfType", { + const data = await this.client.apiRequest("getApparentPropertiesOfType", { snapshot: this.snapshotId, project: this.project.id, objectId: source.id, @@ -672,7 +724,7 @@ class ProjectObjectRegistry { } async fetchPropertyOfType(source: Type, name: string): Promise { - const data = await this.client.apiRequest("getPropertyOfType", { + const data = await this.client.apiRequest("getPropertyOfType", { snapshot: this.snapshotId, project: this.project.id, type: source.id, @@ -682,7 +734,7 @@ class ProjectObjectRegistry { } async fetchSignaturesOfType(source: Type, kind: SignatureKind): Promise { - const data = await this.client.apiRequest("getSignaturesOfType", { + const data = await this.client.apiRequest("getSignaturesOfType", { snapshot: this.snapshotId, project: this.project.id, type: source.id, @@ -692,7 +744,7 @@ class ProjectObjectRegistry { } async fetchIndexInfosOfType(source: Type): Promise { - const data = await this.client.apiRequest("getIndexInfosOfType", { + const data = await this.client.apiRequest("getIndexInfosOfType", { snapshot: this.snapshotId, project: this.project.id, type: source.id, @@ -707,7 +759,7 @@ class ProjectObjectRegistry { } async fetchTypeParameterAtPosition(source: Signature, pos: number): Promise { - const data = await this.client.apiRequest("getTypeParameterAtPosition", { + const data = await this.client.apiRequest("getTypeParameterAtPosition", { snapshot: this.snapshotId, project: this.project.id, signature: source.id, @@ -741,8 +793,11 @@ export class Project { toPath: (fileName: string) => Path, snapshotRegistry: SnapshotObjectRegistry, ) { - this.id = data.id; + this.id = data.id as Path; this.configFileName = data.configFileName; + if (!data.parsedCommandLine?.options) { + throw new Error(`Project '${data.configFileName}' has no parsed command line`); + } this.parsedCommandLine = data.parsedCommandLine; this.compilerOptions = this.parsedCommandLine.options; this.rootFiles = this.parsedCommandLine.fileNames; @@ -767,7 +822,7 @@ export class Project { } /** @deprecated Use `languageService.getImportAdderEdits`. */ - getImportAdderEdits(file: DocumentIdentifier, actions: readonly ImportAdderAction[]): Promise { + getImportAdderEdits(file: DocumentIdentifier, actions: readonly APIImportAdderAction[]): Promise { return this.languageService.getImportAdderEdits(file, actions); } @@ -799,11 +854,11 @@ export class LanguageService { this.objectRegistry = objectRegistry; } - async getImportAdderEdits(file: DocumentIdentifier, actions: readonly ImportAdderAction[]): Promise { - const requestActions: ImportAdderActionRequest[] = actions.map(action => { + async getImportAdderEdits(file: DocumentIdentifier, actions: readonly APIImportAdderAction[]): Promise { + const requestActions: ImportAdderAction[] = actions.map(action => { switch (action.kind) { case "importSymbol": - const importSymbolAction: ImportSymbolActionRequest = { + const importSymbolAction: ImportAdderAction = { kind: "importSymbol", symbol: action.symbol.id, }; @@ -816,7 +871,7 @@ export class LanguageService { } }); - const data = await this.client.apiRequest("getImportAdderEdits", { + const data = await this.client.apiRequest("getImportAdderEdits", { snapshot: this.snapshotId, project: this.project.id, file, @@ -828,7 +883,7 @@ export class LanguageService { async getImportEditsForSymbols(file: DocumentIdentifier, symbols: readonly Symbol[], options: GetImportEditsForSymbolsOptions = {}): Promise { return this.getImportAdderEdits( file, - symbols.map((symbol): ImportAdderAction => { + symbols.map((symbol): APIImportAdderAction => { if (options.isValidTypeOnlyUseSite !== undefined) { return { kind: "importSymbol", @@ -845,7 +900,7 @@ export class LanguageService { } async getReferencedSymbolsForNode(node: Node, position: number): Promise { - const data = await this.client.apiRequest<{ definition: string; symbol?: SymbolResponse; references: string[]; }[] | null>("getReferencedSymbolsForNode", { + const data = await this.client.apiRequest("getReferencedSymbolsForNode", { snapshot: this.snapshotId, project: this.project.id, node: getNodeId(node), @@ -859,7 +914,7 @@ export class LanguageService { } async getSignatureUsage(signatureDecl: Node): Promise { - const data = await this.client.apiRequest<{ name: string; call?: string; }[] | null>("getSignatureUsages", { + const data = await this.client.apiRequest("getSignatureUsages", { snapshot: this.snapshotId, project: this.project.id, signatureDecl: getNodeId(signatureDecl), @@ -871,13 +926,13 @@ export class LanguageService { } async getCompletionsAtPosition(document: string, position: number, options?: CompletionOptions): Promise { - const data = await this.client.apiRequest("getCompletionsAtPosition", { + const data = await this.client.apiRequest("getCompletionsAtPosition", { snapshot: this.snapshotId, project: this.project.id, file: document, position, - triggerCharacter: options?.triggerCharacter, - includeSymbol: options?.includeSymbol, + ...(options?.triggerCharacter !== undefined ? { triggerCharacter: options.triggerCharacter } : {}), + ...(options?.includeSymbol !== undefined ? { includeSymbol: options.includeSymbol } : {}), }); if (!data) return undefined; return { @@ -947,7 +1002,7 @@ export class Program { } async getSourceFileNames(): Promise { - const data = await this.client.apiRequest("getSourceFileNames", { + const data = await this.client.apiRequest("getSourceFileNames", { snapshot: this.snapshotId, project: this.project.id, }); @@ -979,7 +1034,7 @@ export class Program { } private async fetchSourceFileMetadata(path: Path): Promise { - const data = await this.client.apiRequest("getSourceFileMetadata", { + const data = await this.client.apiRequest("getSourceFileMetadata", { snapshot: this.snapshotId, project: this.project.id, file: path, @@ -1012,7 +1067,7 @@ export class Program { * Includes the root config file and any extended config files. */ async getConfigFileNames(): Promise { - const data = await this.client.apiRequest("getConfigFileNames", { + const data = await this.client.apiRequest("getConfigFileNames", { snapshot: this.snapshotId, project: this.project.id, }); @@ -1037,8 +1092,6 @@ export class Program { } /** - * Get syntactic (parse) diagnostics for a specific file or all files. - * @param file - Optional file to get diagnostics for. If omitted, returns diagnostics for all files. * Get syntactic (parse) diagnostics for specific files or all files. * @param file - Optional file(s) to get diagnostics for. If omitted, returns diagnostics for all files. */ @@ -1046,10 +1099,10 @@ export class Program { const files = file === undefined ? undefined : Array.isArray(file) ? file : [file]; - const data = await this.client.apiRequest("getSyntacticDiagnostics", { + const data = await this.client.apiRequest("getSyntacticDiagnostics", { snapshot: this.snapshotId, project: this.project.id, - files, + ...(files !== undefined ? { files } : {}), }); return data ?? []; } @@ -1062,10 +1115,10 @@ export class Program { const files = file === undefined ? undefined : Array.isArray(file) ? file : [file]; - const data = await this.client.apiRequest("getBindDiagnostics", { + const data = await this.client.apiRequest("getBindDiagnostics", { snapshot: this.snapshotId, project: this.project.id, - files, + ...(files !== undefined ? { files } : {}), }); return data ?? []; } @@ -1078,10 +1131,10 @@ export class Program { const files = file === undefined ? undefined : Array.isArray(file) ? file : [file]; - const data = await this.client.apiRequest("getSemanticDiagnostics", { + const data = await this.client.apiRequest("getSemanticDiagnostics", { snapshot: this.snapshotId, project: this.project.id, - files, + ...(files !== undefined ? { files } : {}), }); return data ?? []; } @@ -1094,10 +1147,10 @@ export class Program { const files = file === undefined ? undefined : Array.isArray(file) ? file : [file]; - const data = await this.client.apiRequest("getSuggestionDiagnostics", { + const data = await this.client.apiRequest("getSuggestionDiagnostics", { snapshot: this.snapshotId, project: this.project.id, - files, + ...(files !== undefined ? { files } : {}), }); return data ?? []; } @@ -1110,10 +1163,10 @@ export class Program { const files = file === undefined ? undefined : Array.isArray(file) ? file : [file]; - const data = await this.client.apiRequest("getDeclarationDiagnostics", { + const data = await this.client.apiRequest("getDeclarationDiagnostics", { snapshot: this.snapshotId, project: this.project.id, - files, + ...(files !== undefined ? { files } : {}), }); return data ?? []; } @@ -1122,7 +1175,7 @@ export class Program { * Get program-wide diagnostics for the project, including compiler options diagnostics. */ async getProgramDiagnostics(): Promise { - const data = await this.client.apiRequest("getProgramDiagnostics", { + const data = await this.client.apiRequest("getProgramDiagnostics", { snapshot: this.snapshotId, project: this.project.id, }); @@ -1133,7 +1186,7 @@ export class Program { * Get global (non-file-specific) semantic diagnostics for the project. */ async getGlobalDiagnostics(): Promise { - const data = await this.client.apiRequest("getGlobalDiagnostics", { + const data = await this.client.apiRequest("getGlobalDiagnostics", { snapshot: this.snapshotId, project: this.project.id, }); @@ -1144,7 +1197,7 @@ export class Program { * Get config file parsing diagnostics for the project. */ async getConfigFileParsingDiagnostics(): Promise { - const data = await this.client.apiRequest("getConfigFileParsingDiagnostics", { + const data = await this.client.apiRequest("getConfigFileParsingDiagnostics", { snapshot: this.snapshotId, project: this.project.id, }); @@ -1158,21 +1211,26 @@ export class Program { * is written there. Otherwise, the server writes directly to the host filesystem. */ async emit(emitOnly?: EmitOnly): Promise { - return this.client.apiRequest("emit", { + const response = await this.client.apiRequest("emit", { snapshot: this.snapshotId, project: this.project.id, - emitOnly, + ...(emitOnly !== undefined ? { emitOnly } : {}), }); + return { + emitSkipped: response.emitSkipped, + diagnostics: response.diagnostics, + emittedFiles: response.emittedFiles, + }; } /** * Emits files and returns their contents without writing to the filesystem. */ async emitToString(emitOnly?: EmitOnly): Promise { - const response = await this.client.apiRequest("emitToString", { + const response = await this.client.apiRequest("emitToString", { snapshot: this.snapshotId, project: this.project.id, - emitOnly, + ...(emitOnly !== undefined ? { emitOnly } : {}), }); return toEmitOutput(response); } @@ -1181,7 +1239,7 @@ export class Program { * Gets JavaScript output for selected files regardless of project `noEmit`, `emitDeclarationOnly`, and `noEmitOnError` settings. */ async getJavaScriptEmit(files: readonly DocumentIdentifier[]): Promise { - const response = await this.client.apiRequest("getJavaScriptEmit", { + const response = await this.client.apiRequest("getJavaScriptEmit", { snapshot: this.snapshotId, project: this.project.id, files, @@ -1193,7 +1251,7 @@ export class Program { * Gets declaration output for selected files regardless of project `noEmit`, `declaration`, `emitDeclarationOnly`, and `noEmitOnError` settings. */ async getDeclarationEmit(files: readonly DocumentIdentifier[]): Promise { - const response = await this.client.apiRequest("getDeclarationEmit", { + const response = await this.client.apiRequest("getDeclarationEmit", { snapshot: this.snapshotId, project: this.project.id, files, @@ -1202,7 +1260,7 @@ export class Program { } } -function toEmitOutput(response: EmitOutputResponse): EmitOutput { +function toEmitOutput(response: ProtocolEmitOutputResponse): EmitOutput { const outputFiles = new Map(); for (const { fileName, ...outputFile } of response.outputFiles) { outputFiles.set(fileName, outputFile); @@ -1242,14 +1300,14 @@ 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<(SymbolResponse | null)[]>("getSymbolsAtLocations", { + 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 data = await this.client.apiRequest("getSymbolAtLocation", { + const data = await this.client.apiRequest("getSymbolAtLocation", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(nodeOrNodes as Node), @@ -1261,7 +1319,7 @@ export class Checker { getSymbolAtPosition(file: DocumentIdentifier, positions: readonly number[]): Promise<(Symbol | undefined)[]>; async getSymbolAtPosition(file: DocumentIdentifier, positionOrPositions: number | readonly number[]): Promise { if (typeof positionOrPositions === "number") { - const data = await this.client.apiRequest("getSymbolAtPosition", { + const data = await this.client.apiRequest("getSymbolAtPosition", { snapshot: this.snapshotId, project: this.project.id, file, @@ -1269,7 +1327,7 @@ export class Checker { }); return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; } - const data = await this.client.apiRequest<(SymbolResponse | null)[]>("getSymbolsAtPositions", { + const data = await this.client.apiRequest("getSymbolsAtPositions", { snapshot: this.snapshotId, project: this.project.id, file, @@ -1282,14 +1340,14 @@ export class Checker { getSymbolOfSourceFile(files: readonly DocumentIdentifier[]): Promise<(Symbol | undefined)[]>; async getSymbolOfSourceFile(fileOrFiles: DocumentIdentifier | readonly DocumentIdentifier[]): Promise { if (Array.isArray(fileOrFiles)) { - const data = await this.client.apiRequest<(SymbolResponse | null)[]>("getSymbolsOfSourceFiles", { + 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 data = await this.client.apiRequest("getSymbolOfSourceFile", { + const data = await this.client.apiRequest("getSymbolOfSourceFile", { snapshot: this.snapshotId, project: this.project.id, file: fileOrFiles as DocumentIdentifier, @@ -1306,14 +1364,14 @@ 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", { + 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 data = await this.client.apiRequest("getTypeOfSymbol", { + const data = await this.client.apiRequest("getTypeOfSymbol", { snapshot: this.snapshotId, project: this.project.id, symbol: (symbolOrSymbols as Symbol).id, @@ -1327,7 +1385,7 @@ export class Checker { * {@link Type.isErrorType} to detect it). */ async getDeclaredTypeOfSymbol(symbol: Symbol): Promise { - const data = await this.client.apiRequest("getDeclaredTypeOfSymbol", { + const data = await this.client.apiRequest("getDeclaredTypeOfSymbol", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, @@ -1336,7 +1394,7 @@ export class Checker { } async getReferencesToSymbolInFile(file: DocumentIdentifier, symbol: Symbol): Promise { - const data = await this.client.apiRequest("getReferencesToSymbolInFile", { + const data = await this.client.apiRequest("getReferencesToSymbolInFile", { snapshot: this.snapshotId, project: this.project.id, file, @@ -1369,14 +1427,14 @@ 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", { + 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 data = await this.client.apiRequest("getTypeAtLocation", { + const data = await this.client.apiRequest("getTypeAtLocation", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(nodeOrNodes as Node), @@ -1394,7 +1452,7 @@ export class Checker { * signature (use {@link Checker.isUnknownSignature} to detect it). */ async getResolvedSignature(node: Node): Promise { - const data = await this.client.apiRequest("getResolvedSignature", { + const data = await this.client.apiRequest("getResolvedSignature", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), @@ -1406,7 +1464,7 @@ export class Checker { getTypeAtPosition(file: DocumentIdentifier, positions: readonly number[]): Promise<(Type | undefined)[]>; async getTypeAtPosition(file: DocumentIdentifier, positionOrPositions: number | readonly number[]): Promise { if (typeof positionOrPositions === "number") { - const data = await this.client.apiRequest("getTypeAtPosition", { + const data = await this.client.apiRequest("getTypeAtPosition", { snapshot: this.snapshotId, project: this.project.id, file, @@ -1414,7 +1472,7 @@ export class Checker { }); return data ? this.objectRegistry.getOrCreateType(data) : undefined; } - const data = await this.client.apiRequest<(TypeResponse | null)[]>("getTypesAtPositions", { + const data = await this.client.apiRequest("getTypesAtPositions", { snapshot: this.snapshotId, project: this.project.id, file, @@ -1431,15 +1489,19 @@ export class Checker { ): Promise { // Distinguish Node (has `kind`) from DocumentPosition (has `document` and `position`) const isNode = location && "kind" in location; - const data = await this.client.apiRequest("resolveName", { + const data = await this.client.apiRequest("resolveName", { snapshot: this.snapshotId, project: this.project.id, name, meaning, - location: isNode ? getNodeId(location as Node) : undefined, - file: !isNode && location ? (location as DocumentPosition).document : undefined, - position: !isNode && location ? (location as DocumentPosition).position : undefined, - excludeGlobals, + ...(isNode ? { location: getNodeId(location as Node) } : {}), + ...(!isNode && location + ? { + file: (location as DocumentPosition).document, + position: (location as DocumentPosition).position, + } + : {}), + ...(excludeGlobals !== undefined ? { excludeGlobals } : {}), }); return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; } @@ -1450,13 +1512,16 @@ export class Checker { async getSymbolsInScope(location: Node | DocumentPosition, meaning: SymbolFlags): Promise { // Distinguish Node (has `kind`) from DocumentPosition (has `document` and `position`) const isNode = "kind" in location; - const data = await this.client.apiRequest("getSymbolsInScope", { + const data = await this.client.apiRequest("getSymbolsInScope", { snapshot: this.snapshotId, project: this.project.id, meaning, - location: isNode ? getNodeId(location as Node) : undefined, - file: isNode ? undefined : (location as DocumentPosition).document, - position: isNode ? undefined : (location as DocumentPosition).position, + ...(isNode + ? { location: getNodeId(location as Node) } + : { + file: (location as DocumentPosition).document, + position: (location as DocumentPosition).position, + }), }); return data ? data.map(d => this.objectRegistry.getOrCreateSymbol(d)) : []; } @@ -1468,7 +1533,7 @@ export class Checker { } async getContextualType(node: Expression): Promise { - const data = await this.client.apiRequest("getContextualType", { + const data = await this.client.apiRequest("getContextualType", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), @@ -1478,7 +1543,7 @@ export class Checker { /** Get the base type of a literal type (e.g. `number` for `42`). Always returns a type. */ async getBaseTypeOfLiteralType(type: Type): Promise { - const data = await this.client.apiRequest("getBaseTypeOfLiteralType", { + const data = await this.client.apiRequest("getBaseTypeOfLiteralType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, @@ -1497,7 +1562,7 @@ export class Checker { * {@link Type.isErrorType} to detect it). */ async getTypeFromTypeNode(node: TypeNode): Promise { - const data = await this.client.apiRequest("getTypeFromTypeNode", { + const data = await this.client.apiRequest("getTypeFromTypeNode", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), @@ -1507,7 +1572,7 @@ export class Checker { /** Get the widened type. Always returns a type. */ async getWidenedType(type: Type): Promise { - const data = await this.client.apiRequest("getWidenedType", { + const data = await this.client.apiRequest("getWidenedType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, @@ -1520,7 +1585,7 @@ export class Checker { * returns a type; an out-of-range index yields the `any` type. */ async getParameterType(signature: Signature, index: number): Promise { - const data = await this.client.apiRequest("getParameterType", { + const data = await this.client.apiRequest("getParameterType", { snapshot: this.snapshotId, project: this.project.id, signature: signature.id, @@ -1530,7 +1595,7 @@ export class Checker { } async isArrayLikeType(type: Type): Promise { - return this.client.apiRequest("isArrayLikeType", { + return this.client.apiRequest("isArrayLikeType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, @@ -1538,7 +1603,7 @@ export class Checker { } async isTypeAssignableTo(source: Type, target: Type): Promise { - return this.client.apiRequest("isTypeAssignableTo", { + return this.client.apiRequest("isTypeAssignableTo", { snapshot: this.snapshotId, project: this.project.id, source: source.id, @@ -1547,7 +1612,7 @@ export class Checker { } async getShorthandAssignmentValueSymbol(node: Node): Promise { - const data = await this.client.apiRequest("getShorthandAssignmentValueSymbol", { + const data = await this.client.apiRequest("getShorthandAssignmentValueSymbol", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), @@ -1561,7 +1626,7 @@ export class Checker { * error type (use {@link Type.isErrorType} to detect it). */ async getTypeOfSymbolAtLocation(symbol: Symbol, location: Node): Promise { - const data = await this.client.apiRequest("getTypeOfSymbolAtLocation", { + const data = await this.client.apiRequest("getTypeOfSymbolAtLocation", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, @@ -1570,8 +1635,8 @@ export class Checker { return this.objectRegistry.getOrCreateType(data); } - private async getIntrinsicType(method: string): Promise { - const data = await this.client.apiRequest(method, { + private async getIntrinsicType(method: IntrinsicTypeMethod): Promise { + const data = await this.client.apiRequest(method, { snapshot: this.snapshotId, project: this.project.id, }); @@ -1620,8 +1685,8 @@ export class Checker { snapshot: this.snapshotId, project: this.project.id, type: type.id, - location: enclosingDeclaration ? getNodeId(enclosingDeclaration) : undefined, - flags, + ...(enclosingDeclaration ? { location: getNodeId(enclosingDeclaration) } : {}), + ...(flags !== undefined ? { flags } : {}), }); if (!binaryData) return undefined; return decodeNode(binaryData) as TypeNode; @@ -1633,25 +1698,27 @@ export class Checker { project: this.project.id, signature: signature.id, kind, - location: enclosingDeclaration ? getNodeId(enclosingDeclaration) : undefined, - flags, + ...(enclosingDeclaration ? { location: getNodeId(enclosingDeclaration) } : {}), + ...(flags !== undefined ? { flags } : {}), }); if (!binaryData) return undefined; return decodeNode(binaryData) as Node; } async typeToString(type: Type, enclosingDeclaration?: Node, flags?: number): Promise { - return this.client.apiRequest("typeToString", { + const result = await this.client.apiRequest("typeToString", { snapshot: this.snapshotId, project: this.project.id, type: type.id, - location: enclosingDeclaration ? getNodeId(enclosingDeclaration) : undefined, - flags, + ...(enclosingDeclaration ? { location: getNodeId(enclosingDeclaration) } : {}), + ...(flags !== undefined ? { flags } : {}), }); + if (typeof result !== "string") throw new TypeError("typeToString returned a non-string result"); + return result; } async isContextSensitive(node: Node): Promise { - return this.client.apiRequest("isContextSensitive", { + return this.client.apiRequest("isContextSensitive", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), @@ -1659,7 +1726,7 @@ export class Checker { } async isArrayType(type: Type): Promise { - return this.client.apiRequest("isArrayType", { + return this.client.apiRequest("isArrayType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, @@ -1667,7 +1734,7 @@ export class Checker { } async isTupleType(type: Type): Promise { - return this.client.apiRequest("isTupleType", { + return this.client.apiRequest("isTupleType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, @@ -1684,7 +1751,7 @@ export class Checker { * no rest parameter yields the `any` type. */ async getRestTypeOfSignature(signature: Signature): Promise { - const data = await this.client.apiRequest("getRestTypeOfSignature", { + const data = await this.client.apiRequest("getRestTypeOfSignature", { snapshot: this.snapshotId, project: this.project.id, signature: signature.id, @@ -1693,7 +1760,7 @@ export class Checker { } async getTypePredicateOfSignature(signature: Signature): Promise { - const data = await this.client.apiRequest("getTypePredicateOfSignature", { + const data = await this.client.apiRequest("getTypePredicateOfSignature", { snapshot: this.snapshotId, project: this.project.id, signature: signature.id, @@ -1741,7 +1808,7 @@ export class Checker { } async getBaseConstraintOfType(type: Type): Promise { - const data = await this.client.apiRequest("getBaseConstraintOfType", { + const data = await this.client.apiRequest("getBaseConstraintOfType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, @@ -1750,7 +1817,7 @@ export class Checker { } async getPropertyOfType(type: Type, name: string): Promise { - const data = await this.client.apiRequest("getPropertyOfType", { + const data = await this.client.apiRequest("getPropertyOfType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, @@ -1760,17 +1827,17 @@ export class Checker { } async getConstantValue(node: Node): Promise { - const data = await this.client.apiRequest("getConstantValue", { + const data = await this.client.apiRequest("getConstantValue", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), }); - return data ?? undefined; + return typeof data === "string" || typeof data === "number" ? data : undefined; } /** Get the signature of a function-like declaration. Always returns a signature. */ async getSignatureFromDeclaration(node: Node): Promise { - const data = await this.client.apiRequest("getSignatureFromDeclaration", { + const data = await this.client.apiRequest("getSignatureFromDeclaration", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), @@ -1779,7 +1846,7 @@ export class Checker { } async getExportSpecifierLocalTargetSymbol(node: Node): Promise { - const data = await this.client.apiRequest("getExportSpecifierLocalTargetSymbol", { + const data = await this.client.apiRequest("getExportSpecifierLocalTargetSymbol", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), @@ -1793,7 +1860,7 @@ export class Checker { * {@link Checker.isUnknownSymbol} to detect it). */ async getAliasedSymbol(symbol: Symbol): Promise { - const data = await this.client.apiRequest("getAliasedSymbol", { + const data = await this.client.apiRequest("getAliasedSymbol", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, @@ -1806,7 +1873,7 @@ export class Checker { * (e.g. `"/path/to/module".Namespace.Name`). */ async getFullyQualifiedName(symbol: Symbol): Promise { - return this.client.apiRequest("getFullyQualifiedName", { + return this.client.apiRequest("getFullyQualifiedName", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, @@ -1814,7 +1881,7 @@ export class Checker { } async getImmediateAliasedSymbol(symbol: Symbol): Promise { - const data = await this.client.apiRequest("getImmediateAliasedSymbol", { + const data = await this.client.apiRequest("getImmediateAliasedSymbol", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, @@ -1829,7 +1896,7 @@ export class Checker { * the first call. */ private getWellKnownSymbols(): Promise<{ unknown: number; undefined: number; arguments: number; }> { - return this.wellKnownSymbols ??= this.client.apiRequest<{ unknown: number; undefined: number; arguments: number; }>("getWellKnownSymbols", { + return this.wellKnownSymbols ??= this.client.apiRequest("getWellKnownSymbols", { snapshot: this.snapshotId, project: this.project.id, }); @@ -1863,7 +1930,7 @@ export class Checker { * identity checks against it are local after the first call. */ private getWellKnownSignatures(): Promise<{ unknown: number; }> { - return this.wellKnownSignatures ??= this.client.apiRequest<{ unknown: number; }>("getWellKnownSignatures", { + return this.wellKnownSignatures ??= this.client.apiRequest("getWellKnownSignatures", { snapshot: this.snapshotId, project: this.project.id, }); @@ -1879,7 +1946,7 @@ export class Checker { } async getExportsOfModule(symbol: Symbol): Promise { - const data = await this.client.apiRequest("getExportsOfModule", { + const data = await this.client.apiRequest("getExportsOfModule", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, @@ -1888,7 +1955,7 @@ export class Checker { } async getMemberInModuleExports(symbol: Symbol, name: string): Promise { - const data = await this.client.apiRequest("getMemberInModuleExports", { + const data = await this.client.apiRequest("getMemberInModuleExports", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, @@ -1898,7 +1965,7 @@ export class Checker { } async getJsDocTagsOfSymbol(symbol: Symbol): Promise { - const data = await this.client.apiRequest("getJsDocTags", { + const data = await this.client.apiRequest("getJsDocTags", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, @@ -1907,7 +1974,7 @@ export class Checker { } async getDocumentationCommentOfSymbol(symbol: Symbol): Promise { - return this.client.apiRequest("getDocumentationComment", { + return this.client.apiRequest("getDocumentationComment", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, @@ -1918,7 +1985,7 @@ export class Checker { * Get the type arguments of a type reference (e.g. the `string` in `Array`). */ async getTypeArguments(type: TypeReference): Promise { - const data = await this.client.apiRequest("getTypeArguments", { + const data = await this.client.apiRequest("getTypeArguments", { snapshot: this.snapshotId, project: this.project.id, type: type.id, @@ -1943,9 +2010,11 @@ export class Emitter { async printNode(node: Node, options: PrintNodeOptions = {}): Promise { const encoded = encodeNode(node); const base64 = uint8ArrayToBase64(encoded); - return this.client.apiRequest("printNode", { + return this.client.apiRequest("printNode", { data: base64, - ...options, + ...(options.preserveSourceNewlines !== undefined ? { preserveSourceNewlines: options.preserveSourceNewlines } : {}), + ...(options.neverAsciiEscape !== undefined ? { neverAsciiEscape: options.neverAsciiEscape } : {}), + ...(options.terminateUnterminatedLiterals !== undefined ? { terminateUnterminatedLiterals: options.terminateUnterminatedLiterals } : {}), }); } } @@ -1969,7 +2038,7 @@ export class SnapshotInternalAPI { * @returns The formatted text of the node, indented for the insertion position. */ async formatNodeForInsertion(node: Node, file: DocumentIdentifier, position: number): Promise { - const data = await this.client.apiRequest("getDefaultProjectForFile", { + const data = await this.client.apiRequest("getDefaultProjectForFile", { snapshot: this.snapshotId, file, }); @@ -1979,7 +2048,7 @@ export class SnapshotInternalAPI { const encoded = encodeNode(node); const base64 = uint8ArrayToBase64(encoded); - return this.client.apiRequest("formatNodeForInsertion", { + return this.client.apiRequest("formatNodeForInsertion", { snapshot: this.snapshotId, project: data.id, file, @@ -2067,11 +2136,11 @@ export class Symbol { this.objectRegistry = objectRegistry; this.id = data.id; - this.escapedName = data.name; - this.name = unescapeLeadingUnderscores(data.name); + this.escapedName = data.name as __String; + this.name = unescapeLeadingUnderscores(data.name as __String); this.flags = data.flags; this.checkFlags = data.checkFlags; - const canonicalProject = objectRegistry.getProject(data.project); + const canonicalProject = objectRegistry.getProject(data.project as Path); if (!canonicalProject) { throw new Error(`Symbol ${data.id} references unknown canonical project '${data.project}'`); } @@ -2103,7 +2172,7 @@ export class Symbol { return this.exportsCache ??= this.fetchSymbolTable("getExportsOfSymbol"); } - private async fetchSymbolTable(method: string): Promise> { + private async fetchSymbolTable(method: SymbolsPropertyMethod): Promise> { const symbols = await this.objectRegistry.fetchSymbols(this, method, undefined, this.canonicalProject.id); const table = new Map<__String, Symbol>(); for (const symbol of symbols) { @@ -2187,7 +2256,8 @@ class TypeObject implements Type { if (data.value != null) { // BigInt literal values are serialized as decimal strings (e.g. "-123") because // JSON cannot represent bigint. Decode them back into a real bigint here. - this.value = (data.flags & TypeFlags.BigIntLiteral) ? BigInt(data.value) : data.value; + const value = data.value as string | number | boolean; + this.value = (data.flags & TypeFlags.BigIntLiteral) ? BigInt(value as string) : value; } if (data.intrinsicName !== undefined) this.intrinsicName = data.intrinsicName; if (data.isThisType !== undefined) this.isThisType = data.isThisType; diff --git a/_packages/native-preview/src/api/async/client.ts b/_packages/native-preview/src/api/async/client.ts index cdcc2343f8e..11eecc0b73b 100644 --- a/_packages/native-preview/src/api/async/client.ts +++ b/_packages/native-preview/src/api/async/client.ts @@ -21,6 +21,10 @@ import { isSpawnOptions, resolveExePath, } from "../options.ts"; +import type { + APIMethodInfo, + SourceFileResponseMethod, +} from "../proto.ts"; import { combineTimingInfo, disabledServerTimingInfo, @@ -154,7 +158,7 @@ export class Client { } } - async apiRequest(method: string, params?: unknown): Promise { + async apiRequest(method: K, params: APIMethodInfo[K]["params"]): Promise { if (!this.connected) { await this.connect(); } @@ -162,7 +166,7 @@ export class Client { throw new Error("Connection not established"); } - const requestType = new RequestType(method); + const requestType = new RequestType(method); if (!this.timing) { return this.connection.sendRequest(requestType, params); } @@ -186,8 +190,8 @@ export class Client { return result; } - async apiRequestBinary(method: string, params?: unknown): Promise { - const response = await this.apiRequest<{ data: string; } | null>(method, params); + async apiRequestBinary(method: K, params: APIMethodInfo[K]["params"]): Promise { + const response = await this.apiRequest(method, params); if (!response) return undefined; const buffer = Buffer.from(response.data, "base64"); return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength); diff --git a/_packages/native-preview/src/api/compilerOptions.ts b/_packages/native-preview/src/api/compilerOptions.ts deleted file mode 100644 index 8290b43b0d8..00000000000 --- a/_packages/native-preview/src/api/compilerOptions.ts +++ /dev/null @@ -1,112 +0,0 @@ -import type { JsxEmit } from "#enums/jsxEmit"; -import type { ModuleDetectionKind } from "#enums/moduleDetectionKind"; -import type { ModuleKind } from "#enums/moduleKind"; -import type { ModuleResolutionKind } from "#enums/moduleResolutionKind"; -import type { NewLineKind } from "#enums/newLineKind"; -import type { ScriptTarget } from "#enums/scriptTarget"; - -// Keep in sync with compileroptions.go, obviously - -export interface CompilerOptions { - allowJs?: boolean; - allowArbitraryExtensions?: boolean; - allowImportingTsExtensions?: boolean; - allowNonTsExtensions?: boolean; - allowUmdGlobalAccess?: boolean; - allowUnreachableCode?: boolean; - allowUnusedLabels?: boolean; - assumeChangesOnlyAffectDirectDependencies?: boolean; - checkJs?: boolean; - customConditions?: string[]; - composite?: boolean; - configFilePath?: string; - emitDeclarationOnly?: boolean; - emitBOM?: boolean; - emitDecoratorMetadata?: boolean; - declaration?: boolean; - declarationDir?: string; - declarationMap?: boolean; - deduplicatePackages?: boolean; - disableSizeLimit?: boolean; - disableSourceOfProjectReferenceRedirect?: boolean; - disableSolutionSearching?: boolean; - disableReferencedProjectLoad?: boolean; - erasableSyntaxOnly?: boolean; - exactOptionalPropertyTypes?: boolean; - experimentalDecorators?: boolean; - forceConsistentCasingInFileNames?: boolean; - isolatedModules?: boolean; - isolatedDeclarations?: boolean; - ignoreConfig?: boolean; - ignoreDeprecations?: string; - importHelpers?: boolean; - inlineSourceMap?: boolean; - inlineSources?: boolean; - init?: boolean; - incremental?: boolean; - jsx?: JsxEmit; - jsxFactory?: string; - jsxFragmentFactory?: string; - jsxImportSource?: string; - lib?: string[]; - libReplacement?: boolean; - locale?: string; - mapRoot?: string; - module?: ModuleKind; - moduleResolution?: ModuleResolutionKind; - moduleSuffixes?: string[]; - moduleDetection?: ModuleDetectionKind; - newLine?: NewLineKind; - noEmit?: boolean; - noCheck?: boolean; - noErrorTruncation?: boolean; - noFallthroughCasesInSwitch?: boolean; - noImplicitAny?: boolean; - noImplicitThis?: boolean; - noImplicitReturns?: boolean; - noEmitHelpers?: boolean; - noLib?: boolean; - noPropertyAccessFromIndexSignature?: boolean; - noUncheckedIndexedAccess?: boolean; - noEmitOnError?: boolean; - noUnusedLocals?: boolean; - noUnusedParameters?: boolean; - noResolve?: boolean; - noImplicitOverride?: boolean; - noUncheckedSideEffectImports?: boolean; - outDir?: string; - paths?: Record; - preserveConstEnums?: boolean; - preserveSymlinks?: boolean; - project?: string; - resolveJsonModule?: boolean; - resolvePackageJsonExports?: boolean; - resolvePackageJsonImports?: boolean; - removeComments?: boolean; - rewriteRelativeImportExtensions?: boolean; - reactNamespace?: string; - rootDir?: string; - rootDirs?: string[]; - skipLibCheck?: boolean; - stableTypeOrdering?: boolean; - strict?: boolean; - strictBindCallApply?: boolean; - strictBuiltinIteratorReturn?: boolean; - strictFunctionTypes?: boolean; - strictNullChecks?: boolean; - strictPropertyInitialization?: boolean; - stripInternal?: boolean; - skipDefaultLibCheck?: boolean; - sourceMap?: boolean; - sourceRoot?: string; - suppressOutputPathCheck?: boolean; - target?: ScriptTarget; - traceResolution?: boolean; - tsBuildInfoFile?: string; - typeRoots?: string[]; - types?: string[]; - useDefineForClassFields?: boolean; - useUnknownInCatchVariables?: boolean; - verbatimModuleSyntax?: boolean; - maxNodeModuleJsDepth?: number; -} diff --git a/_packages/native-preview/src/api/proto.generated.ts b/_packages/native-preview/src/api/proto.generated.ts new file mode 100644 index 00000000000..5e30d12bae2 --- /dev/null +++ b/_packages/native-preview/src/api/proto.generated.ts @@ -0,0 +1,1054 @@ +// Code generated by gen-proto; DO NOT EDIT. + +import type { JsxEmit } from "#enums/jsxEmit"; +import type { ModuleDetectionKind } from "#enums/moduleDetectionKind"; +import type { ModuleKind } from "#enums/moduleKind"; +import type { ModuleResolutionKind } from "#enums/moduleResolutionKind"; +import type { NewLineKind } from "#enums/newLineKind"; +import type { ScriptTarget } from "#enums/scriptTarget"; + +export type APIMethod = { params: TParams; result: TResult; }; + +export interface APIMethodInfo { + release: APIMethod; + initialize: APIMethod; + updateSnapshot: APIMethod; + updateTemporarySnapshot: APIMethod; + parseCommandLine: APIMethod; + readConfigFile: APIMethod; + parseJsonConfigFileContent: APIMethod; + parseConfigFile: APIMethod; + transpileModule: APIMethod; + transpileModuleFromFile: APIMethod; + transpileDeclaration: APIMethod; + transpileDeclarationFromFile: APIMethod; + getDefaultProjectForFile: APIMethod; + getSymbolAtPosition: APIMethod; + getSymbolsAtPositions: APIMethod; + getSymbolAtLocation: APIMethod; + getSymbolsAtLocations: APIMethod; + getSymbolOfSourceFile: APIMethod; + getSymbolsOfSourceFiles: APIMethod; + getTypeOfSymbol: APIMethod; + getTypesOfSymbols: APIMethod; + getDeclaredTypeOfSymbol: APIMethod; + getSourceFile: APIMethod; + getSourceFileNames: APIMethod; + getSourceFileMetadata: APIMethod; + getConfigFileNames: APIMethod; + getConfigSourceFile: APIMethod; + resolveName: APIMethod; + getSymbolsInScope: APIMethod; + getSignaturesOfType: APIMethod; + getResolvedSignature: APIMethod; + getTypeAtLocation: APIMethod; + getTypeAtLocations: APIMethod; + getTypeAtPosition: APIMethod; + getTypesAtPositions: APIMethod; + getParentOfSymbol: APIMethod; + getMembersOfSymbol: APIMethod; + getExportsOfSymbol: APIMethod; + getExportSymbolOfSymbol: APIMethod; + getSymbolOfType: APIMethod; + getTargetOfType: APIMethod; + getFreshTypeOfType: APIMethod; + getRegularTypeOfType: APIMethod; + getTypesOfType: APIMethod; + getTypeParametersOfType: APIMethod; + getOuterTypeParametersOfType: APIMethod; + getLocalTypeParametersOfType: APIMethod; + getAliasTypeArgumentsOfType: APIMethod; + getAliasSymbolOfType: APIMethod; + getObjectTypeOfType: APIMethod; + getIndexTypeOfType: APIMethod; + getCheckTypeOfType: APIMethod; + getExtendsTypeOfType: APIMethod; + getBaseTypeOfType: APIMethod; + getConstraintOfType: APIMethod; + getTypeParametersOfSignature: APIMethod; + getParametersOfSignature: APIMethod; + getThisParameterOfSignature: APIMethod; + getTargetOfSignature: APIMethod; + getContextualType: APIMethod; + getBaseTypeOfLiteralType: APIMethod; + getNonNullableType: APIMethod; + getTypeFromTypeNode: APIMethod; + getWidenedType: APIMethod; + getParameterType: APIMethod; + getTypeParameterAtPosition: APIMethod; + isArrayLikeType: APIMethod; + isTypeAssignableTo: APIMethod; + getShorthandAssignmentValueSymbol: APIMethod; + getTypeOfSymbolAtLocation: APIMethod; + typeToTypeNode: APIMethod; + signatureToSignatureDeclaration: APIMethod; + typeToString: APIMethod; + isContextSensitive: APIMethod; + getReturnTypeOfSignature: APIMethod; + getRestTypeOfSignature: APIMethod; + getTypePredicateOfSignature: APIMethod; + getBaseTypes: APIMethod; + getPropertiesOfType: APIMethod; + getApparentPropertiesOfType: APIMethod; + getApparentType: APIMethod; + getPropertyOfType: APIMethod; + getIndexInfosOfType: APIMethod; + getConstraintOfTypeParameter: APIMethod; + getDefaultFromTypeParameter: APIMethod; + getBaseConstraintOfType: APIMethod; + getTypeArguments: APIMethod; + getImportAdderEdits: APIMethod; + getTrueTypeOfConditionalType: APIMethod; + getFalseTypeOfConditionalType: APIMethod; + getConstantValue: APIMethod; + getSignatureFromDeclaration: APIMethod; + getExportSpecifierLocalTargetSymbol: APIMethod; + getAliasedSymbol: APIMethod; + getImmediateAliasedSymbol: APIMethod; + getFullyQualifiedName: APIMethod; + getExportsOfModule: APIMethod; + getMemberInModuleExports: APIMethod; + getJsDocTags: APIMethod; + getDocumentationComment: APIMethod; + isArrayType: APIMethod; + isTupleType: APIMethod; + getReferencesToSymbolInFile: APIMethod; + getReferencedSymbolsForNode: APIMethod; + getSignatureUsages: APIMethod; + getCompletionsAtPosition: APIMethod; + getSyntacticDiagnostics: APIMethod; + getBindDiagnostics: APIMethod; + getSemanticDiagnostics: APIMethod; + getSuggestionDiagnostics: APIMethod; + getDeclarationDiagnostics: APIMethod; + getProgramDiagnostics: APIMethod; + getGlobalDiagnostics: APIMethod; + getConfigFileParsingDiagnostics: APIMethod; + printNode: APIMethod; + formatNodeForInsertion: APIMethod; + emit: APIMethod; + emitToString: APIMethod; + getJavaScriptEmit: APIMethod; + getDeclarationEmit: APIMethod; + getAnyType: APIMethod; + getStringType: APIMethod; + getNumberType: APIMethod; + getBooleanType: APIMethod; + getVoidType: APIMethod; + getUndefinedType: APIMethod; + getNullType: APIMethod; + getNeverType: APIMethod; + getUnknownType: APIMethod; + getBigIntType: APIMethod; + getESSymbolType: APIMethod; + getNonPrimitiveType: APIMethod; + getWellKnownSymbols: APIMethod; + getWellKnownSignatures: APIMethod; + startCPUProfile: APIMethod; + stopCPUProfile: APIMethod; + saveHeapProfile: APIMethod; +} + +export type DocumentIdentifier = string | { uri: string; }; + +/** ReleaseParams are the parameters for the release method. */ +export interface ReleaseParams { + snapshot: number; +} + +/** InitializeResponse is returned by the initialize method. */ +export interface InitializeResponse { + /** UseCaseSensitiveFileNames indicates whether the host file system is case-sensitive. */ + useCaseSensitiveFileNames: boolean; + /** CurrentDirectory is the server's current working directory. */ + currentDirectory: string; +} + +/** + * UpdateSnapshotParams are the parameters for creating a new snapshot. + * All fields are optional. With no fields set, the server adopts the latest LSP state. + */ +export interface UpdateSnapshotParams { + /** + * OpenProjects lists tsconfig.json files to open/load in the new snapshot. + * Opens are ref-counted and persist across snapshots until closed. + */ + openProjects?: readonly DocumentIdentifier[]; + /** + * CloseProjects lists tsconfig.json files to release in the new snapshot. + * A project is only unloaded once every API client that opened it closes it. + */ + closeProjects?: readonly DocumentIdentifier[]; + /** FileChanges describes file system changes since the last snapshot. */ + fileChanges?: APIFileChanges; + /** + * OpenFiles lists files to keep open for the API client, mirroring LSP's + * textDocument/didOpen. For each file, ancestor directories are searched for a + * tsconfig that contains it; if found, that configured project is loaded and + * becomes the file's default project. Otherwise the file is loaded into the + * inferred project (e.g. a node_modules d.ts not in any project's import graph). + * Opens persist across snapshots until the file is closed. + */ + openFiles?: readonly DocumentIdentifier[]; + /** + * CloseFiles lists files to release in the new snapshot. A file is only fully + * closed once every API client that opened it closes it. + */ + closeFiles?: readonly DocumentIdentifier[]; +} + +/** UpdateSnapshotResponse is returned by updateSnapshot. */ +export interface UpdateSnapshotResponse { + /** Snapshot is the handle for the newly created snapshot. */ + snapshot: number; + /** Projects is the list of projects in the snapshot. */ + projects: ProjectResponse[]; + /** + * Changes describes source file differences from the previous snapshot. + * Nil for the first snapshot in a session. + */ + changes?: SnapshotChanges; +} + +/** + * UpdateTemporarySnapshotParams are the parameters for creating a temporary + * snapshot that overrides a single file's content. + */ +export interface UpdateTemporarySnapshotParams { + /** Snapshot is the current client snapshot on which to layer the temporary update. */ + snapshot: number; + /** File identifies the file whose content is temporarily overridden. */ + file: DocumentIdentifier; + /** NewText is the temporary content for the file. */ + newText: string; +} + +export interface ParseCommandLineParams { + commandLine: readonly string[] | null; +} + +export interface ConfigFileResponse { + fileNames: string[]; + options: CompilerOptions; + projectReferences?: ProjectReference[]; + typeAcquisition?: TypeAcquisition; + compileOnSave?: boolean; + raw?: unknown; + errors: DiagnosticResponse[]; +} + +export interface ReadConfigFileParams { + file: DocumentIdentifier; +} + +export interface ReadConfigFileResponse { + config: unknown; + error?: DiagnosticResponse; +} + +export interface ParseJsonConfigFileContentParams { + json: unknown; + configDirectory?: string; + configFileName?: DocumentIdentifier; +} + +export interface ParseConfigFileParams { + file: DocumentIdentifier; +} + +export interface TranspileParams { + input: string; + options: TranspileOptions; +} + +export interface TranspileOutputResponse { + outputText: string; + diagnostics?: DiagnosticResponse[]; + sourceMapText?: string; +} + +export interface TranspileFromFileParams { + fileName: string; + options: TranspileOptions; +} + +export interface GetDefaultProjectForFileParams { + snapshot: number; + file: DocumentIdentifier; +} + +export interface ProjectResponse { + id: string; + configFileName: string; + parsedCommandLine: ConfigFileResponse; + /** @deprecated Use parsedCommandLine.fileNames. */ + rootFiles: string[]; + /** @deprecated Use parsedCommandLine.options. */ + compilerOptions: CompilerOptions; +} + +export interface GetSymbolAtPositionParams { + snapshot: number; + project: string; + file: DocumentIdentifier; + position: number; +} + +export interface SymbolResponse { + id: number; + /** + * Project is the project in which the symbol was first observed. It is the + * default project for follow-up lookups whose results can vary by project. + */ + project: string; + name: string; + flags: number; + checkFlags: number; + declarations?: string[]; + valueDeclaration?: string; + parent?: number; + exportSymbol?: number; +} + +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; + symbol: number; +} + +export interface TypeResponse { + id: number; + flags: number; + objectFlags?: number; + /** + * Value is literal type data. BigInt literals are encoded as signed decimal + * strings because JSON cannot represent bigint; absent values are null. + */ + value: unknown; + /** ObjectType / TypeReference / StringMappingType / IndexType target */ + target?: number; + /** InterfaceType type parameters */ + typeParameters?: number[]; + outerTypeParameters?: number[]; + localTypeParameters?: number[]; + /** TupleType data */ + elementFlags?: number[]; + fixedLength?: number; + readonly?: boolean; + /** IndexedAccessType data */ + objectType?: number; + indexType?: number; + /** ConditionalType data */ + checkType?: number; + extendsType?: number; + /** SubstitutionType data */ + baseType?: number; + substConstraint?: number; + /** TemplateLiteralType text segments */ + texts?: string[]; + /** FreshableType data (LiteralType and computed enum types) */ + freshType?: number; + regularType?: number; + /** TypeParameter data */ + isThisType?: boolean; + /** IntrinsicType data */ + intrinsicName?: string; + /** TypeAlias data */ + aliasTypeArguments?: number[]; + aliasSymbol?: number; + /** Symbol associated with structured types */ + symbol?: number; +} + +export interface GetTypesOfSymbolsParams { + snapshot: number; + project: string; + symbols: readonly number[] | null; +} + +export interface GetSourceFileParams { + snapshot: number; + project: string; + file: DocumentIdentifier; +} + +/** + * SourceFileResponse contains the binary-encoded AST data for a source file. + * The Data field is base64-encoded binary data in the encoder's format. + */ +export interface SourceFileResponse { + /** Data is the base64-encoded binary AST data in the encoder's format. */ + data: string; +} + +export interface GetSourceFileNamesParams { + snapshot: number; + project: string; +} + +/** SourceFileMetadata carries program-stored metadata about a single source file. */ +export interface SourceFileMetadata { + isDefaultLibrary: boolean; + isFromExternalLibrary: boolean; + packageJsonType: string; + packageJsonDirectory: string; + impliedNodeFormat: ModuleKind; +} + +/** GetProjectDiagnosticsParams are parameters for project-wide diagnostic methods. */ +export interface GetProjectDiagnosticsParams { + snapshot: number; + project: string; +} + +export interface ResolveNameParams { + snapshot: number; + project: string; + name: string; + /** Optional: node handle for location context */ + location?: string; + /** Optional: file for location context (alternative to Location) */ + file?: DocumentIdentifier; + /** Optional: position in file for location context (with File) */ + position?: number; + /** SymbolFlags for what kind of symbol to find */ + meaning: number; + /** Whether to exclude global symbols */ + excludeGlobals?: boolean; +} + +/** + * GetSymbolsInScopeParams are parameters for getSymbolsInScope, which returns + * all symbols visible at a given location. + */ +export interface GetSymbolsInScopeParams { + snapshot: number; + project: string; + /** Optional: node handle for location context */ + location?: string; + /** Optional: file for location context (alternative to Location) */ + file?: DocumentIdentifier; + /** Optional: position in file for location context (with File) */ + position?: number; + /** SymbolFlags for what kind of symbols to find */ + meaning: number; +} + +export interface GetSignaturesOfTypeParams { + snapshot: number; + project: string; + type: number; + kind: number; +} + +export interface SignatureResponse { + id: number; + flags: number; + declaration?: string; + typeParameters?: number[]; + parameters?: number[]; + thisParameter?: number; + target?: number; +} + +export interface GetResolvedSignatureParams { + snapshot: number; + project: string; + location: string; +} + +export interface GetTypeAtLocationParams { + snapshot: number; + project: string; + location: string; +} + +export interface GetTypeAtLocationsParams { + snapshot: number; + project: string; + locations: readonly string[] | null; +} + +export interface GetTypeAtPositionParams { + snapshot: number; + project: string; + file: DocumentIdentifier; + 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; + project: string; + objectId: number; +} + +/** GetTypePropertyParams is used for all type sub-property endpoints. */ +export interface GetTypePropertyParams { + snapshot: number; + project: string; + objectId: number; +} + +/** GetSignaturePropertyParams is used for all signature sub-property endpoints. */ +export interface GetSignaturePropertyParams { + snapshot: number; + project: string; + objectId: number; +} + +/** GetContextualTypeParams returns the contextual type for a node. */ +export interface GetContextualTypeParams { + snapshot: number; + project: string; + location: string; +} + +/** GetBaseTypeOfLiteralTypeParams returns the base type of a literal type. */ +export interface GetBaseTypeOfLiteralTypeParams { + snapshot: number; + project: string; + type: number; +} + +/** GetTypeFromTypeNodeParams are the parameters for the getTypeFromTypeNode method. */ +export interface GetTypeFromTypeNodeParams { + snapshot: number; + project: string; + location: string; +} + +/** GetWidenedTypeParams are the parameters for the getWidenedType method. */ +export interface GetWidenedTypeParams { + snapshot: number; + project: string; + type: number; +} + +/** GetParameterTypeParams are the parameters for the getParameterType method. */ +export interface GetParameterTypeParams { + snapshot: number; + project: string; + signature: number; + index: number; +} + +/** IsArrayLikeTypeParams checks whether a type is array-like. */ +export interface IsArrayLikeTypeParams { + snapshot: number; + project: string; + type: number; +} + +/** IsTypeAssignableToParams checks assignability between two types. */ +export interface IsTypeAssignableToParams { + snapshot: number; + project: string; + source: number; + target: number; +} + +/** GetTypeOfSymbolAtLocationParams returns the narrowed type of a symbol at a specific location. */ +export interface GetTypeOfSymbolAtLocationParams { + snapshot: number; + project: string; + symbol: number; + location: string; +} + +/** TypeToTypeNodeParams are the parameters for the typeToTypeNode method. */ +export interface TypeToTypeNodeParams { + snapshot: number; + project: string; + type: number; + location?: string; + flags?: number; +} + +/** SignatureToSignatureDeclarationParams are the parameters for the signatureToSignatureDeclaration method. */ +export interface SignatureToSignatureDeclarationParams { + snapshot: number; + project: string; + signature: number; + kind: number; + location?: string; + flags?: number; +} + +/** CheckerSignatureParams are parameters for checker methods that operate on a signature. */ +export interface CheckerSignatureParams { + snapshot: number; + project: string; + signature: number; +} + +/** TypePredicateResponse is the response for getTypePredicateOfSignature. */ +export interface TypePredicateResponse { + kind: number; + parameterIndex: number; + parameterName?: string; + type?: TypeResponse; +} + +/** CheckerTypeParams are parameters for checker methods that operate on a type. */ +export interface CheckerTypeParams { + snapshot: number; + project: string; + type: number; +} + +/** GetPropertyOfTypeParams are parameters for getPropertyOfType (a named property of a type). */ +export interface GetPropertyOfTypeParams { + snapshot: number; + project: string; + type: number; + name: string; +} + +/** IndexInfoResponse represents a single index signature. */ +export interface IndexInfoResponse { + keyType: TypeResponse; + valueType: TypeResponse; + isReadonly?: boolean; + declaration?: string; +} + +export interface GetImportAdderEditsParams { + snapshot: number; + project: string; + file: DocumentIdentifier; + actions: readonly ImportAdderAction[] | null; +} + +export interface TextEdit { + pos: number; + end: number; + newText: string; +} + +/** CheckerNodeParams are parameters for checker methods that operate on a node location. */ +export interface CheckerNodeParams { + snapshot: number; + project: string; + location: string; +} + +/** CheckerSymbolParams are parameters for checker methods that operate on a symbol. */ +export interface CheckerSymbolParams { + snapshot: number; + project: string; + symbol: number; +} + +/** GetMemberInModuleExportsParams are parameters for getMemberInModuleExports. */ +export interface GetMemberInModuleExportsParams { + snapshot: number; + project: string; + symbol: number; + name: string; +} + +/** + * JSDocTagInfo is a single JSDoc tag, mirroring Strada's JSDocTagInfo but with the tag text + * rendered as a plain string rather than SymbolDisplayPart[]. + */ +export interface JSDocTagInfo { + name: string; + text?: string; +} + +/** GetReferencesToSymbolInFileParams are the parameters for the getReferencesToSymbolInFile method. */ +export interface GetReferencesToSymbolInFileParams { + snapshot: number; + project: string; + file: DocumentIdentifier; + symbol: number; +} + +/** GetReferencedSymbolsForNodeParams are the parameters for the getReferencedSymbolsForNode method. */ +export interface GetReferencedSymbolsForNodeParams { + snapshot: number; + project: string; + node: string; + position: number; +} + +/** ReferencedSymbolEntry represents a symbol definition and its references. */ +export interface ReferencedSymbolEntry { + definition: string; + symbol?: SymbolResponse; + references: string[]; +} + +/** GetSignatureUsagesParams are the parameters for the getSignatureUsages method. */ +export interface GetSignatureUsagesParams { + snapshot: number; + project: string; + signatureDecl: string; +} + +/** SignatureUsageResponse represents a single usage of a signature as a name-call pair. */ +export interface SignatureUsageResponse { + name: string; + call?: string; +} + +/** GetCompletionsAtPositionParams are the parameters for the getCompletionsAtPosition method. */ +export interface GetCompletionsAtPositionParams { + snapshot: number; + project: string; + file: DocumentIdentifier; + position: number; + triggerCharacter?: string; + includeSymbol?: boolean; +} + +/** CompletionInfoResponse wraps a list of completion entries. */ +export interface CompletionInfoResponse { + isIncomplete: boolean; + entries: CompletionEntryResponse[]; +} + +/** GetDiagnosticsParams are parameters for per-file diagnostic methods. */ +export interface GetDiagnosticsParams { + snapshot: number; + project: string; + files?: readonly DocumentIdentifier[]; +} + +/** DiagnosticResponse is the API response for a single diagnostic. */ +export interface DiagnosticResponse { + /** FileName is the path of the file this diagnostic belongs to, if any. */ + fileName?: string; + /** Pos is the start position of the diagnostic in the source file. */ + pos: number; + /** End is the end position of the diagnostic in the source file. */ + end: number; + /** Code is the diagnostic error code. */ + code: number; + /** Category is the diagnostic category (error, warning, suggestion, message). */ + category: number; + /** Text is the localized diagnostic message text. */ + text: string; + /** ReportsUnnecessary indicates this diagnostic highlights unnecessary code. */ + reportsUnnecessary?: boolean; + /** ReportsDeprecated indicates this diagnostic highlights deprecated code. */ + reportsDeprecated?: boolean; + /** MessageChain contains chained diagnostic messages, if any. */ + messageChain?: DiagnosticResponse[]; + /** RelatedInformation contains related diagnostic information, if any. */ + relatedInformation?: DiagnosticResponse[]; +} + +/** PrintNodeParams are the parameters for the printNode method. */ +export interface PrintNodeParams { + /** base64-encoded binary AST data */ + data: string; + preserveSourceNewlines?: boolean; + neverAsciiEscape?: boolean; + terminateUnterminatedLiterals?: boolean; +} + +/** FormatNodeForInsertionParams are the parameters for the formatNodeForInsertion method. */ +export interface FormatNodeForInsertionParams { + snapshot: number; + project: string; + /** target file where the node will be inserted */ + file: DocumentIdentifier; + /** UTF-16 code-unit offset of the insertion position in the target file */ + position: number; + /** base64-encoded binary AST data for the synthesized node */ + data: string; +} + +export interface EmitParams { + snapshot: number; + project: string; + emitOnly?: number; +} + +export interface EmitResponse { + emitSkipped: boolean; + diagnostics: DiagnosticResponse[]; + emittedFiles: string[]; +} + +export interface EmitOutputResponse { + emitSkipped: boolean; + diagnostics: DiagnosticResponse[]; + outputFiles: EmitOutputFile[]; +} + +export interface SelectedFilesEmitParams { + snapshot: number; + project: string; + files: readonly DocumentIdentifier[] | null; +} + +/** GetIntrinsicTypeParams is used for intrinsic type getters (anyType, stringType, etc.). */ +export interface GetIntrinsicTypeParams { + snapshot: number; + project: string; +} + +/** + * WellKnownSymbolsResponse carries the handle ids of the per-checker singleton + * symbols (unknown, undefined, arguments) so the client can identify them by id + * without a round-trip on every check. + */ +export interface WellKnownSymbolsResponse { + unknown: number; + undefined: number; + arguments: number; +} + +/** + * WellKnownSignaturesResponse carries the handle id of the per-checker singleton + * unknown signature (the signature the checker yields when a call cannot be + * resolved) so the client can identify it by id without a round-trip on every check. + */ +export interface WellKnownSignaturesResponse { + unknown: number; +} + +export interface ProfileParams { + dir: string; +} + +export interface ProfileResult { + file: string; +} + +/** + * APIFileChanges describes file changes to apply when updating a snapshot. + * Either InvalidateAll is true (discard all caches) or Changed/Created/Deleted + * list individual documents. + */ +export interface APIFileChanges { + invalidateAll?: boolean; + changed?: DocumentIdentifier[]; + created?: DocumentIdentifier[]; + deleted?: DocumentIdentifier[]; +} + +/** + * SnapshotChanges describes what changed between the previous latest snapshot + * and the newly created snapshot. Changes are reported per-project so clients + * can track cache refs at the (snapshot, project) level. + */ +export interface SnapshotChanges { + /** + * ChangedProjects maps project handles to the file changes within that project. + * Projects not listed here (and not in RemovedProjects) are unchanged. + */ + changedProjects?: Record; + /** + * RemovedProjects lists project handles that were present in the previous + * snapshot but absent from the new one. + */ + removedProjects?: string[]; +} + +/** CompilerOptions contains the compiler options exposed by the API. */ +export interface CompilerOptions { + allowJs?: boolean; + allowArbitraryExtensions?: boolean; + allowImportingTsExtensions?: boolean; + allowNonTsExtensions?: boolean; + allowUmdGlobalAccess?: boolean; + allowUnreachableCode?: boolean; + allowUnusedLabels?: boolean; + assumeChangesOnlyAffectDirectDependencies?: boolean; + checkJs?: boolean; + customConditions?: string[]; + composite?: boolean; + emitDeclarationOnly?: boolean; + emitBOM?: boolean; + emitDecoratorMetadata?: boolean; + declaration?: boolean; + declarationDir?: string; + declarationMap?: boolean; + deduplicatePackages?: boolean; + disableSizeLimit?: boolean; + disableSourceOfProjectReferenceRedirect?: boolean; + disableSolutionSearching?: boolean; + disableReferencedProjectLoad?: boolean; + erasableSyntaxOnly?: boolean; + exactOptionalPropertyTypes?: boolean; + experimentalDecorators?: boolean; + forceConsistentCasingInFileNames?: boolean; + isolatedModules?: boolean; + isolatedDeclarations?: boolean; + ignoreConfig?: boolean; + ignoreDeprecations?: string; + importHelpers?: boolean; + inlineSourceMap?: boolean; + inlineSources?: boolean; + init?: boolean; + incremental?: boolean; + jsx?: JsxEmit; + jsxFactory?: string; + jsxFragmentFactory?: string; + jsxImportSource?: string; + lib?: string[]; + libReplacement?: boolean; + locale?: string; + mapRoot?: string; + module?: ModuleKind; + moduleResolution?: ModuleResolutionKind; + moduleSuffixes?: string[]; + moduleDetection?: ModuleDetectionKind; + newLine?: NewLineKind; + noEmit?: boolean; + noCheck?: boolean; + noErrorTruncation?: boolean; + noFallthroughCasesInSwitch?: boolean; + noImplicitAny?: boolean; + noImplicitThis?: boolean; + noImplicitReturns?: boolean; + noEmitHelpers?: boolean; + noLib?: boolean; + noPropertyAccessFromIndexSignature?: boolean; + noUncheckedIndexedAccess?: boolean; + noEmitOnError?: boolean; + noUnusedLocals?: boolean; + noUnusedParameters?: boolean; + noResolve?: boolean; + noImplicitOverride?: boolean; + noUncheckedSideEffectImports?: boolean; + outDir?: string; + paths?: Record; + preserveConstEnums?: boolean; + preserveSymlinks?: boolean; + project?: string; + resolveJsonModule?: boolean; + resolvePackageJsonExports?: boolean; + resolvePackageJsonImports?: boolean; + removeComments?: boolean; + rewriteRelativeImportExtensions?: boolean; + reactNamespace?: string; + rootDir?: string; + rootDirs?: string[]; + skipLibCheck?: boolean; + stableTypeOrdering?: boolean; + strict?: boolean; + strictBindCallApply?: boolean; + strictBuiltinIteratorReturn?: boolean; + strictFunctionTypes?: boolean; + strictNullChecks?: boolean; + strictPropertyInitialization?: boolean; + stripInternal?: boolean; + skipDefaultLibCheck?: boolean; + sourceMap?: boolean; + sourceRoot?: string; + suppressOutputPathCheck?: boolean; + target?: ScriptTarget; + traceResolution?: boolean; + tsBuildInfoFile?: string; + typeRoots?: string[]; + types?: string[]; + useDefineForClassFields?: boolean; + useUnknownInCatchVariables?: boolean; + verbatimModuleSyntax?: boolean; + maxNodeModuleJsDepth?: number; + /** Internal fields */ + configFilePath?: string; +} + +export interface ProjectReference { + /** Path is a normalized path on disk. */ + path: string; + /** OriginalPath is the path as it was originally written. */ + originalPath: string; + /** Circular indicates that this reference is intended to form a circularity. */ + circular: boolean; +} + +export interface TypeAcquisition { + enable?: boolean; + include?: string[]; + exclude?: string[]; + disableFilenameBasedTypeAcquisition?: boolean; +} + +export interface TranspileOptions { + compilerOptions?: CompilerOptions; + fileName?: string; + reportDiagnostics?: boolean; +} + +export interface ImportAdderAction { + kind: "importSymbol"; + symbol?: number; + isValidTypeOnlyUseSite?: boolean; +} + +/** CompletionEntryResponse represents a single completion item. */ +export interface CompletionEntryResponse { + name: string; + kind?: number; + sortText?: string; + insertText?: string; + filterText?: string; + detail?: string; + labelDetails?: CompletionEntryLabelDetailsResponse; + symbol?: SymbolResponse; +} + +export interface EmitOutputFile { + fileName: string; + text: string; + sourceFileName?: string; +} + +/** ProjectFileChanges describes what source files changed within a single project. */ +export interface ProjectFileChanges { + /** ChangedFiles lists source file paths whose content differs. */ + changedFiles?: string[]; + /** DeletedFiles lists source file paths removed from the project's program. */ + deletedFiles?: string[]; +} + +/** CompletionEntryLabelDetailsResponse holds additional label display text for a completion entry. */ +export interface CompletionEntryLabelDetailsResponse { + detail?: string; + description?: string; +} diff --git a/_packages/native-preview/src/api/proto.ts b/_packages/native-preview/src/api/proto.ts index 1624f0e14d9..4f9b56b55b6 100644 --- a/_packages/native-preview/src/api/proto.ts +++ b/_packages/native-preview/src/api/proto.ts @@ -1,30 +1,29 @@ -import type { CheckFlags } from "#enums/checkFlags"; -import type { CompletionItemKind } from "#enums/completionItemKind"; -import type { DiagnosticCategory } from "#enums/diagnosticCategory"; -import type { ModuleKind } from "#enums/moduleKind"; -import type { - __String, - Path, -} from "../ast/index.ts"; -import type { CompilerOptions } from "./compilerOptions.ts"; import { documentURIToFileName, fileNameToDocumentURI, } from "./path.ts"; - -export type { CompilerOptions } from "./compilerOptions.ts"; - -/** - * A document identifier that can be either a file name (path string) or a document URI object. - * - * @example - * // Using a file name - * project.program.getSourceFile("/path/to/file.ts"); - * - * // Using a URI - * project.program.getSourceFile({ uri: "file:///path/to/file.ts" }); - */ -export type DocumentIdentifier = string | { uri: string; }; +import type { + APIMethodInfo, + DocumentIdentifier, + SignatureResponse, + SourceFileResponse, + SymbolResponse, + TypeResponse, + UpdateSnapshotParams as CoreUpdateSnapshotParams, +} from "./proto.generated.ts"; +export type { ConfigFileResponse as ParsedCommandLine, DiagnosticResponse as Diagnostic } from "./proto.generated.ts"; + +export * from "./proto.generated.ts"; + +export type APIMethodsReturning = { [K in keyof APIMethodInfo]: [T] extends [NonNullable] ? [NonNullable] extends [T] ? K : never : never; }[keyof APIMethodInfo]; + +export type SourceFileResponseMethod = APIMethodsReturning; +export type SymbolPropertyMethod = APIMethodsReturning; +export type SymbolsPropertyMethod = APIMethodsReturning; +export type SignaturePropertyMethod = APIMethodsReturning; +export type TypePropertyMethod = Exclude, IntrinsicTypeMethod>; +export type TypesPropertyMethod = APIMethodsReturning; +export type IntrinsicTypeMethod = "getAnyType" | "getBigIntType" | "getBooleanType" | "getESSymbolType" | "getNeverType" | "getNonPrimitiveType" | "getNullType" | "getNumberType" | "getStringType" | "getUndefinedType" | "getUnknownType" | "getVoidType"; /** * A position within a document, combining a document identifier with an offset. @@ -36,20 +35,6 @@ export interface DocumentPosition { position: number; } -export interface TextEdit { - pos: number; - end: number; - newText: string; -} - -export interface ImportSymbolActionRequest { - kind: "importSymbol"; - symbol: number; - isValidTypeOnlyUseSite?: boolean; -} - -export type ImportAdderActionRequest = ImportSymbolActionRequest; - /** * Resolves a DocumentIdentifier to a file name. * If the identifier contains a URI, it is converted to a file name. @@ -72,135 +57,26 @@ export function resolveDocumentURI(identifier: DocumentIdentifier): string { return identifier.uri; } -/** - * Response from the initialize method. - */ -export interface InitializeResponse { - /** Whether the host file system is case-sensitive */ - useCaseSensitiveFileNames: boolean; - /** The server's current working directory */ - currentDirectory: string; -} - -export interface TypeAcquisition { - enable?: boolean; - include?: string[]; - exclude?: string[]; - disableFilenameBasedTypeAcquisition?: boolean; -} - -export interface ProjectReference { - /** A normalized path on disk */ - path: string; - /** The path as the user originally wrote it */ - originalPath?: string; - /** True if it is intended that this reference form a circularity */ - circular?: boolean; -} - -/** - * A diagnostic message from the TypeScript compiler. - */ -export interface Diagnostic { - /** File name of the source file this diagnostic belongs to, if any */ - readonly fileName?: string | undefined; - /** Start position of the diagnostic */ - readonly pos: number; - /** End position of the diagnostic */ - readonly end: number; - /** Diagnostic error code */ - readonly code: number; - /** Diagnostic category (error, warning, suggestion, message) */ - readonly category: DiagnosticCategory; - /** Localized diagnostic message text */ - readonly text: string; - /** Whether this diagnostic highlights unnecessary code */ - readonly reportsUnnecessary?: boolean | undefined; - /** Whether this diagnostic highlights deprecated code */ - readonly reportsDeprecated?: boolean | undefined; - /** Chained diagnostic messages */ - readonly messageChain?: readonly Diagnostic[] | undefined; - /** Related diagnostic information */ - readonly relatedInformation?: readonly Diagnostic[] | undefined; -} - -export interface ParsedCommandLine { - options: CompilerOptions; - fileNames: string[]; - projectReferences?: ProjectReference[]; - typeAcquisition?: TypeAcquisition; - compileOnSave?: boolean; - raw?: any; - errors: readonly Diagnostic[]; -} - -export interface ReadConfigFileResult { - config: any; - error?: Diagnostic; -} - -export interface LSPUpdateSnapshotParams { +export interface LSPUpdateSnapshotParams extends CoreUpdateSnapshotParams { /** * @deprecated Use {@link openProjects} instead. * Path to a tsconfig.json file to open in the new snapshot. */ openProject?: string; - /** - * tsconfig.json files to open/load in the new snapshot. Opens are ref-counted - * and persist across snapshots until closed via {@link closeProjects}. - */ - openProjects?: DocumentIdentifier[]; - /** - * tsconfig.json files to release in the new snapshot. A project is only unloaded - * once every API client that opened it closes it. - */ - closeProjects?: DocumentIdentifier[]; - /** - * Files to keep open for the API client, mirroring LSP's `textDocument/didOpen`. - * For each file, ancestor directories are searched for a tsconfig that contains it; - * if one is found, that configured project is loaded and becomes the file's default - * project. Otherwise the file is loaded into the inferred project (e.g. a d.ts in - * node_modules that is not part of any project's import graph). Opens persist across - * subsequent snapshots until the file is closed via {@link closeFiles}. - * After calling updateSnapshot with openFiles, getDefaultProjectForFile returns the - * resolved configured or inferred project. - */ - openFiles?: DocumentIdentifier[]; - /** - * Files to release in the new snapshot. A file is only fully closed once every - * API client that opened it closes it. - */ - closeFiles?: DocumentIdentifier[]; -} - -export interface FileChangeSummary { - changed?: DocumentIdentifier[]; - created?: DocumentIdentifier[]; - deleted?: DocumentIdentifier[]; -} -export type FileChanges = FileChangeSummary | { invalidateAll: true; }; - -/** - * Parameters for updateSnapshot. - */ -export interface UpdateSnapshotParams extends LSPUpdateSnapshotParams { - fileChanges?: FileChanges; + /** FileChanges are not supplied by the LSP */ + fileChanges?: never; } /** - * Parameters for updateTemporarySnapshot. Unlike {@link UpdateSnapshotParams}, this - * only overrides a single file's content: it does not open or close projects/files - * and does not advance the session's latest snapshot. The resulting snapshot is only - * for the caller's own queries and must be released when done. + * Parameters for updateSnapshot, including deprecated members handled by `toUpdateSnapshotRequest` */ -export interface UpdateTemporarySnapshotParams { - /** The current client snapshot on which to layer the temporary update. */ - snapshot: number; - /** The file whose content is temporarily overridden. */ - file: DocumentIdentifier; - /** The temporary content for the file. */ - newText: string; +export interface UpdateSnapshotParams extends CoreUpdateSnapshotParams { + /** + * @deprecated Use {@link openProjects} instead. + * Path to a tsconfig.json file to open in the new snapshot. + */ + openProject?: string; } /** @@ -218,157 +94,3 @@ export function toUpdateSnapshotRequest(params?: UpdateSnapshotParams): UpdateSn ...(mergedOpenProjects !== undefined ? { openProjects: mergedOpenProjects } : {}), }; } - -/** - * Changes to source files within a single project. - */ -export interface ProjectFileChanges { - /** Source file paths whose content changed */ - changedFiles?: string[]; - /** Source file paths removed from the project's program */ - deletedFiles?: string[]; -} - -/** - * Changes between two consecutive snapshots, reported per-project. - */ -export interface SnapshotChanges { - /** Project handles mapped to their file changes. Projects not listed are unchanged. */ - changedProjects?: Record; - /** Project handles that were removed from the snapshot */ - removedProjects?: string[]; -} - -/** - * Response from updateSnapshot. - */ -export interface UpdateSnapshotResponse { - /** Handle for the newly created snapshot */ - snapshot: number; - /** List of projects in the snapshot */ - projects: ProjectResponse[]; - /** Changes from the previous snapshot (absent for the first snapshot) */ - changes?: SnapshotChanges; -} - -export interface ProjectResponse { - id: Path; - configFileName: string; - parsedCommandLine: ParsedCommandLine; - /** @deprecated Use `parsedCommandLine.options`. */ - compilerOptions: CompilerOptions; - /** @deprecated Use `parsedCommandLine.fileNames`. */ - rootFiles: string[]; -} - -export interface SourceFileResponse { - /** Base64-encoded binary AST data */ - data: string; -} - -export interface SourceFileMetadata { - isDefaultLibrary: boolean; - isFromExternalLibrary: boolean; - packageJsonType: string; - packageJsonDirectory: string; - impliedNodeFormat: ModuleKind; -} - -export interface SymbolResponse { - id: number; - /** - * The project the symbol was first observed in. Used as the default project for - * follow-up lookups that need a project context (e.g. members/exports), since symbols - * are shared snapshot-wide and such lookups can vary by project. - */ - project: Path; - name: __String; - flags: number; - checkFlags: CheckFlags; - declarations?: string[]; - valueDeclaration?: string; - parent?: number; - exportSymbol?: number; -} - -export interface TypeResponse { - id: number; - flags: number; - objectFlags?: number; - /** Literal value. BigInt literals are encoded as a decimal string (e.g. "-123") since JSON cannot represent bigint. Absent values are serialized as null. */ - value?: string | number | boolean | null; - freshType?: number; - regularType?: number; - target?: number; - typeParameters?: number[]; - outerTypeParameters?: number[]; - localTypeParameters?: number[]; - elementFlags?: number[]; - fixedLength?: number; - readonly?: boolean; - objectType?: number; - indexType?: number; - checkType?: number; - extendsType?: number; - baseType?: number; - substConstraint?: number; - texts?: string[]; - intrinsicName?: string; - isThisType?: boolean; - aliasTypeArguments?: number[]; - aliasSymbol?: number; - symbol?: number; -} - -export interface SignatureResponse { - id: number; - flags: number; - declaration?: string; - typeParameters?: number[]; - parameters?: number[]; - thisParameter?: number; - target?: number; -} - -export interface TypePredicateResponse { - kind: number; - parameterIndex: number; - parameterName?: string; - type?: TypeResponse; -} - -export interface IndexInfoResponse { - keyType: TypeResponse; - valueType: TypeResponse; - isReadonly?: boolean; - declaration?: string; -} - -export interface ProfileParams { - dir: string; -} - -export interface ProfileResult { - file: string; -} - -export interface CompletionEntryLabelDetailsResponse { - detail?: string; - description?: string; -} - -export interface CompletionEntryResponse { - name: string; - kind?: CompletionItemKind; - sortText?: string; - insertText?: string; - filterText?: string; - detail?: string; - labelDetails?: CompletionEntryLabelDetailsResponse; - symbol?: SymbolResponse; -} - -export interface CompletionInfoResponse { - isIncomplete: boolean; - entries: CompletionEntryResponse[]; -} diff --git a/_packages/native-preview/src/api/sync/api.ts b/_packages/native-preview/src/api/sync/api.ts index caaec748b1d..253ed1d1f93 100644 --- a/_packages/native-preview/src/api/sync/api.ts +++ b/_packages/native-preview/src/api/sync/api.ts @@ -57,26 +57,28 @@ import { } from "../path.ts"; import type { CompilerOptions, - CompletionInfoResponse, + Diagnostic, DocumentIdentifier, DocumentPosition, - ImportAdderActionRequest, - ImportSymbolActionRequest, - IndexInfoResponse, - InitializeResponse, + EmitOutputResponse as ProtocolEmitOutputResponse, + ImportAdderAction, + IntrinsicTypeMethod, LSPUpdateSnapshotParams, ParsedCommandLine, - ProfileResult, ProjectReference, ProjectResponse, - ReadConfigFileResult, + ReadConfigFileResponse, + SignaturePropertyMethod, SignatureResponse, SourceFileMetadata, + SymbolPropertyMethod, SymbolResponse, + SymbolsPropertyMethod, TextEdit, TypeAcquisition, - TypePredicateResponse, + TypePropertyMethod, TypeResponse, + TypesPropertyMethod, UpdateSnapshotParams, UpdateSnapshotResponse, } from "../proto.ts"; @@ -104,14 +106,13 @@ import type { CompletionInfo, CompletionOptions, ConditionalType, - Diagnostic, EmitOutput, EmitOutputFile, EmitResult, FreshableType, GetImportEditsForSymbolsOptions, IdentifierTypePredicate, - ImportAdderAction, + ImportAdderAction as APIImportAdderAction, IndexedAccessType, IndexInfo, IndexType, @@ -140,13 +141,64 @@ import type { export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts"; export { CheckFlags, CompletionItemKind, DiagnosticCategory, ElementFlags, EmitOnly, ModifierFlags, ModuleKind, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind }; -export type { APIOptions, AssertsIdentifierTypePredicate, AssertsThisTypePredicate, BigIntLiteralType, BooleanLiteralType, ClientSocketOptions, ClientSpawnOptions, CompilerOptions, CompletionEntry, CompletionInfo, CompletionOptions, ConditionalType, Diagnostic, DocumentIdentifier, DocumentPosition, EmitOutput, EmitOutputFile, EmitResult, FreshableType, GetImportEditsForSymbolsOptions, IdentifierTypePredicate, ImportAdderAction, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, JSDocTagInfo, LiteralType, LSPConnectionOptions, NumberLiteralType, ObjectType, ParsedCommandLine, ProjectReference, ReadConfigFileResult, RequestTiming, SourceFileMetadata, StringLiteralType, StringMappingType, StructuredType, SubstitutionType, TemplateLiteralType, TextEdit, ThisTypePredicate, TimingAccumulators, TimingInfo, TupleType, Type, TypeAcquisition, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; - -interface EmitOutputResponse { - readonly emitSkipped: boolean; - readonly diagnostics: readonly Diagnostic[]; - readonly outputFiles: readonly (EmitOutputFile & { readonly fileName: string; })[]; -} +export type { + APIImportAdderAction as ImportAdderAction, + APIOptions, + AssertsIdentifierTypePredicate, + AssertsThisTypePredicate, + BigIntLiteralType, + BooleanLiteralType, + ClientSocketOptions, + ClientSpawnOptions, + CompilerOptions, + CompletionEntry, + CompletionInfo, + CompletionOptions, + ConditionalType, + Diagnostic, + DocumentIdentifier, + DocumentPosition, + EmitOutput, + EmitOutputFile, + EmitResult, + FreshableType, + GetImportEditsForSymbolsOptions, + IdentifierTypePredicate, + IndexedAccessType, + IndexInfo, + IndexType, + InterfaceType, + IntersectionType, + IntrinsicType, + JSDocTagInfo, + LiteralType, + LSPConnectionOptions, + NumberLiteralType, + ObjectType, + ParsedCommandLine, + ProjectReference, + ReadConfigFileResponse, + RequestTiming, + SourceFileMetadata, + StringLiteralType, + StringMappingType, + StructuredType, + SubstitutionType, + TemplateLiteralType, + TextEdit, + ThisTypePredicate, + TimingAccumulators, + TimingInfo, + TupleType, + Type, + TypeAcquisition, + TypeParameter, + TypePredicate, + TypePredicateBase, + TypeReference, + UnionOrIntersectionType, + UnionType, +}; export interface TranspileOptions { compilerOptions?: CompilerOptions; @@ -180,14 +232,14 @@ export class API { * Use this when connecting to an API pipe provided by an LSP server via custom/initializeAPISession. */ static fromLSPConnection(options: LSPConnectionOptions): API { - const api = new API(options); + const api = new API(options); api.ensureInitialized(); return api; } private ensureInitialized(): void { if (!this.initialized) { - const response = this.client.apiRequest("initialize", null); + const response = this.client.apiRequest("initialize", null); const getCanonicalFileName = createGetCanonicalFileName(response.useCaseSensitiveFileNames); const currentDirectory = response.currentDirectory; this.toPath = (fileName: string) => toPath(fileName, currentDirectory, getCanonicalFileName) as Path; @@ -197,17 +249,17 @@ export class API { parseConfigFile(file: DocumentIdentifier): ParsedCommandLine { this.ensureInitialized(); - return this.client.apiRequest("parseConfigFile", { file }); + return this.client.apiRequest("parseConfigFile", { file }); } parseCommandLine(commandLine: readonly string[]): ParsedCommandLine { this.ensureInitialized(); - return this.client.apiRequest("parseCommandLine", { commandLine }); + return this.client.apiRequest("parseCommandLine", { commandLine }); } - readConfigFile(file: DocumentIdentifier): ReadConfigFileResult { + readConfigFile(file: DocumentIdentifier): ReadConfigFileResponse { this.ensureInitialized(); - return this.client.apiRequest("readConfigFile", { file }); + return this.client.apiRequest("readConfigFile", { file }); } parseJsonConfigFileContent( @@ -217,34 +269,34 @@ export class API { | { configFileName: DocumentIdentifier; configDirectory?: never; }, ): ParsedCommandLine { this.ensureInitialized(); - return this.client.apiRequest("parseJsonConfigFileContent", { json, ...options }); + return this.client.apiRequest("parseJsonConfigFileContent", { json, ...options }); } transpileModule(input: string, options: TranspileOptions = {}): TranspileOutput { this.ensureInitialized(); - return this.client.apiRequest("transpileModule", { input, options }); + return this.client.apiRequest("transpileModule", { input, options }); } transpileModuleFromFile(fileName: string, options: TranspileOptions = {}): TranspileOutput { this.ensureInitialized(); - return this.client.apiRequest("transpileModuleFromFile", { fileName, options }); + return this.client.apiRequest("transpileModuleFromFile", { fileName, options }); } transpileDeclaration(input: string, options: TranspileOptions = {}): TranspileOutput { this.ensureInitialized(); - return this.client.apiRequest("transpileDeclaration", { input, options }); + return this.client.apiRequest("transpileDeclaration", { input, options }); } transpileDeclarationFromFile(fileName: string, options: TranspileOptions = {}): TranspileOutput { this.ensureInitialized(); - return this.client.apiRequest("transpileDeclarationFromFile", { fileName, options }); + return this.client.apiRequest("transpileDeclarationFromFile", { fileName, options }); } updateSnapshot(params?: FromLSP extends true ? LSPUpdateSnapshotParams : UpdateSnapshotParams): Snapshot { this.ensureInitialized(); const requestParams = toUpdateSnapshotRequest(params); - const data = this.client.apiRequest("updateSnapshot", requestParams); + const data = this.client.apiRequest("updateSnapshot", requestParams); // Retain cached source files from previous snapshot for unchanged files if (this.latestSnapshot) { @@ -296,7 +348,7 @@ export class API { if (!this.activeSnapshots.has(baseSnapshot) || baseSnapshot.isDisposed()) { throw new Error("Cannot run a temporary file update on an inactive snapshot"); } - const data = this.client.apiRequest("updateTemporarySnapshot", { snapshot: baseSnapshot.id, file, newText }); + const data = this.client.apiRequest("updateTemporarySnapshot", { snapshot: baseSnapshot.id, file, newText }); // Retain cached source files from the base snapshot for files unchanged by // the temporary update. The temporary snapshot is not the latest snapshot, so @@ -361,13 +413,13 @@ export class InternalAPI { stopCPUProfile(): string { this.ensureInitialized(); - const result = this.client.apiRequest("stopCPUProfile", null); + const result = this.client.apiRequest("stopCPUProfile", null); return result.file; } saveHeapProfile(dir: string): string { this.ensureInitialized(); - const result = this.client.apiRequest("saveHeapProfile", { dir }); + const result = this.client.apiRequest("saveHeapProfile", { dir }); return result.file; } } @@ -416,7 +468,7 @@ export class Snapshot { getDefaultProjectForFile(file: DocumentIdentifier): Project | undefined { this.ensureNotDisposed(); - const data = this.client.apiRequest("getDefaultProjectForFile", { + const data = this.client.apiRequest("getDefaultProjectForFile", { snapshot: this.id, file, }); @@ -485,12 +537,12 @@ class SnapshotObjectRegistry { this.symbols.clear(); } - fetchSymbol(source: Symbol | Signature | Type, method: string, handle: number | undefined, projectId?: Path): Symbol { + fetchSymbol(source: Symbol | Signature | Type, method: SymbolPropertyMethod, handle: number | undefined, projectId: Path): Symbol { if (!handle) return undefined as unknown as Symbol; const cached = this.getSymbol(handle); if (cached) return cached; - const data = this.client.apiRequest(method, { + const data = this.client.apiRequest(method, { snapshot: this.snapshotId, project: projectId, objectId: source.id, @@ -499,7 +551,7 @@ class SnapshotObjectRegistry { return this.getOrCreateSymbol(data); } - fetchSymbols(source: Symbol | Signature | Type, method: string, handles?: readonly number[], projectId?: Path): readonly Symbol[] { + fetchSymbols(source: Symbol | Signature | Type, method: SymbolsPropertyMethod, handles: readonly number[] | undefined, projectId: Path): readonly Symbol[] { if (handles) { const result = new Array(handles.length); let allCached = true; @@ -513,7 +565,7 @@ class SnapshotObjectRegistry { } if (allCached) return result; } - const symbolData = this.client.apiRequest(method, { + const symbolData = this.client.apiRequest(method, { snapshot: this.snapshotId, project: projectId, objectId: source.id, @@ -582,14 +634,14 @@ class ProjectObjectRegistry { this.signatures.clear(); } - fetchOptionalType(source: Symbol | Signature | Type, method: string, handle: number | false | undefined): T | undefined { + fetchOptionalType(source: Symbol | Signature | Type, method: TypePropertyMethod, handle: number | false | undefined): T | undefined { if (handle !== false) { if (!handle) return undefined; const cached = this.getType(handle); if (cached) return cached as unknown as T; } - const data = this.client.apiRequest(method, { + const data = this.client.apiRequest(method, { snapshot: this.snapshotId, project: this.project.id, objectId: source.id, @@ -598,22 +650,22 @@ class ProjectObjectRegistry { return this.getOrCreateType(data) as unknown as T; } - fetchType(source: Symbol | Signature | Type, method: string, handle: number | false | undefined): T { + fetchType(source: Symbol | Signature | Type, method: TypePropertyMethod, handle: number | false | undefined): T { const result = this.fetchOptionalType(source, method, handle); if (result === undefined) throw new Error(`${method} returned no type for ${source.constructor.name} ${source.id}`); return result; } - fetchSymbol(source: Symbol | Signature | Type, method: string, handle: number | undefined): Symbol { + fetchSymbol(source: Symbol | Signature | Type, method: SymbolPropertyMethod, handle: number | undefined): Symbol { return this.snapshotRegistry.fetchSymbol(source, method, handle, this.project.id); } - fetchSignature(source: Symbol | Signature | Type, method: string, handle: number | undefined): Signature { + fetchSignature(source: Symbol | Signature | Type, method: SignaturePropertyMethod, handle: number | undefined): Signature { if (!handle) return undefined as unknown as Signature; const cached = this.getSignature(handle); if (cached) return cached; - const data = this.client.apiRequest(method, { + const data = this.client.apiRequest(method, { snapshot: this.snapshotId, project: this.project.id, objectId: source.id, @@ -622,7 +674,7 @@ class ProjectObjectRegistry { return this.getOrCreateSignature(data); } - fetchTypes(source: Symbol | Signature | Type, method: string, handles?: readonly number[]): readonly Type[] { + fetchTypes(source: Symbol | Signature | Type, method: TypesPropertyMethod, handles?: readonly number[]): readonly Type[] { if (handles) { const result = new Array(handles.length); let allCached = true; @@ -636,7 +688,7 @@ class ProjectObjectRegistry { } if (allCached) return result; } - const typesData = this.client.apiRequest(method, { + const typesData = this.client.apiRequest(method, { snapshot: this.snapshotId, project: this.project.id, objectId: source.id, @@ -645,14 +697,14 @@ class ProjectObjectRegistry { else return typesData.map(data => this.getOrCreateType(data)); } - fetchSymbols(source: Symbol | Signature | Type, method: string, handles?: readonly number[]): readonly Symbol[] { + fetchSymbols(source: Symbol | Signature | Type, method: SymbolsPropertyMethod, handles?: readonly number[]): readonly Symbol[] { return this.snapshotRegistry.fetchSymbols(source, method, handles, this.project.id); } // getBaseTypes is a checker-level endpoint keyed by `type` (not `objectId`), // so it cannot go through fetchTypes. This helper reuses that server method. fetchBaseTypes(source: Type): readonly Type[] { - const typesData = this.client.apiRequest("getBaseTypes", { + const typesData = this.client.apiRequest("getBaseTypes", { snapshot: this.snapshotId, project: this.project.id, type: source.id, @@ -662,7 +714,7 @@ class ProjectObjectRegistry { } fetchPropertiesOfType(source: Type): readonly Symbol[] { - const data = this.client.apiRequest("getPropertiesOfType", { + const data = this.client.apiRequest("getPropertiesOfType", { snapshot: this.snapshotId, project: this.project.id, type: source.id, @@ -671,7 +723,7 @@ class ProjectObjectRegistry { } fetchApparentPropertiesOfType(source: Type): readonly Symbol[] { - const data = this.client.apiRequest("getApparentPropertiesOfType", { + const data = this.client.apiRequest("getApparentPropertiesOfType", { snapshot: this.snapshotId, project: this.project.id, objectId: source.id, @@ -680,7 +732,7 @@ class ProjectObjectRegistry { } fetchPropertyOfType(source: Type, name: string): Symbol | undefined { - const data = this.client.apiRequest("getPropertyOfType", { + const data = this.client.apiRequest("getPropertyOfType", { snapshot: this.snapshotId, project: this.project.id, type: source.id, @@ -690,7 +742,7 @@ class ProjectObjectRegistry { } fetchSignaturesOfType(source: Type, kind: SignatureKind): readonly Signature[] { - const data = this.client.apiRequest("getSignaturesOfType", { + const data = this.client.apiRequest("getSignaturesOfType", { snapshot: this.snapshotId, project: this.project.id, type: source.id, @@ -700,7 +752,7 @@ class ProjectObjectRegistry { } fetchIndexInfosOfType(source: Type): readonly IndexInfo[] { - const data = this.client.apiRequest("getIndexInfosOfType", { + const data = this.client.apiRequest("getIndexInfosOfType", { snapshot: this.snapshotId, project: this.project.id, type: source.id, @@ -715,7 +767,7 @@ class ProjectObjectRegistry { } fetchTypeParameterAtPosition(source: Signature, pos: number): Type { - const data = this.client.apiRequest("getTypeParameterAtPosition", { + const data = this.client.apiRequest("getTypeParameterAtPosition", { snapshot: this.snapshotId, project: this.project.id, signature: source.id, @@ -749,8 +801,11 @@ export class Project { toPath: (fileName: string) => Path, snapshotRegistry: SnapshotObjectRegistry, ) { - this.id = data.id; + this.id = data.id as Path; this.configFileName = data.configFileName; + if (!data.parsedCommandLine?.options) { + throw new Error(`Project '${data.configFileName}' has no parsed command line`); + } this.parsedCommandLine = data.parsedCommandLine; this.compilerOptions = this.parsedCommandLine.options; this.rootFiles = this.parsedCommandLine.fileNames; @@ -775,7 +830,7 @@ export class Project { } /** @deprecated Use `languageService.getImportAdderEdits`. */ - getImportAdderEdits(file: DocumentIdentifier, actions: readonly ImportAdderAction[]): readonly TextEdit[] { + getImportAdderEdits(file: DocumentIdentifier, actions: readonly APIImportAdderAction[]): readonly TextEdit[] { return this.languageService.getImportAdderEdits(file, actions); } @@ -807,11 +862,11 @@ export class LanguageService { this.objectRegistry = objectRegistry; } - getImportAdderEdits(file: DocumentIdentifier, actions: readonly ImportAdderAction[]): readonly TextEdit[] { - const requestActions: ImportAdderActionRequest[] = actions.map(action => { + getImportAdderEdits(file: DocumentIdentifier, actions: readonly APIImportAdderAction[]): readonly TextEdit[] { + const requestActions: ImportAdderAction[] = actions.map(action => { switch (action.kind) { case "importSymbol": - const importSymbolAction: ImportSymbolActionRequest = { + const importSymbolAction: ImportAdderAction = { kind: "importSymbol", symbol: action.symbol.id, }; @@ -824,7 +879,7 @@ export class LanguageService { } }); - const data = this.client.apiRequest("getImportAdderEdits", { + const data = this.client.apiRequest("getImportAdderEdits", { snapshot: this.snapshotId, project: this.project.id, file, @@ -836,7 +891,7 @@ export class LanguageService { getImportEditsForSymbols(file: DocumentIdentifier, symbols: readonly Symbol[], options: GetImportEditsForSymbolsOptions = {}): readonly TextEdit[] { return this.getImportAdderEdits( file, - symbols.map((symbol): ImportAdderAction => { + symbols.map((symbol): APIImportAdderAction => { if (options.isValidTypeOnlyUseSite !== undefined) { return { kind: "importSymbol", @@ -853,7 +908,7 @@ export class LanguageService { } getReferencedSymbolsForNode(node: Node, position: number): ReferencedSymbolEntry[] { - const data = this.client.apiRequest<{ definition: string; symbol?: SymbolResponse; references: string[]; }[] | null>("getReferencedSymbolsForNode", { + const data = this.client.apiRequest("getReferencedSymbolsForNode", { snapshot: this.snapshotId, project: this.project.id, node: getNodeId(node), @@ -867,7 +922,7 @@ export class LanguageService { } getSignatureUsage(signatureDecl: Node): SignatureUsage[] { - const data = this.client.apiRequest<{ name: string; call?: string; }[] | null>("getSignatureUsages", { + const data = this.client.apiRequest("getSignatureUsages", { snapshot: this.snapshotId, project: this.project.id, signatureDecl: getNodeId(signatureDecl), @@ -879,13 +934,13 @@ export class LanguageService { } getCompletionsAtPosition(document: string, position: number, options?: CompletionOptions): CompletionInfo | undefined { - const data = this.client.apiRequest("getCompletionsAtPosition", { + const data = this.client.apiRequest("getCompletionsAtPosition", { snapshot: this.snapshotId, project: this.project.id, file: document, position, - triggerCharacter: options?.triggerCharacter, - includeSymbol: options?.includeSymbol, + ...(options?.triggerCharacter !== undefined ? { triggerCharacter: options.triggerCharacter } : {}), + ...(options?.includeSymbol !== undefined ? { includeSymbol: options.includeSymbol } : {}), }); if (!data) return undefined; return { @@ -955,7 +1010,7 @@ export class Program { } getSourceFileNames(): readonly string[] { - const data = this.client.apiRequest("getSourceFileNames", { + const data = this.client.apiRequest("getSourceFileNames", { snapshot: this.snapshotId, project: this.project.id, }); @@ -987,7 +1042,7 @@ export class Program { } private fetchSourceFileMetadata(path: Path): SourceFileMetadata | undefined { - const data = this.client.apiRequest("getSourceFileMetadata", { + const data = this.client.apiRequest("getSourceFileMetadata", { snapshot: this.snapshotId, project: this.project.id, file: path, @@ -1020,7 +1075,7 @@ export class Program { * Includes the root config file and any extended config files. */ getConfigFileNames(): readonly string[] { - const data = this.client.apiRequest("getConfigFileNames", { + const data = this.client.apiRequest("getConfigFileNames", { snapshot: this.snapshotId, project: this.project.id, }); @@ -1045,8 +1100,6 @@ export class Program { } /** - * Get syntactic (parse) diagnostics for a specific file or all files. - * @param file - Optional file to get diagnostics for. If omitted, returns diagnostics for all files. * Get syntactic (parse) diagnostics for specific files or all files. * @param file - Optional file(s) to get diagnostics for. If omitted, returns diagnostics for all files. */ @@ -1054,10 +1107,10 @@ export class Program { const files = file === undefined ? undefined : Array.isArray(file) ? file : [file]; - const data = this.client.apiRequest("getSyntacticDiagnostics", { + const data = this.client.apiRequest("getSyntacticDiagnostics", { snapshot: this.snapshotId, project: this.project.id, - files, + ...(files !== undefined ? { files } : {}), }); return data ?? []; } @@ -1070,10 +1123,10 @@ export class Program { const files = file === undefined ? undefined : Array.isArray(file) ? file : [file]; - const data = this.client.apiRequest("getBindDiagnostics", { + const data = this.client.apiRequest("getBindDiagnostics", { snapshot: this.snapshotId, project: this.project.id, - files, + ...(files !== undefined ? { files } : {}), }); return data ?? []; } @@ -1086,10 +1139,10 @@ export class Program { const files = file === undefined ? undefined : Array.isArray(file) ? file : [file]; - const data = this.client.apiRequest("getSemanticDiagnostics", { + const data = this.client.apiRequest("getSemanticDiagnostics", { snapshot: this.snapshotId, project: this.project.id, - files, + ...(files !== undefined ? { files } : {}), }); return data ?? []; } @@ -1102,10 +1155,10 @@ export class Program { const files = file === undefined ? undefined : Array.isArray(file) ? file : [file]; - const data = this.client.apiRequest("getSuggestionDiagnostics", { + const data = this.client.apiRequest("getSuggestionDiagnostics", { snapshot: this.snapshotId, project: this.project.id, - files, + ...(files !== undefined ? { files } : {}), }); return data ?? []; } @@ -1118,10 +1171,10 @@ export class Program { const files = file === undefined ? undefined : Array.isArray(file) ? file : [file]; - const data = this.client.apiRequest("getDeclarationDiagnostics", { + const data = this.client.apiRequest("getDeclarationDiagnostics", { snapshot: this.snapshotId, project: this.project.id, - files, + ...(files !== undefined ? { files } : {}), }); return data ?? []; } @@ -1130,7 +1183,7 @@ export class Program { * Get program-wide diagnostics for the project, including compiler options diagnostics. */ getProgramDiagnostics(): readonly Diagnostic[] { - const data = this.client.apiRequest("getProgramDiagnostics", { + const data = this.client.apiRequest("getProgramDiagnostics", { snapshot: this.snapshotId, project: this.project.id, }); @@ -1141,7 +1194,7 @@ export class Program { * Get global (non-file-specific) semantic diagnostics for the project. */ getGlobalDiagnostics(): readonly Diagnostic[] { - const data = this.client.apiRequest("getGlobalDiagnostics", { + const data = this.client.apiRequest("getGlobalDiagnostics", { snapshot: this.snapshotId, project: this.project.id, }); @@ -1152,7 +1205,7 @@ export class Program { * Get config file parsing diagnostics for the project. */ getConfigFileParsingDiagnostics(): readonly Diagnostic[] { - const data = this.client.apiRequest("getConfigFileParsingDiagnostics", { + const data = this.client.apiRequest("getConfigFileParsingDiagnostics", { snapshot: this.snapshotId, project: this.project.id, }); @@ -1166,21 +1219,26 @@ export class Program { * is written there. Otherwise, the server writes directly to the host filesystem. */ emit(emitOnly?: EmitOnly): EmitResult { - return this.client.apiRequest("emit", { + const response = this.client.apiRequest("emit", { snapshot: this.snapshotId, project: this.project.id, - emitOnly, + ...(emitOnly !== undefined ? { emitOnly } : {}), }); + return { + emitSkipped: response.emitSkipped, + diagnostics: response.diagnostics, + emittedFiles: response.emittedFiles, + }; } /** * Emits files and returns their contents without writing to the filesystem. */ emitToString(emitOnly?: EmitOnly): EmitOutput { - const response = this.client.apiRequest("emitToString", { + const response = this.client.apiRequest("emitToString", { snapshot: this.snapshotId, project: this.project.id, - emitOnly, + ...(emitOnly !== undefined ? { emitOnly } : {}), }); return toEmitOutput(response); } @@ -1189,7 +1247,7 @@ export class Program { * Gets JavaScript output for selected files regardless of project `noEmit`, `emitDeclarationOnly`, and `noEmitOnError` settings. */ getJavaScriptEmit(files: readonly DocumentIdentifier[]): EmitOutput { - const response = this.client.apiRequest("getJavaScriptEmit", { + const response = this.client.apiRequest("getJavaScriptEmit", { snapshot: this.snapshotId, project: this.project.id, files, @@ -1201,7 +1259,7 @@ export class Program { * Gets declaration output for selected files regardless of project `noEmit`, `declaration`, `emitDeclarationOnly`, and `noEmitOnError` settings. */ getDeclarationEmit(files: readonly DocumentIdentifier[]): EmitOutput { - const response = this.client.apiRequest("getDeclarationEmit", { + const response = this.client.apiRequest("getDeclarationEmit", { snapshot: this.snapshotId, project: this.project.id, files, @@ -1210,7 +1268,7 @@ export class Program { } } -function toEmitOutput(response: EmitOutputResponse): EmitOutput { +function toEmitOutput(response: ProtocolEmitOutputResponse): EmitOutput { const outputFiles = new Map(); for (const { fileName, ...outputFile } of response.outputFiles) { outputFiles.set(fileName, outputFile); @@ -1250,14 +1308,14 @@ export class Checker { getSymbolAtLocation(nodes: readonly Node[]): (Symbol | undefined)[]; getSymbolAtLocation(nodeOrNodes: Node | readonly Node[]): Symbol | (Symbol | undefined)[] | undefined { if (Array.isArray(nodeOrNodes)) { - const data = this.client.apiRequest<(SymbolResponse | null)[]>("getSymbolsAtLocations", { + const data = 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 data = this.client.apiRequest("getSymbolAtLocation", { + const data = this.client.apiRequest("getSymbolAtLocation", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(nodeOrNodes as Node), @@ -1269,7 +1327,7 @@ export class Checker { getSymbolAtPosition(file: DocumentIdentifier, positions: readonly number[]): (Symbol | undefined)[]; getSymbolAtPosition(file: DocumentIdentifier, positionOrPositions: number | readonly number[]): Symbol | (Symbol | undefined)[] | undefined { if (typeof positionOrPositions === "number") { - const data = this.client.apiRequest("getSymbolAtPosition", { + const data = this.client.apiRequest("getSymbolAtPosition", { snapshot: this.snapshotId, project: this.project.id, file, @@ -1277,7 +1335,7 @@ export class Checker { }); return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; } - const data = this.client.apiRequest<(SymbolResponse | null)[]>("getSymbolsAtPositions", { + const data = this.client.apiRequest("getSymbolsAtPositions", { snapshot: this.snapshotId, project: this.project.id, file, @@ -1290,14 +1348,14 @@ export class Checker { getSymbolOfSourceFile(files: readonly DocumentIdentifier[]): (Symbol | undefined)[]; getSymbolOfSourceFile(fileOrFiles: DocumentIdentifier | readonly DocumentIdentifier[]): Symbol | (Symbol | undefined)[] | undefined { if (Array.isArray(fileOrFiles)) { - const data = this.client.apiRequest<(SymbolResponse | null)[]>("getSymbolsOfSourceFiles", { + const data = this.client.apiRequest("getSymbolsOfSourceFiles", { snapshot: this.snapshotId, project: this.project.id, files: fileOrFiles, }); return data.map(d => d ? this.objectRegistry.getOrCreateSymbol(d) : undefined); } - const data = this.client.apiRequest("getSymbolOfSourceFile", { + const data = this.client.apiRequest("getSymbolOfSourceFile", { snapshot: this.snapshotId, project: this.project.id, file: fileOrFiles as DocumentIdentifier, @@ -1314,14 +1372,14 @@ export class Checker { getTypeOfSymbol(symbols: readonly Symbol[]): Type[]; getTypeOfSymbol(symbolOrSymbols: Symbol | readonly Symbol[]): Type | Type[] { if (Array.isArray(symbolOrSymbols)) { - const data = this.client.apiRequest("getTypesOfSymbols", { + const data = 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 data = this.client.apiRequest("getTypeOfSymbol", { + const data = this.client.apiRequest("getTypeOfSymbol", { snapshot: this.snapshotId, project: this.project.id, symbol: (symbolOrSymbols as Symbol).id, @@ -1335,7 +1393,7 @@ export class Checker { * {@link Type.isErrorType} to detect it). */ getDeclaredTypeOfSymbol(symbol: Symbol): Type { - const data = this.client.apiRequest("getDeclaredTypeOfSymbol", { + const data = this.client.apiRequest("getDeclaredTypeOfSymbol", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, @@ -1344,7 +1402,7 @@ export class Checker { } getReferencesToSymbolInFile(file: DocumentIdentifier, symbol: Symbol): NodeHandle[] { - const data = this.client.apiRequest("getReferencesToSymbolInFile", { + const data = this.client.apiRequest("getReferencesToSymbolInFile", { snapshot: this.snapshotId, project: this.project.id, file, @@ -1377,14 +1435,14 @@ export class Checker { getTypeAtLocation(nodes: readonly Node[]): Type[]; getTypeAtLocation(nodeOrNodes: Node | readonly Node[]): Type | Type[] { if (Array.isArray(nodeOrNodes)) { - const data = this.client.apiRequest("getTypeAtLocations", { + const data = 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 data = this.client.apiRequest("getTypeAtLocation", { + const data = this.client.apiRequest("getTypeAtLocation", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(nodeOrNodes as Node), @@ -1402,7 +1460,7 @@ export class Checker { * signature (use {@link Checker.isUnknownSignature} to detect it). */ getResolvedSignature(node: Node): Signature { - const data = this.client.apiRequest("getResolvedSignature", { + const data = this.client.apiRequest("getResolvedSignature", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), @@ -1414,7 +1472,7 @@ export class Checker { getTypeAtPosition(file: DocumentIdentifier, positions: readonly number[]): (Type | undefined)[]; getTypeAtPosition(file: DocumentIdentifier, positionOrPositions: number | readonly number[]): Type | (Type | undefined)[] | undefined { if (typeof positionOrPositions === "number") { - const data = this.client.apiRequest("getTypeAtPosition", { + const data = this.client.apiRequest("getTypeAtPosition", { snapshot: this.snapshotId, project: this.project.id, file, @@ -1422,7 +1480,7 @@ export class Checker { }); return data ? this.objectRegistry.getOrCreateType(data) : undefined; } - const data = this.client.apiRequest<(TypeResponse | null)[]>("getTypesAtPositions", { + const data = this.client.apiRequest("getTypesAtPositions", { snapshot: this.snapshotId, project: this.project.id, file, @@ -1439,15 +1497,19 @@ export class Checker { ): Symbol | undefined { // Distinguish Node (has `kind`) from DocumentPosition (has `document` and `position`) const isNode = location && "kind" in location; - const data = this.client.apiRequest("resolveName", { + const data = this.client.apiRequest("resolveName", { snapshot: this.snapshotId, project: this.project.id, name, meaning, - location: isNode ? getNodeId(location as Node) : undefined, - file: !isNode && location ? (location as DocumentPosition).document : undefined, - position: !isNode && location ? (location as DocumentPosition).position : undefined, - excludeGlobals, + ...(isNode ? { location: getNodeId(location as Node) } : {}), + ...(!isNode && location + ? { + file: (location as DocumentPosition).document, + position: (location as DocumentPosition).position, + } + : {}), + ...(excludeGlobals !== undefined ? { excludeGlobals } : {}), }); return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; } @@ -1458,13 +1520,16 @@ export class Checker { getSymbolsInScope(location: Node | DocumentPosition, meaning: SymbolFlags): readonly Symbol[] { // Distinguish Node (has `kind`) from DocumentPosition (has `document` and `position`) const isNode = "kind" in location; - const data = this.client.apiRequest("getSymbolsInScope", { + const data = this.client.apiRequest("getSymbolsInScope", { snapshot: this.snapshotId, project: this.project.id, meaning, - location: isNode ? getNodeId(location as Node) : undefined, - file: isNode ? undefined : (location as DocumentPosition).document, - position: isNode ? undefined : (location as DocumentPosition).position, + ...(isNode + ? { location: getNodeId(location as Node) } + : { + file: (location as DocumentPosition).document, + position: (location as DocumentPosition).position, + }), }); return data ? data.map(d => this.objectRegistry.getOrCreateSymbol(d)) : []; } @@ -1476,7 +1541,7 @@ export class Checker { } getContextualType(node: Expression): Type | undefined { - const data = this.client.apiRequest("getContextualType", { + const data = this.client.apiRequest("getContextualType", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), @@ -1486,7 +1551,7 @@ export class Checker { /** Get the base type of a literal type (e.g. `number` for `42`). Always returns a type. */ getBaseTypeOfLiteralType(type: Type): Type { - const data = this.client.apiRequest("getBaseTypeOfLiteralType", { + const data = this.client.apiRequest("getBaseTypeOfLiteralType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, @@ -1505,7 +1570,7 @@ export class Checker { * {@link Type.isErrorType} to detect it). */ getTypeFromTypeNode(node: TypeNode): Type { - const data = this.client.apiRequest("getTypeFromTypeNode", { + const data = this.client.apiRequest("getTypeFromTypeNode", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), @@ -1515,7 +1580,7 @@ export class Checker { /** Get the widened type. Always returns a type. */ getWidenedType(type: Type): Type { - const data = this.client.apiRequest("getWidenedType", { + const data = this.client.apiRequest("getWidenedType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, @@ -1528,7 +1593,7 @@ export class Checker { * returns a type; an out-of-range index yields the `any` type. */ getParameterType(signature: Signature, index: number): Type { - const data = this.client.apiRequest("getParameterType", { + const data = this.client.apiRequest("getParameterType", { snapshot: this.snapshotId, project: this.project.id, signature: signature.id, @@ -1538,7 +1603,7 @@ export class Checker { } isArrayLikeType(type: Type): boolean { - return this.client.apiRequest("isArrayLikeType", { + return this.client.apiRequest("isArrayLikeType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, @@ -1546,7 +1611,7 @@ export class Checker { } isTypeAssignableTo(source: Type, target: Type): boolean { - return this.client.apiRequest("isTypeAssignableTo", { + return this.client.apiRequest("isTypeAssignableTo", { snapshot: this.snapshotId, project: this.project.id, source: source.id, @@ -1555,7 +1620,7 @@ export class Checker { } getShorthandAssignmentValueSymbol(node: Node): Symbol | undefined { - const data = this.client.apiRequest("getShorthandAssignmentValueSymbol", { + const data = this.client.apiRequest("getShorthandAssignmentValueSymbol", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), @@ -1569,7 +1634,7 @@ export class Checker { * error type (use {@link Type.isErrorType} to detect it). */ getTypeOfSymbolAtLocation(symbol: Symbol, location: Node): Type { - const data = this.client.apiRequest("getTypeOfSymbolAtLocation", { + const data = this.client.apiRequest("getTypeOfSymbolAtLocation", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, @@ -1578,8 +1643,8 @@ export class Checker { return this.objectRegistry.getOrCreateType(data); } - private getIntrinsicType(method: string): Type { - const data = this.client.apiRequest(method, { + private getIntrinsicType(method: IntrinsicTypeMethod): Type { + const data = this.client.apiRequest(method, { snapshot: this.snapshotId, project: this.project.id, }); @@ -1628,8 +1693,8 @@ export class Checker { snapshot: this.snapshotId, project: this.project.id, type: type.id, - location: enclosingDeclaration ? getNodeId(enclosingDeclaration) : undefined, - flags, + ...(enclosingDeclaration ? { location: getNodeId(enclosingDeclaration) } : {}), + ...(flags !== undefined ? { flags } : {}), }); if (!binaryData) return undefined; return decodeNode(binaryData) as TypeNode; @@ -1641,25 +1706,27 @@ export class Checker { project: this.project.id, signature: signature.id, kind, - location: enclosingDeclaration ? getNodeId(enclosingDeclaration) : undefined, - flags, + ...(enclosingDeclaration ? { location: getNodeId(enclosingDeclaration) } : {}), + ...(flags !== undefined ? { flags } : {}), }); if (!binaryData) return undefined; return decodeNode(binaryData) as Node; } typeToString(type: Type, enclosingDeclaration?: Node, flags?: number): string { - return this.client.apiRequest("typeToString", { + const result = this.client.apiRequest("typeToString", { snapshot: this.snapshotId, project: this.project.id, type: type.id, - location: enclosingDeclaration ? getNodeId(enclosingDeclaration) : undefined, - flags, + ...(enclosingDeclaration ? { location: getNodeId(enclosingDeclaration) } : {}), + ...(flags !== undefined ? { flags } : {}), }); + if (typeof result !== "string") throw new TypeError("typeToString returned a non-string result"); + return result; } isContextSensitive(node: Node): boolean { - return this.client.apiRequest("isContextSensitive", { + return this.client.apiRequest("isContextSensitive", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), @@ -1667,7 +1734,7 @@ export class Checker { } isArrayType(type: Type): boolean { - return this.client.apiRequest("isArrayType", { + return this.client.apiRequest("isArrayType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, @@ -1675,7 +1742,7 @@ export class Checker { } isTupleType(type: Type): boolean { - return this.client.apiRequest("isTupleType", { + return this.client.apiRequest("isTupleType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, @@ -1692,7 +1759,7 @@ export class Checker { * no rest parameter yields the `any` type. */ getRestTypeOfSignature(signature: Signature): Type { - const data = this.client.apiRequest("getRestTypeOfSignature", { + const data = this.client.apiRequest("getRestTypeOfSignature", { snapshot: this.snapshotId, project: this.project.id, signature: signature.id, @@ -1701,7 +1768,7 @@ export class Checker { } getTypePredicateOfSignature(signature: Signature): TypePredicate | undefined { - const data = this.client.apiRequest("getTypePredicateOfSignature", { + const data = this.client.apiRequest("getTypePredicateOfSignature", { snapshot: this.snapshotId, project: this.project.id, signature: signature.id, @@ -1749,7 +1816,7 @@ export class Checker { } getBaseConstraintOfType(type: Type): Type | undefined { - const data = this.client.apiRequest("getBaseConstraintOfType", { + const data = this.client.apiRequest("getBaseConstraintOfType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, @@ -1758,7 +1825,7 @@ export class Checker { } getPropertyOfType(type: Type, name: string): Symbol | undefined { - const data = this.client.apiRequest("getPropertyOfType", { + const data = this.client.apiRequest("getPropertyOfType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, @@ -1768,17 +1835,17 @@ export class Checker { } getConstantValue(node: Node): string | number | undefined { - const data = this.client.apiRequest("getConstantValue", { + const data = this.client.apiRequest("getConstantValue", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), }); - return data ?? undefined; + return typeof data === "string" || typeof data === "number" ? data : undefined; } /** Get the signature of a function-like declaration. Always returns a signature. */ getSignatureFromDeclaration(node: Node): Signature { - const data = this.client.apiRequest("getSignatureFromDeclaration", { + const data = this.client.apiRequest("getSignatureFromDeclaration", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), @@ -1787,7 +1854,7 @@ export class Checker { } getExportSpecifierLocalTargetSymbol(node: Node): Symbol | undefined { - const data = this.client.apiRequest("getExportSpecifierLocalTargetSymbol", { + const data = this.client.apiRequest("getExportSpecifierLocalTargetSymbol", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), @@ -1801,7 +1868,7 @@ export class Checker { * {@link Checker.isUnknownSymbol} to detect it). */ getAliasedSymbol(symbol: Symbol): Symbol { - const data = this.client.apiRequest("getAliasedSymbol", { + const data = this.client.apiRequest("getAliasedSymbol", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, @@ -1814,7 +1881,7 @@ export class Checker { * (e.g. `"/path/to/module".Namespace.Name`). */ getFullyQualifiedName(symbol: Symbol): string { - return this.client.apiRequest("getFullyQualifiedName", { + return this.client.apiRequest("getFullyQualifiedName", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, @@ -1822,7 +1889,7 @@ export class Checker { } getImmediateAliasedSymbol(symbol: Symbol): Symbol | undefined { - const data = this.client.apiRequest("getImmediateAliasedSymbol", { + const data = this.client.apiRequest("getImmediateAliasedSymbol", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, @@ -1837,7 +1904,7 @@ export class Checker { * the first call. */ private getWellKnownSymbols(): { unknown: number; undefined: number; arguments: number; } { - return this.wellKnownSymbols ??= this.client.apiRequest<{ unknown: number; undefined: number; arguments: number; }>("getWellKnownSymbols", { + return this.wellKnownSymbols ??= this.client.apiRequest("getWellKnownSymbols", { snapshot: this.snapshotId, project: this.project.id, }); @@ -1871,7 +1938,7 @@ export class Checker { * identity checks against it are local after the first call. */ private getWellKnownSignatures(): { unknown: number; } { - return this.wellKnownSignatures ??= this.client.apiRequest<{ unknown: number; }>("getWellKnownSignatures", { + return this.wellKnownSignatures ??= this.client.apiRequest("getWellKnownSignatures", { snapshot: this.snapshotId, project: this.project.id, }); @@ -1887,7 +1954,7 @@ export class Checker { } getExportsOfModule(symbol: Symbol): readonly Symbol[] { - const data = this.client.apiRequest("getExportsOfModule", { + const data = this.client.apiRequest("getExportsOfModule", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, @@ -1896,7 +1963,7 @@ export class Checker { } getMemberInModuleExports(symbol: Symbol, name: string): Symbol | undefined { - const data = this.client.apiRequest("getMemberInModuleExports", { + const data = this.client.apiRequest("getMemberInModuleExports", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, @@ -1906,7 +1973,7 @@ export class Checker { } getJsDocTagsOfSymbol(symbol: Symbol): readonly JSDocTagInfo[] { - const data = this.client.apiRequest("getJsDocTags", { + const data = this.client.apiRequest("getJsDocTags", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, @@ -1915,7 +1982,7 @@ export class Checker { } getDocumentationCommentOfSymbol(symbol: Symbol): string { - return this.client.apiRequest("getDocumentationComment", { + return this.client.apiRequest("getDocumentationComment", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, @@ -1926,7 +1993,7 @@ export class Checker { * Get the type arguments of a type reference (e.g. the `string` in `Array`). */ getTypeArguments(type: TypeReference): readonly Type[] { - const data = this.client.apiRequest("getTypeArguments", { + const data = this.client.apiRequest("getTypeArguments", { snapshot: this.snapshotId, project: this.project.id, type: type.id, @@ -1951,9 +2018,11 @@ export class Emitter { printNode(node: Node, options: PrintNodeOptions = {}): string { const encoded = encodeNode(node); const base64 = uint8ArrayToBase64(encoded); - return this.client.apiRequest("printNode", { + return this.client.apiRequest("printNode", { data: base64, - ...options, + ...(options.preserveSourceNewlines !== undefined ? { preserveSourceNewlines: options.preserveSourceNewlines } : {}), + ...(options.neverAsciiEscape !== undefined ? { neverAsciiEscape: options.neverAsciiEscape } : {}), + ...(options.terminateUnterminatedLiterals !== undefined ? { terminateUnterminatedLiterals: options.terminateUnterminatedLiterals } : {}), }); } } @@ -1977,7 +2046,7 @@ export class SnapshotInternalAPI { * @returns The formatted text of the node, indented for the insertion position. */ formatNodeForInsertion(node: Node, file: DocumentIdentifier, position: number): string { - const data = this.client.apiRequest("getDefaultProjectForFile", { + const data = this.client.apiRequest("getDefaultProjectForFile", { snapshot: this.snapshotId, file, }); @@ -1987,7 +2056,7 @@ export class SnapshotInternalAPI { const encoded = encodeNode(node); const base64 = uint8ArrayToBase64(encoded); - return this.client.apiRequest("formatNodeForInsertion", { + return this.client.apiRequest("formatNodeForInsertion", { snapshot: this.snapshotId, project: data.id, file, @@ -2075,11 +2144,11 @@ export class Symbol { this.objectRegistry = objectRegistry; this.id = data.id; - this.escapedName = data.name; - this.name = unescapeLeadingUnderscores(data.name); + this.escapedName = data.name as __String; + this.name = unescapeLeadingUnderscores(data.name as __String); this.flags = data.flags; this.checkFlags = data.checkFlags; - const canonicalProject = objectRegistry.getProject(data.project); + const canonicalProject = objectRegistry.getProject(data.project as Path); if (!canonicalProject) { throw new Error(`Symbol ${data.id} references unknown canonical project '${data.project}'`); } @@ -2111,7 +2180,7 @@ export class Symbol { return this.exportsCache ??= this.fetchSymbolTable("getExportsOfSymbol"); } - private fetchSymbolTable(method: string): ReadonlyMap<__String, Symbol> { + private fetchSymbolTable(method: SymbolsPropertyMethod): ReadonlyMap<__String, Symbol> { const symbols = this.objectRegistry.fetchSymbols(this, method, undefined, this.canonicalProject.id); const table = new Map<__String, Symbol>(); for (const symbol of symbols) { @@ -2195,7 +2264,8 @@ class TypeObject implements Type { if (data.value != null) { // BigInt literal values are serialized as decimal strings (e.g. "-123") because // JSON cannot represent bigint. Decode them back into a real bigint here. - this.value = (data.flags & TypeFlags.BigIntLiteral) ? BigInt(data.value) : data.value; + const value = data.value as string | number | boolean; + this.value = (data.flags & TypeFlags.BigIntLiteral) ? BigInt(value as string) : value; } if (data.intrinsicName !== undefined) this.intrinsicName = data.intrinsicName; if (data.isThisType !== undefined) this.isThisType = data.isThisType; diff --git a/_packages/native-preview/src/api/sync/client.ts b/_packages/native-preview/src/api/sync/client.ts index 615fc299fa7..d1f25008acf 100644 --- a/_packages/native-preview/src/api/sync/client.ts +++ b/_packages/native-preview/src/api/sync/client.ts @@ -7,6 +7,10 @@ import { isSpawnOptions, resolveExePath, } from "../options.ts"; +import type { + APIMethodInfo, + SourceFileResponseMethod, +} from "../proto.ts"; import { SyncRpcChannel } from "../syncChannel.ts"; import { combineTimingInfo, @@ -81,18 +85,18 @@ export class Client { } } - apiRequest(method: string, params?: unknown): T { + apiRequest(method: K, params?: APIMethodInfo[K]["params"]): APIMethodInfo[K]["result"] { const encodedPayload = JSON.stringify(params); const start = performance.now(); const result = this.channel.requestSync(method, encodedPayload); this.recordTiming(method, start); if (result.length) { - return JSON.parse(result) as T; + return JSON.parse(result) as APIMethodInfo[K]["result"]; } - return undefined as unknown as T; + return undefined as APIMethodInfo[K]["result"]; } - apiRequestBinary(method: string, params?: unknown): Uint8Array | undefined { + apiRequestBinary(method: K, params?: APIMethodInfo[K]["params"]): Uint8Array | undefined { const start = performance.now(); const result = this.channel.requestBinarySync(method, this.encoder.encode(JSON.stringify(params))); this.recordTiming(method, start); diff --git a/_packages/native-preview/test/compilerOptions.test.ts b/_packages/native-preview/test/compilerOptions.test.ts deleted file mode 100644 index a5ddeda3f9f..00000000000 --- a/_packages/native-preview/test/compilerOptions.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -import assert from "node:assert"; -import { readFileSync } from "node:fs"; -import { - dirname, - join, -} from "node:path"; -import { - describe, - test, -} from "node:test"; -import { fileURLToPath } from "node:url"; - -const testDir = dirname(fileURLToPath(import.meta.url)); -const repoRoot = join(testDir, "..", "..", ".."); -const goOptionsPath = join(repoRoot, "internal", "core", "compileroptions.go"); -const tsOptionsPath = join(testDir, "..", "src", "api", "compilerOptions.ts"); -const exposedInternalOptions = new Set(["configFilePath"]); - -/** - * Extracts the JSON tag names of the fields exposed in the TypeScript API. - * These are the public, non-deprecated Go fields plus explicitly selected - * internal fields. - */ -function getGoApiOptionNames(): Set { - const source = readFileSync(goOptionsPath, "utf-8"); - const structMatch = source.match(/type CompilerOptions struct \{([\s\S]*?)\n\}/); - assert.ok(structMatch, "Could not find `type CompilerOptions struct` in compileroptions.go"); - - let body = structMatch[1]; - const internalMarker = body.indexOf("// Internal fields"); - assert.notStrictEqual(internalMarker, -1, "Could not find `// Internal fields` marker in compileroptions.go"); - const internalBody = body.slice(internalMarker); - body = body.slice(0, internalMarker); - - const names = new Set(); - let prevDeprecated = false; - for (const rawLine of body.split("\n")) { - const line = rawLine.trim(); - if (line === "") continue; - - const tagMatch = line.match(/`json:"([^",]+)/); - if (tagMatch) { - // A field's `// Deprecated:` doc comment sits on the line directly above it. - if (!prevDeprecated) { - names.add(tagMatch[1]); - } - prevDeprecated = false; - } - else { - prevDeprecated = line.startsWith("// Deprecated:"); - } - } - for (const match of internalBody.matchAll(/`json:"([^",]+)/g)) { - if (exposedInternalOptions.has(match[1])) { - names.add(match[1]); - } - } - return names; -} - -/** - * Extracts the property names of the TS `CompilerOptions` interface. - */ -function getTsOptionNames(): Set { - const source = readFileSync(tsOptionsPath, "utf-8"); - const interfaceMatch = source.match(/export interface CompilerOptions \{([\s\S]*?)\n\}/); - assert.ok(interfaceMatch, "Could not find `export interface CompilerOptions` in compilerOptions.ts"); - - const names = new Set(); - for (const line of interfaceMatch[1].split("\n")) { - const propMatch = line.match(/^\s*([A-Za-z_]\w*)\??:/); - if (propMatch) { - names.add(propMatch[1]); - } - } - return names; -} - -describe("CompilerOptions type stays in sync with Go", () => { - const goNames = getGoApiOptionNames(); - const tsNames = getTsOptionNames(); - - test("sanity: both sides parsed a plausible number of options", () => { - assert.ok(goNames.size > 50, `Parsed too few Go options (${goNames.size}); parser likely broke`); - assert.ok(tsNames.size > 50, `Parsed too few TS options (${tsNames.size}); parser likely broke`); - }); - - test("no public Go option is missing from the TS interface", () => { - const missing = [...goNames].filter(n => !tsNames.has(n)).sort(); - assert.deepStrictEqual( - missing, - [], - `The following public compiler options exist in internal/core/compileroptions.go but are missing from ` - + `src/api/compilerOptions.ts. Add them, or, if they are internal, move them after the ` - + `"// Internal fields" marker (or annotate them with a "// Deprecated:" comment) in the Go struct:\n ${missing.join("\n ")}`, - ); - }); - - test("no TS option is absent from the public Go struct", () => { - const extra = [...tsNames].filter(n => !goNames.has(n)).sort(); - assert.deepStrictEqual( - extra, - [], - `The following options exist in src/api/compilerOptions.ts but are not public fields of ` - + `internal/core/compileroptions.go (they may have been removed, renamed, or marked internal):\n ${extra.join("\n ")}`, - ); - }); -}); diff --git a/_tools/gen-proto/main.go b/_tools/gen-proto/main.go new file mode 100644 index 00000000000..4750c788f0f --- /dev/null +++ b/_tools/gen-proto/main.go @@ -0,0 +1,735 @@ +package main + +import ( + "bytes" + "errors" + "fmt" + "go/ast" + "go/constant" + "go/types" + "os" + "path/filepath" + "reflect" + "slices" + "sort" + "strconv" + "strings" + + "golang.org/x/tools/go/packages" +) + +func main() { + os.Exit(run()) +} + +func run() int { + if len(os.Args) != 3 { + fmt.Fprintln(os.Stderr, "Usage: gen-proto .go .ts") + return 1 + } + if err := generate(os.Args[1], os.Args[2]); err != nil { + fmt.Fprintln(os.Stderr, err) + return 1 + } + return 0 +} + +type methodInfo struct { + name string + params types.Type + result types.Type + paramsText string + resultText string + resultNullable bool +} + +func generate(inputPath string, outputPath string) error { + absInput, err := filepath.Abs(inputPath) + if err != nil { + return err + } + + 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 fmt.Errorf("load API package: %w", err) + } + if len(pkgs) != 1 { + return fmt.Errorf("load API package: expected one package, got %d", len(pkgs)) + } + if packages.PrintErrors(pkgs) != 0 { + return errors.New("load API package: package contains errors") + } + pkg := pkgs[0] + + inputFile := findSyntaxFile(pkg, absInput) + if inputFile == nil { + return fmt.Errorf("input file %q was not part of package %q", absInput, pkg.PkgPath) + } + + 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) + discoverConnectionMethods(pkg, methodObjects, methods) + + for _, method := range methods { + if method.paramsText == "" && method.params == nil { + return fmt.Errorf("method %q has no request dispatch", method.name) + } + if method.resultText == "" && method.result == nil { + return fmt.Errorf("method %q has no result type", method.name) + } + } + // release is an ownership notification from the client's perspective. The + // server's internal bool acknowledgement is deliberately not part of the API. + for _, method := range methods { + if method.name == "release" || method.name == "startCPUProfile" { + method.result = nil + method.resultText = "void" + } + } + + renderer := newTypeRenderer(pkg) + var methodsOut bytes.Buffer + methodsOut.WriteString("export type APIMethod = { params: TParams; result: TResult; };\n\n") + methodsOut.WriteString("export interface APIMethodInfo {\n") + for _, method := range methods { + params := method.paramsText + if params == "" { + params = renderer.requestType(method.params) + } + result := method.resultText + if result == "" { + result = renderer.resultType(method.result, method.resultNullable) + } + fmt.Fprintf(&methodsOut, " %s: APIMethod<%s, %s>;\n", propertyName(method.name), params, result) + } + methodsOut.WriteString("}\n") + + declarations, err := renderer.declarations() + if err != nil { + return err + } + var out bytes.Buffer + out.WriteString("// Code generated by gen-proto; DO NOT EDIT.\n\n") + if imports := renderer.importDeclarations(); imports != "" { + out.WriteString(imports) + out.WriteString("\n") + } + out.Write(methodsOut.Bytes()) + if declarations != "" { + out.WriteString("\n") + out.WriteString(declarations) + } + + absOutput, err := filepath.Abs(outputPath) + if err != nil { + return err + } + if err := os.MkdirAll(filepath.Dir(absOutput), 0o755); err != nil { + return err + } + data := bytes.ReplaceAll(out.Bytes(), []byte{'\n'}, []byte{'\r', '\n'}) + if err := os.WriteFile(absOutput, data, 0o644); err != nil { + return fmt.Errorf("write output: %w", err) + } + return nil +} + +func findSyntaxFile(pkg *packages.Package, path string) *ast.File { + cleanPath := filepath.Clean(path) + for i, filePath := range pkg.CompiledGoFiles { + if filepath.Clean(filePath) == cleanPath { + return pkg.Syntax[i] + } + } + return nil +} + +func declaredMethods(pkg *packages.Package, file *ast.File) ([]*methodInfo, map[types.Object]*methodInfo, error) { + var methods []*methodInfo + methodObjects := make(map[types.Object]*methodInfo) + for _, decl := range file.Decls { + genDecl, ok := decl.(*ast.GenDecl) + if !ok || genDecl.Tok.String() != "const" { + continue + } + for _, spec := range genDecl.Specs { + valueSpec := spec.(*ast.ValueSpec) + for _, name := range valueSpec.Names { + if !strings.HasPrefix(name.Name, "Method") { + continue + } + obj := pkg.TypesInfo.Defs[name] + constantObject, ok := obj.(*types.Const) + 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())} + methods = append(methods, method) + methodObjects[obj] = method + } + } + } + return methods, methodObjects, nil +} + +func discoverResultMetadata(pkg *packages.Package) (map[types.Object]types.Type, map[types.Object]bool, error) { + const resultDirective = "@gen-proto-result:" + const nullableDirective = "@gen-proto-nullable" + overrides := make(map[types.Object]types.Type) + nullableResults := make(map[types.Object]bool) + for _, file := range pkg.Syntax { + for _, decl := range file.Decls { + fn, ok := decl.(*ast.FuncDecl) + if !ok || fn.Doc == nil { + continue + } + fnObject := pkg.TypesInfo.Defs[fn.Name] + for _, comment := range fn.Doc.List { + text := strings.TrimSpace(strings.TrimPrefix(comment.Text, "//")) + if text == nullableDirective { + nullableResults[fnObject] = true + continue + } + typeName, ok := strings.CutPrefix(text, resultDirective) + if !ok { + continue + } + typeName = strings.TrimSpace(typeName) + typeObject, ok := pkg.Types.Scope().Lookup(typeName).(*types.TypeName) + if !ok { + return nil, nil, fmt.Errorf("result type override on %s refers to unknown type %q", fn.Name.Name, typeName) + } + overrides[fnObject] = typeObject.Type() + } + } + } + return overrides, nullableResults, nil +} + +func discoverSessionMethods(pkg *packages.Package, methodObjects map[types.Object]*methodInfo, methods []*methodInfo, resultTypeOverrides map[types.Object]types.Type, nullableResults map[types.Object]bool) { + 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 { + continue + } + ast.Inspect(fn.Body, func(node ast.Node) bool { + clause, isCaseClause := node.(*ast.CaseClause) + if !isCaseClause || len(clause.List) != 1 { + return true + } + method := methodForExpression(pkg, methodObjects, clause.List[0]) + if method == nil { + return true + } + + method.paramsText = "null" + ast.Inspect(clause, func(child ast.Node) bool { + assertion, isTypeAssertion := child.(*ast.TypeAssertExpr) + if !isTypeAssertion || assertion.Type == nil { + return true + } + if ident, isIdent := assertion.X.(*ast.Ident); isIdent && ident.Name == "parsed" { + method.params = pkg.TypesInfo.TypeOf(assertion.Type) + method.paramsText = "" + } + return true + }) + + for _, stmt := range clause.Body { + returnStmt, isReturnStmt := stmt.(*ast.ReturnStmt) + if !isReturnStmt || len(returnStmt.Results) == 0 { + continue + } + call, isCall := returnStmt.Results[0].(*ast.CallExpr) + if !isCall { + continue + } + sig, isSignature := types.Unalias(pkg.TypesInfo.TypeOf(call.Fun)).(*types.Signature) + if isSignature && sig.Results().Len() > 0 { + method.result = sig.Results().At(0).Type() + } + called := calledObject(pkg, call.Fun) + if override := resultTypeOverrides[called]; override != nil { + method.result = override + } + method.resultNullable = nullableResults[called] + } + return false + }) + } + } + + for _, method := range methods { + if method.params != nil && method.paramsText == "null" { + method.paramsText = "" + } + } +} + +func calledObject(pkg *packages.Package, expr ast.Expr) types.Object { + switch expr := expr.(type) { + case *ast.Ident: + return pkg.TypesInfo.Uses[expr] + case *ast.SelectorExpr: + if selection := pkg.TypesInfo.Selections[expr]; selection != nil { + return selection.Obj() + } + return pkg.TypesInfo.Uses[expr.Sel] + default: + return nil + } +} + +func discoverConnectionMethods(pkg *packages.Package, methodObjects map[types.Object]*methodInfo, methods []*methodInfo) { + missing := make(map[*methodInfo]bool) + for _, method := range methods { + missing[method] = method.params == nil && method.paramsText == "" + } + for _, file := range pkg.Syntax { + ast.Inspect(file, func(node ast.Node) bool { + clause, isCaseClause := node.(*ast.CaseClause) + if !isCaseClause || len(clause.List) != 1 { + return true + } + method := methodForExpression(pkg, methodObjects, clause.List[0]) + if method == nil || !missing[method] { + return true + } + ast.Inspect(clause, func(child ast.Node) bool { + call, isCall := child.(*ast.CallExpr) + if !isCall || len(call.Args) < 2 { + return true + } + selector, isSelector := call.Fun.(*ast.SelectorExpr) + if !isSelector || selector.Sel.Name != "WriteResponse" { + return true + } + method.paramsText = "void" + if ident, isIdent := call.Args[1].(*ast.Ident); isIdent && ident.Name == "nil" { + method.resultText = "void" + } else { + method.result = pkg.TypesInfo.TypeOf(call.Args[1]) + } + missing[method] = false + return false + }) + return false + }) + } +} + +func methodForExpression(pkg *packages.Package, methodObjects map[types.Object]*methodInfo, expr ast.Expr) *methodInfo { + call, ok := expr.(*ast.CallExpr) + if ok && len(call.Args) == 1 { + expr = call.Args[0] + } + ident, ok := expr.(*ast.Ident) + if !ok { + return nil + } + return methodObjects[pkg.TypesInfo.Uses[ident]] +} + +type typeRenderer struct { + apiPackagePath string + queued []*types.Named + seen map[*types.TypeName]bool + names map[string]*types.TypeName + imports map[string][]string + docs map[types.Object]string + packages map[string]*packages.Package + documentIdentifier *types.TypeName +} + +func newTypeRenderer(apiPackage *packages.Package) *typeRenderer { + r := &typeRenderer{ + apiPackagePath: apiPackage.PkgPath, + seen: make(map[*types.TypeName]bool), + names: make(map[string]*types.TypeName), + imports: make(map[string][]string), + docs: make(map[types.Object]string), + packages: make(map[string]*packages.Package), + } + r.indexPackage(apiPackage) + return r +} + +func (r *typeRenderer) indexPackage(pkg *packages.Package) { + if r.packages[pkg.PkgPath] != nil { + return + } + r.packages[pkg.PkgPath] = pkg + for _, imported := range pkg.Imports { + r.indexPackage(imported) + } + for _, file := range pkg.Syntax { + for _, decl := range file.Decls { + genDecl, ok := decl.(*ast.GenDecl) + if !ok || genDecl.Tok.String() != "type" { + continue + } + for _, spec := range genDecl.Specs { + typeSpec := spec.(*ast.TypeSpec) + r.recordDoc(pkg.TypesInfo.Defs[typeSpec.Name], firstDoc(typeSpec.Doc, genDecl.Doc)) + structType, ok := typeSpec.Type.(*ast.StructType) + if !ok { + continue + } + for _, field := range structType.Fields.List { + for _, name := range field.Names { + r.recordDoc(pkg.TypesInfo.Defs[name], firstDoc(field.Doc, field.Comment)) + } + } + } + } + } +} + +func (r *typeRenderer) recordDoc(obj types.Object, doc *ast.CommentGroup) { + if obj != nil && doc != nil { + r.docs[obj] = strings.TrimSpace(doc.Text()) + } +} + +func firstDoc(docs ...*ast.CommentGroup) *ast.CommentGroup { + for _, doc := range docs { + if doc != nil { + return doc + } + } + return nil +} + +func (r *typeRenderer) requestType(t types.Type) string { + if pointer, ok := types.Unalias(t).(*types.Pointer); ok { + t = pointer.Elem() + } + return r.typeString(t, false) +} + +func (r *typeRenderer) resultType(t types.Type, nullable bool) string { + if pointer, ok := types.Unalias(t).(*types.Pointer); ok { + t = pointer.Elem() + } + result := r.typeString(t, false) + if nullable { + result += " | null" + } + return result +} + +func (r *typeRenderer) typeString(t types.Type, allowNull bool) string { + if t == nil { + return "void" + } + t = types.Unalias(t) + var result string + switch t := t.(type) { + case *types.Basic: + result = basicType(t) + case *types.Pointer: + result = r.typeString(t.Elem(), false) + case *types.Slice: + result = arrayElement(r.typeString(t.Elem(), false)) + "[]" + case *types.Array: + result = arrayElement(r.typeString(t.Elem(), false)) + "[]" + case *types.Map: + result = fmt.Sprintf("Record", r.typeString(t.Elem(), true)) + case *types.Interface: + result = "unknown" + case *types.Struct: + result = r.inlineStruct(t) + case *types.Named: + result = r.namedType(t) + default: + result = "unknown" + } + if allowNull && result != "unknown" { + switch t.(type) { + case *types.Pointer, *types.Slice, *types.Map: + return result + " | null" + } + } + return result +} + +func basicType(t *types.Basic) string { + switch { + case t.Info()&types.IsBoolean != 0: + return "boolean" + case t.Info()&(types.IsInteger|types.IsFloat|types.IsComplex) != 0: + return "number" + case t.Info()&types.IsString != 0: + return "string" + case t.Kind() == types.UntypedNil: + return "null" + default: + return "unknown" + } +} + +func (r *typeRenderer) namedType(named *types.Named) string { + obj := named.Obj() + qualifiedName := obj.Pkg().Path() + "." + obj.Name() + switch qualifiedName { + case r.apiPackagePath + ".DocumentIdentifier": + r.documentIdentifier = obj + return "DocumentIdentifier" + case "github.com/microsoft/typescript-go/internal/packagejson.JSONValue": + return "unknown" + case "github.com/microsoft/typescript-go/internal/core.Tristate": + return "boolean" + case "github.com/microsoft/typescript-go/internal/core.JsxEmit": + return r.importType("JsxEmit", "#enums/jsxEmit") + case "github.com/microsoft/typescript-go/internal/core.ModuleDetectionKind": + return r.importType("ModuleDetectionKind", "#enums/moduleDetectionKind") + case "github.com/microsoft/typescript-go/internal/core.ModuleKind": + return r.importType("ModuleKind", "#enums/moduleKind") + case "github.com/microsoft/typescript-go/internal/core.ModuleResolutionKind": + return r.importType("ModuleResolutionKind", "#enums/moduleResolutionKind") + case "github.com/microsoft/typescript-go/internal/core.NewLineKind": + return r.importType("NewLineKind", "#enums/newLineKind") + case "github.com/microsoft/typescript-go/internal/core.ScriptTarget": + return r.importType("ScriptTarget", "#enums/scriptTarget") + case "github.com/microsoft/typescript-go/internal/collections.OrderedMap": + if named.TypeArgs().Len() != 2 { + return "Record" + } + return fmt.Sprintf("Record", r.typeString(named.TypeArgs().At(1), false)) + } + if _, ok := named.Underlying().(*types.Struct); !ok { + if literals := r.stringLiterals(named); len(literals) > 0 { + return strings.Join(literals, " | ") + } + return r.typeString(named.Underlying(), false) + } + tsName := exportedName(obj.Name()) + if previous := r.names[tsName]; previous != nil && previous != obj { + panic(fmt.Sprintf("TypeScript name collision between %s and %s", previous, obj)) + } + r.names[tsName] = obj + if !r.seen[obj] { + r.seen[obj] = true + r.queued = append(r.queued, named) + } + return tsName +} + +func (r *typeRenderer) stringLiterals(named *types.Named) []string { + if basic, ok := named.Underlying().(*types.Basic); !ok || basic.Info()&types.IsString == 0 { + return nil + } + pkg := r.packages[named.Obj().Pkg().Path()] + if pkg == nil { + return nil + } + var literals []string + for _, name := range pkg.Types.Scope().Names() { + constantObject, ok := pkg.Types.Scope().Lookup(name).(*types.Const) + if !ok || constantObject.Val().Kind() != constant.String || !types.Identical(constantObject.Type(), named) { + continue + } + literal := strconv.Quote(constant.StringVal(constantObject.Val())) + if !slices.Contains(literals, literal) { + literals = append(literals, literal) + } + } + sort.Strings(literals) + return literals +} + +func (r *typeRenderer) inlineStruct(structType *types.Struct) string { + var fields []string + multiline := false + for i := range structType.NumFields() { + field, include, optional, nonnil, deprecated, internal := jsonField(structType, i) + if !include || deprecated || internal { + continue + } + fieldType := r.typeString(structType.Field(i).Type(), !optional && !nonnil) + doc := r.docs[structType.Field(i)] + multiline = multiline || doc != "" + fields = append(fields, fmt.Sprintf("%s%s%s: %s", inlineDoc(doc), propertyName(field), optionalMarker(optional), fieldType)) + } + if len(fields) == 0 { + return "Record" + } + if multiline { + return "{\n" + strings.Join(fields, ";\n") + ";\n}" + } + return "{ " + strings.Join(fields, "; ") + "; }" +} + +func inlineDoc(doc string) string { + if doc == "" { + return "" + } + doc = strings.ReplaceAll(doc, "*/", "*\\/") + return "/** " + jsDocLine(strings.Join(strings.Fields(doc), " ")) + " */\n" +} + +func (r *typeRenderer) declarations() (string, error) { + var out bytes.Buffer + if strings.Contains(strings.Join(r.referencedNames(), ","), "DocumentIdentifier") { + writeDoc(&out, "", r.docs[r.documentIdentifier]) + out.WriteString("export type DocumentIdentifier = string | { uri: string; };\n\n") + } + for len(r.queued) > 0 { + named := r.queued[0] + r.queued = r.queued[1:] + structType := named.Underlying().(*types.Struct) + isParams := strings.HasSuffix(named.Obj().Name(), "Params") + writeDoc(&out, "", r.docs[named.Obj()]) + fmt.Fprintf(&out, "export interface %s {\n", exportedName(named.Obj().Name())) + for i := range structType.NumFields() { + field, include, optional, nonnil, deprecated, internal := jsonField(structType, i) + if !include || deprecated || internal { + continue + } + fieldType := r.typeString(structType.Field(i).Type(), !optional && !nonnil) + if isParams && isArrayType(structType.Field(i).Type()) { + fieldType = "readonly " + fieldType + } + writeDoc(&out, " ", r.docs[structType.Field(i)]) + fmt.Fprintf(&out, " %s%s: %s;\n", propertyName(field), optionalMarker(optional), fieldType) + } + out.WriteString("}\n\n") + } + return strings.TrimRight(out.String(), "\n") + "\n", nil +} + +func writeDoc(out *bytes.Buffer, indent string, doc string) { + if doc == "" { + return + } + doc = strings.ReplaceAll(doc, "*/", "*\\/") + lines := strings.Split(doc, "\n") + if len(lines) == 1 { + fmt.Fprintf(out, "%s/** %s */\n", indent, jsDocLine(lines[0])) + return + } + fmt.Fprintf(out, "%s/**\n", indent) + for _, line := range lines { + fmt.Fprintf(out, "%s * %s\n", indent, jsDocLine(line)) + } + fmt.Fprintf(out, "%s */\n", indent) +} + +func jsDocLine(line string) string { + if after, ok := strings.CutPrefix(line, "Deprecated:"); ok { + return "@deprecated" + after + } + return line +} + +func (r *typeRenderer) importDeclarations() string { + var out bytes.Buffer + paths := make([]string, 0, len(r.imports)) + for path := range r.imports { + paths = append(paths, path) + } + sort.Strings(paths) + for _, path := range paths { + names := r.imports[path] + sort.Strings(names) + fmt.Fprintf(&out, "import type { %s } from %q;\n", strings.Join(names, ", "), path) + } + return out.String() +} + +func (r *typeRenderer) importType(name string, path string) string { + if !slices.Contains(r.imports[path], name) { + r.imports[path] = append(r.imports[path], name) + } + return name +} + +func (r *typeRenderer) referencedNames() []string { + names := make([]string, 0, len(r.names)+1) + for name := range r.names { + names = append(names, name) + } + // DocumentIdentifier is represented specially and is not in names. + names = append(names, "DocumentIdentifier") + sort.Strings(names) + return names +} + +func jsonField(structType *types.Struct, index int) (name string, include bool, optional bool, nonnil bool, deprecated bool, internal bool) { + field := structType.Field(index) + if !field.Exported() { + return "", false, false, false, false, false + } + tag := reflect.StructTag(structType.Tag(index)).Get("json") + noniltag := reflect.StructTag(structType.Tag(index)).Get("nonnil") + deprecatedtag := reflect.StructTag(structType.Tag(index)).Get("deprecated") + internaltag := reflect.StructTag(structType.Tag(index)).Get("internal") + parts := strings.Split(tag, ",") + name = parts[0] + if name == "-" { + return "", false, false, noniltag == "true", deprecatedtag == "true", internaltag == "true" + } + if name == "" { + name = field.Name() + } + for _, option := range parts[1:] { + if option == "omitempty" || option == "omitzero" { + optional = true + } + } + return name, true, optional, noniltag == "true", deprecatedtag == "true", internaltag == "true" +} + +func exportedName(value string) string { + if value == "" { + return value + } + return strings.ToUpper(value[:1]) + value[1:] +} + +func optionalMarker(optional bool) string { + if optional { + return "?" + } + return "" +} + +func propertyName(name string) string { + for i, char := range name { + if (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || char == '_' || char == '$' || (i > 0 && char >= '0' && char <= '9') { + continue + } + return strconv.Quote(name) + } + return name +} + +func arrayElement(element string) string { + if strings.Contains(element, " | ") { + return "(" + element + ")" + } + return element +} + +func isArrayType(t types.Type) bool { + switch types.Unalias(t).(type) { + case *types.Array, *types.Slice: + return true + default: + return false + } +} diff --git a/_tools/gen-proto/main_test.go b/_tools/gen-proto/main_test.go new file mode 100644 index 00000000000..3ad1017ef5a --- /dev/null +++ b/_tools/gen-proto/main_test.go @@ -0,0 +1,105 @@ +package main + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestGenerate(t *testing.T) { + t.Parallel() + + repoRoot := filepath.Clean(filepath.Join("..", "..")) + input := filepath.Join(repoRoot, "internal", "api", "proto.go") + output := filepath.Join(t.TempDir(), "proto.generated.ts") + + err := generate(input, output) + if err != nil { + t.Fatal(err) + } + first, err := os.ReadFile(output) + if err != nil { + t.Fatal(err) + } + generated := strings.ReplaceAll(string(first), "\r\n", "\n") + + for _, expected := range []string{ + `release: APIMethod;`, + `updateSnapshot: APIMethod;`, + `initialize: APIMethod;`, + `export type DocumentIdentifier = string | { uri: string; };`, + `export interface ReleaseParams`, + `export interface UpdateSnapshotParams`, + `openProjects?: readonly DocumentIdentifier[];`, + `snapshot: number;`, + `file: DocumentIdentifier;`, + `import type { JsxEmit } from "#enums/jsxEmit";`, + `import type { ModuleDetectionKind } from "#enums/moduleDetectionKind";`, + `import type { ModuleKind } from "#enums/moduleKind";`, + `import type { ModuleResolutionKind } from "#enums/moduleResolutionKind";`, + `import type { NewLineKind } from "#enums/newLineKind";`, + `import type { ScriptTarget } from "#enums/scriptTarget";`, + `jsx?: JsxEmit;`, + `module?: ModuleKind;`, + `moduleResolution?: ModuleResolutionKind;`, + `moduleDetection?: ModuleDetectionKind;`, + `newLine?: NewLineKind;`, + `paths?: Record;`, + `target?: ScriptTarget;`, + `/** InitializeResponse is returned by the initialize method. */ +export interface InitializeResponse`, + `/** UseCaseSensitiveFileNames indicates whether the host file system is case-sensitive. */ + useCaseSensitiveFileNames: boolean;`, + `/** CompilerOptions contains the compiler options exposed by the API. */ +export interface CompilerOptions`, + `projectReferences?: ProjectReference[];`, + `errors: DiagnosticResponse[];`, + `getSymbolsAtPositions: APIMethod;`, + `getContextualType: APIMethod;`, + `getTypePredicateOfSignature: APIMethod;`, + `getTypeParametersOfType: APIMethod;`, + `getTypeOfSymbol: APIMethod;`, + `getSourceFile: APIMethod;`, + `getConfigSourceFile: APIMethod;`, + `typeToTypeNode: APIMethod;`, + `signatureToSignatureDeclaration: APIMethod;`, + `export interface SourceFileResponse { + /** Data is the base64-encoded binary AST data in the encoder's format. */ + data: string; +}`, + `projects: ProjectResponse[];`, + `entries: CompletionEntryResponse[];`, + `outputFiles: EmitOutputFile[];`, + `/** Path is a normalized path on disk. */ + path: string;`, + `/** Snapshot is the current client snapshot on which to layer the temporary update. */ + snapshot: number;`, + `kind: "importSymbol";`, + } { + if !strings.Contains(generated, expected) { + t.Errorf("generated output does not contain %q", expected) + } + } + if strings.Contains(generated, "snapshot: number | null;") { + t.Error("required numeric fields must not be nullable") + } + if strings.Contains(generated, " | null)[]") { + t.Error("list elements must not be nullable") + } + if strings.Contains(generated, "projects: readonly ProjectResponse[];") { + t.Error("response array fields must remain mutable") + } + + err = generate(input, output) + if err != nil { + t.Fatal(err) + } + second, err := os.ReadFile(output) + if err != nil { + t.Fatal(err) + } + if string(first) != string(second) { + t.Error("generation is not deterministic") + } +} diff --git a/internal/api/proto.go b/internal/api/proto.go index c7a45330b7a..b0578d1b591 100644 --- a/internal/api/proto.go +++ b/internal/api/proto.go @@ -1,5 +1,7 @@ package api +//go:generate npx hereby generate:api + import ( "errors" "fmt" @@ -59,17 +61,6 @@ func parseProjectHandle(handle ProjectID) tspath.Path { const ( MethodRelease Method = "release" - // MethodGetServerTiming retrieves the server's collected per-request - // processing-time totals and recent-request ring buffer. It is handled by - // the connection itself (not the session) and is not recorded in the timing - // it reports. - MethodGetServerTiming Method = "getServerTiming" - - // MethodResetServerTiming clears the server's collected timing totals and - // recent-request ring buffer. Like MethodGetServerTiming, it is handled by - // the connection itself and is not recorded. - MethodResetServerTiming Method = "resetServerTiming" - MethodInitialize Method = "initialize" MethodUpdateSnapshot Method = "updateSnapshot" MethodUpdateTemporarySnapshot Method = "updateTemporarySnapshot" @@ -242,6 +233,16 @@ type InitializeResponse struct { // DocumentIdentifier identifies a document by either a file name (plain string) or a URI object. // On the wire it is string | { uri: string }. +// +// @example +// +// Using a file name: +// +// project.program.getSourceFile("/path/to/file.ts"); +// +// Using a URI: +// +// project.program.getSourceFile({ uri: "file:///path/to/file.ts" }); type DocumentIdentifier struct { FileName string `json:"fileName,omitempty"` URI lsproto.DocumentUri `json:"uri,omitempty"` @@ -392,7 +393,7 @@ type UpdateSnapshotResponse struct { // Snapshot is the handle for the newly created snapshot. Snapshot SnapshotID `json:"snapshot"` // Projects is the list of projects in the snapshot. - Projects []*ProjectResponse `json:"projects"` + Projects []*ProjectResponse `json:"projects" nonnil:"true"` // Changes describes source file differences from the previous snapshot. // Nil for the first snapshot in a session. Changes *SnapshotChanges `json:"changes,omitempty"` @@ -621,13 +622,13 @@ type ProfileResult struct { } type ConfigFileResponse struct { - FileNames []string `json:"fileNames"` - Options *core.CompilerOptions `json:"options"` + FileNames []string `json:"fileNames" nonnil:"true"` + Options *core.CompilerOptions `json:"options" nonnil:"true"` ProjectReferences []*core.ProjectReference `json:"projectReferences,omitempty"` TypeAcquisition *core.TypeAcquisition `json:"typeAcquisition,omitempty"` CompileOnSave *bool `json:"compileOnSave,omitempty"` Raw any `json:"raw,omitempty"` - Errors []*DiagnosticResponse `json:"errors"` + Errors []*DiagnosticResponse `json:"errors" nonnil:"true"` } type ReadConfigFileResponse struct { @@ -641,11 +642,13 @@ type GetDefaultProjectForFileParams struct { } type ProjectResponse struct { - Id ProjectID `json:"id"` - ConfigFileName string `json:"configFileName"` - ParsedCommandLine *ConfigFileResponse `json:"parsedCommandLine"` - RootFiles []string `json:"rootFiles"` - CompilerOptions *core.CompilerOptions `json:"compilerOptions"` + Id ProjectID `json:"id"` + ConfigFileName string `json:"configFileName"` + ParsedCommandLine *ConfigFileResponse `json:"parsedCommandLine" nonnil:"true"` + // Deprecated: Use parsedCommandLine.fileNames. + RootFiles []string `json:"rootFiles" nonnil:"true"` + // Deprecated: Use parsedCommandLine.options. + CompilerOptions *core.CompilerOptions `json:"compilerOptions" nonnil:"true"` } func NewConfigFileResponse(parsedCommandLine *tsoptions.ParsedCommandLine) *ConfigFileResponse { @@ -753,7 +756,9 @@ type GetSymbolsAtLocationsParams struct { } type SymbolResponse struct { - Id SymbolID `json:"id"` + Id SymbolID `json:"id"` + // Project is the project in which the symbol was first observed. It is the + // default project for follow-up lookups whose results can vary by project. Project ProjectID `json:"project"` Name string `json:"name"` Flags uint32 `json:"flags"` @@ -792,7 +797,8 @@ type TypeResponse struct { Flags uint32 `json:"flags"` ObjectFlags uint32 `json:"objectFlags,omitempty"` - // LiteralType data + // Value is literal type data. BigInt literals are encoded as signed decimal + // strings because JSON cannot represent bigint; absent values are null. Value any `json:"value"` // ObjectType / TypeReference / StringMappingType / IndexType target @@ -1063,7 +1069,7 @@ type GetReferencedSymbolsForNodeParams struct { type ReferencedSymbolEntry struct { Definition NodeHandle `json:"definition"` Symbol *SymbolResponse `json:"symbol,omitempty"` - References []NodeHandle `json:"references"` + References []NodeHandle `json:"references" nonnil:"true"` } // GetSignatureUsagesParams are the parameters for the getSignatureUsages method. @@ -1110,7 +1116,7 @@ type CompletionEntryResponse struct { // CompletionInfoResponse wraps a list of completion entries. type CompletionInfoResponse struct { IsIncomplete bool `json:"isIncomplete"` - Entries []*CompletionEntryResponse `json:"entries"` + Entries []*CompletionEntryResponse `json:"entries" nonnil:"true"` } // GetIntrinsicTypeParams is used for intrinsic type getters (anyType, stringType, etc.). @@ -1291,8 +1297,8 @@ type SelectedFilesEmitParams struct { type EmitResponse struct { EmitSkipped bool `json:"emitSkipped"` - Diagnostics []*DiagnosticResponse `json:"diagnostics"` - EmittedFiles []string `json:"emittedFiles"` + Diagnostics []*DiagnosticResponse `json:"diagnostics" nonnil:"true"` + EmittedFiles []string `json:"emittedFiles" nonnil:"true"` } type EmitOutputFile struct { @@ -1303,8 +1309,8 @@ type EmitOutputFile struct { type EmitOutputResponse struct { EmitSkipped bool `json:"emitSkipped"` - Diagnostics []*DiagnosticResponse `json:"diagnostics"` - OutputFiles []*EmitOutputFile `json:"outputFiles"` + Diagnostics []*DiagnosticResponse `json:"diagnostics" nonnil:"true"` + OutputFiles []*EmitOutputFile `json:"outputFiles" nonnil:"true"` } // FormatNodeForInsertionParams are the parameters for the formatNodeForInsertion method. @@ -1386,6 +1392,7 @@ type IndexInfoResponse struct { // SourceFileResponse contains the binary-encoded AST data for a source file. // The Data field is base64-encoded binary data in the encoder's format. type SourceFileResponse struct { + // Data is the base64-encoded binary AST data in the encoder's format. Data string `json:"data"` } diff --git a/internal/api/session.go b/internal/api/session.go index 3bb0670ebee..70a602012fb 100644 --- a/internal/api/session.go +++ b/internal/api/session.go @@ -1141,6 +1141,7 @@ func (s *Session) handleRelease(ctx context.Context, params *ReleaseParams) (any // handleGetDefaultProjectForFile returns the default project for a given file, // or nil if no project currently contains the file. +// @gen-proto-nullable func (s *Session) handleGetDefaultProjectForFile(ctx context.Context, params *GetDefaultProjectForFileParams) (*ProjectResponse, error) { sd, err := s.getSnapshotData(params.Snapshot) if err != nil { @@ -1279,6 +1280,8 @@ func transpileOutput(ctx context.Context, input string, options TranspileOptions } // handleGetSourceFile returns a source file from a project within a snapshot. +// @gen-proto-result: SourceFileResponse +// @gen-proto-nullable func (s *Session) handleGetSourceFile(ctx context.Context, params *GetSourceFileParams) (any, error) { sd, err := s.getSnapshotData(params.Snapshot) if err != nil { @@ -1294,6 +1297,7 @@ func (s *Session) handleGetSourceFile(ctx context.Context, params *GetSourceFile } // handleGetConfigFileNames returns tsconfig file names associated with the project's command line. +// @gen-proto-nullable func (s *Session) handleGetConfigFileNames(ctx context.Context, params *GetProjectDiagnosticsParams) ([]string, error) { sd, err := s.getSnapshotData(params.Snapshot) if err != nil { @@ -1318,6 +1322,8 @@ func (s *Session) handleGetConfigFileNames(ctx context.Context, params *GetProje } // handleGetConfigSourceFile returns a tsconfig source file associated with the project's command line. +// @gen-proto-result: SourceFileResponse +// @gen-proto-nullable func (s *Session) handleGetConfigSourceFile(ctx context.Context, params *GetSourceFileParams) (any, error) { sd, err := s.getSnapshotData(params.Snapshot) if err != nil { @@ -1401,6 +1407,7 @@ func (s *Session) handleGetSourceFileNames(ctx context.Context, params *GetSourc // handleGetSourceFileMetadata returns program-stored metadata for a single source file. // The client fetches this lazily per file and caches it. +// @gen-proto-nullable func (s *Session) handleGetSourceFileMetadata(ctx context.Context, params *GetSourceFileParams) (*SourceFileMetadata, error) { sd, err := s.getSnapshotData(params.Snapshot) if err != nil { @@ -1428,6 +1435,7 @@ func (s *Session) handleGetSourceFileMetadata(ctx context.Context, params *GetSo } // handleGetSymbolAtPosition returns the symbol at a position in a file. +// @gen-proto-nullable func (s *Session) handleGetSymbolAtPosition(ctx context.Context, params *GetSymbolAtPositionParams) (*SymbolResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -1456,6 +1464,7 @@ func (s *Session) handleGetSymbolAtPosition(ctx context.Context, params *GetSymb // handleGetSymbolOfSourceFile returns the module symbol for a source file, if any. // For non-module (script) files, returns nil. +// @gen-proto-nullable func (s *Session) handleGetSymbolOfSourceFile(ctx context.Context, params *GetSymbolOfSourceFileParams) (*SymbolResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -1527,6 +1536,7 @@ func (s *Session) handleGetSymbolsAtPositions(ctx context.Context, params *GetSy } // handleGetSymbolAtLocation returns the symbol at a node location. +// @gen-proto-nullable func (s *Session) handleGetSymbolAtLocation(ctx context.Context, params *GetSymbolAtLocationParams) (*SymbolResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -1631,6 +1641,7 @@ func (s *Session) handleGetDeclaredTypeOfSymbol(ctx context.Context, params *Get } // handleResolveName resolves a name to a symbol at a given location. +// @gen-proto-nullable func (s *Session) handleResolveName(ctx context.Context, params *ResolveNameParams) (*SymbolResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -1754,6 +1765,7 @@ func (s *Session) handleGetTypeAtLocations(ctx context.Context, params *GetTypeA } // handleGetTypeAtPosition returns the type at a position in a file. +// @gen-proto-nullable func (s *Session) handleGetTypeAtPosition(ctx context.Context, params *GetTypeAtPositionParams) (*TypeResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -1809,26 +1821,31 @@ func (s *Session) handleGetTypesAtPositions(ctx context.Context, params *GetType 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 }) } +// @gen-proto-nullable func (s *Session) handleGetMembersOfSymbol(ctx context.Context, params *GetSymbolPropertyParams) ([]*SymbolResponse, error) { return s.resolveSymbolTablePropertyOfSymbol(ctx, params, func(symbol *ast.Symbol) ast.SymbolTable { return symbol.Members }) } +// @gen-proto-nullable func (s *Session) handleGetExportsOfSymbol(ctx context.Context, params *GetSymbolPropertyParams) ([]*SymbolResponse, error) { return s.resolveSymbolTablePropertyOfSymbol(ctx, params, func(symbol *ast.Symbol) ast.SymbolTable { return symbol.Exports }) } +// @gen-proto-nullable func (s *Session) handleGetExportSymbolOfSymbol(_ context.Context, params *GetSymbolPropertyParams) (*SymbolResponse, error) { return s.resolveSymbolPropertyOfSymbol(params, func(sym *ast.Symbol) *ast.Symbol { return sym.ExportSymbol }) } +// @gen-proto-nullable func (s *Session) handleGetSymbolOfType(_ context.Context, params *GetTypePropertyParams) (*SymbolResponse, error) { return s.resolveSymbolPropertyOfType(params, (*checker.Type).Symbol) } @@ -1837,30 +1854,37 @@ func (s *Session) handleGetTargetOfType(_ context.Context, params *GetTypeProper return s.resolveTypePropertyOfType(params, (*checker.Type).Target) } +// @gen-proto-nullable func (s *Session) handleGetFreshTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { return s.resolveTypePropertyOfType(params, func(t *checker.Type) *checker.Type { return t.AsLiteralType().FreshType() }) } +// @gen-proto-nullable func (s *Session) handleGetRegularTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { return s.resolveTypePropertyOfType(params, func(t *checker.Type) *checker.Type { return t.AsLiteralType().RegularType() }) } +// @gen-proto-nullable func (s *Session) handleGetTypesOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) { return s.resolveTypeArrayPropertyOfType(params, (*checker.Type).Types) } +// @gen-proto-nullable func (s *Session) handleGetTypeParametersOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) { return s.resolveTypeArrayPropertyOfType(params, func(t *checker.Type) []*checker.Type { return t.AsInterfaceType().TypeParameters() }) } +// @gen-proto-nullable func (s *Session) handleGetOuterTypeParametersOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) { return s.resolveTypeArrayPropertyOfType(params, func(t *checker.Type) []*checker.Type { return t.AsInterfaceType().OuterTypeParameters() }) } +// @gen-proto-nullable func (s *Session) handleGetLocalTypeParametersOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) { return s.resolveTypeArrayPropertyOfType(params, func(t *checker.Type) []*checker.Type { return t.AsInterfaceType().LocalTypeParameters() }) } +// @gen-proto-nullable func (s *Session) handleGetAliasTypeArgumentsOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) { return s.resolveTypeArrayPropertyOfType(params, func(t *checker.Type) []*checker.Type { if t.Alias() == nil { @@ -1870,6 +1894,7 @@ func (s *Session) handleGetAliasTypeArgumentsOfType(_ context.Context, params *G }) } +// @gen-proto-nullable func (s *Session) handleGetAliasSymbolOfType(_ context.Context, params *GetTypePropertyParams) (*SymbolResponse, error) { return s.resolveSymbolPropertyOfType(params, func(t *checker.Type) *ast.Symbol { if t.Alias() == nil { @@ -1905,18 +1930,22 @@ func (s *Session) handleGetConstraintOfType(_ context.Context, params *GetTypePr return s.resolveTypePropertyOfType(params, func(t *checker.Type) *checker.Type { return t.AsSubstitutionType().SubstConstraint() }) } +// @gen-proto-nullable func (s *Session) handleGetTypeParametersOfSignature(_ context.Context, params *GetSignaturePropertyParams) ([]*TypeResponse, error) { return s.resolveTypeArrayPropertyOfSignature(params, (*checker.Signature).TypeParameters) } +// @gen-proto-nullable func (s *Session) handleGetParametersOfSignature(_ context.Context, params *GetSignaturePropertyParams) ([]*SymbolResponse, error) { return s.resolveSymbolArrayPropertyOfSignature(params, (*checker.Signature).Parameters) } +// @gen-proto-nullable func (s *Session) handleGetThisParameterOfSignature(_ context.Context, params *GetSignaturePropertyParams) (*SymbolResponse, error) { return s.resolveSymbolPropertyOfSignature(params, (*checker.Signature).ThisParameter) } +// @gen-proto-nullable func (s *Session) handleGetTargetOfSignature(_ context.Context, params *GetSignaturePropertyParams) (*SignatureResponse, error) { return s.resolveSignaturePropertyOfSignature(params, (*checker.Signature).Target) } @@ -2258,6 +2287,7 @@ func (s *Session) resolveSignaturePropertyOfSignature(params *GetSignatureProper } // handleGetContextualType returns the contextual type for a node. +// @gen-proto-nullable func (s *Session) handleGetContextualType(ctx context.Context, params *GetContextualTypeParams) (*TypeResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -2419,6 +2449,7 @@ func (s *Session) handleIsTypeAssignableTo(ctx context.Context, params *IsTypeAs } // handleGetShorthandAssignmentValueSymbol returns the value symbol of a shorthand property assignment. +// @gen-proto-nullable func (s *Session) handleGetShorthandAssignmentValueSymbol(ctx context.Context, params *GetTypeAtLocationParams) (*SymbolResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -2464,6 +2495,8 @@ func (s *Session) handleGetTypeOfSymbolAtLocation(ctx context.Context, params *G } // handleTypeToTypeNode converts a Type to a TypeNode AST and returns it as binary-encoded data. +// @gen-proto-result: SourceFileResponse +// @gen-proto-nullable func (s *Session) handleTypeToTypeNode(ctx context.Context, params *TypeToTypeNodeParams) (any, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -2502,6 +2535,8 @@ func (s *Session) handleTypeToTypeNode(ctx context.Context, params *TypeToTypeNo }, nil } +// @gen-proto-result: SourceFileResponse +// @gen-proto-nullable func (s *Session) handleSignatureToSignatureDeclaration(ctx context.Context, params *SignatureToSignatureDeclarationParams) (any, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -2873,6 +2908,7 @@ func (s *Session) handleGetRestTypeOfSignature(ctx context.Context, params *Chec } // handleGetTypePredicateOfSignature returns the type predicate of a signature. +// @gen-proto-nullable func (s *Session) handleGetTypePredicateOfSignature(ctx context.Context, params *CheckerSignatureParams) (*TypePredicateResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -2935,6 +2971,7 @@ func (s *Session) handleIsTupleType(ctx context.Context, params *CheckerTypePara } // handleGetBaseTypes returns the base types of an interface/class type. +// @gen-proto-nullable func (s *Session) handleGetBaseTypes(ctx context.Context, params *CheckerTypeParams) ([]*TypeResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -2961,6 +2998,7 @@ func (s *Session) handleGetBaseTypes(ctx context.Context, params *CheckerTypePar } // handleGetPropertiesOfType returns the properties of a type. +// @gen-proto-nullable func (s *Session) handleGetPropertiesOfType(ctx context.Context, params *CheckerTypeParams) ([]*SymbolResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -3025,6 +3063,7 @@ func (s *Session) handleGetApparentType(ctx context.Context, params *GetTypeProp } // handleGetIndexInfosOfType returns the index infos of a type. +// @gen-proto-nullable func (s *Session) handleGetIndexInfosOfType(ctx context.Context, params *CheckerTypeParams) ([]*IndexInfoResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -3058,6 +3097,7 @@ func (s *Session) handleGetIndexInfosOfType(ctx context.Context, params *Checker } // handleGetConstraintOfTypeParameter returns the constraint of a type parameter. +// @gen-proto-nullable func (s *Session) handleGetConstraintOfTypeParameter(ctx context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -3079,6 +3119,7 @@ func (s *Session) handleGetConstraintOfTypeParameter(ctx context.Context, params } // handleGetDefaultFromTypeParameter returns the default type of a type parameter. +// @gen-proto-nullable func (s *Session) handleGetDefaultFromTypeParameter(ctx context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -3095,6 +3136,7 @@ func (s *Session) handleGetDefaultFromTypeParameter(ctx context.Context, params } // handleGetBaseConstraintOfType returns the base constraint of an instantiable type. +// @gen-proto-nullable func (s *Session) handleGetBaseConstraintOfType(ctx context.Context, params *CheckerTypeParams) (*TypeResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -3116,6 +3158,7 @@ func (s *Session) handleGetBaseConstraintOfType(ctx context.Context, params *Che } // handleGetPropertyOfType returns a named property symbol of a type. +// @gen-proto-nullable func (s *Session) handleGetPropertyOfType(ctx context.Context, params *GetPropertyOfTypeParams) (*SymbolResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -3137,6 +3180,7 @@ func (s *Session) handleGetPropertyOfType(ctx context.Context, params *GetProper } // handleGetConstantValue returns the constant value of an enum member or const enum access. +// @gen-proto-nullable func (s *Session) handleGetConstantValue(ctx context.Context, params *CheckerNodeParams) (any, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -3172,6 +3216,7 @@ func (s *Session) handleGetSignatureFromDeclaration(ctx context.Context, params } // handleGetExportSpecifierLocalTargetSymbol returns the local target symbol of an export specifier. +// @gen-proto-nullable func (s *Session) handleGetExportSpecifierLocalTargetSymbol(ctx context.Context, params *CheckerNodeParams) (*SymbolResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -3232,6 +3277,7 @@ func (s *Session) handleGetFullyQualifiedName(ctx context.Context, params *Check } // handleGetImmediateAliasedSymbol resolves one level of alias indirection. +// @gen-proto-nullable func (s *Session) handleGetImmediateAliasedSymbol(ctx context.Context, params *CheckerSymbolParams) (*SymbolResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -3257,6 +3303,7 @@ func (s *Session) handleGetImmediateAliasedSymbol(ctx context.Context, params *C // handleGetExportsOfModule returns the resolved exports of a module symbol, // including those introduced by `export *` and re-exports. +// @gen-proto-nullable func (s *Session) handleGetExportsOfModule(ctx context.Context, params *CheckerSymbolParams) ([]*SymbolResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -3287,6 +3334,7 @@ func (s *Session) handleGetExportsOfModule(ctx context.Context, params *CheckerS } // handleGetMemberInModuleExports returns an export by name from a module symbol. +// @gen-proto-nullable func (s *Session) handleGetMemberInModuleExports(ctx context.Context, params *GetMemberInModuleExportsParams) (*SymbolResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -3311,6 +3359,7 @@ func (s *Session) handleGetMemberInModuleExports(ctx context.Context, params *Ge } // handleGetJSDocTags returns the JSDoc tags of a symbol as structured name/text pairs. +// @gen-proto-nullable func (s *Session) handleGetJSDocTags(ctx context.Context, params *CheckerSymbolParams) ([]*JSDocTagInfo, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -3357,6 +3406,7 @@ func (s *Session) handleGetDocumentationComment(ctx context.Context, params *Che } // handleGetTypeArguments returns the type arguments of a type reference. +// @gen-proto-nullable func (s *Session) handleGetTypeArguments(ctx context.Context, params *CheckerTypeParams) ([]*TypeResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) if err != nil { @@ -3607,32 +3657,38 @@ func (s *Session) getDiagnostics(ctx context.Context, params *GetDiagnosticsPara return NewDiagnosticResponses(getter(program, ctx, nil)), nil } +// @gen-proto-nullable func (s *Session) handleGetSyntacticDiagnostics(ctx context.Context, params *GetDiagnosticsParams) ([]*DiagnosticResponse, error) { ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics) return s.getDiagnostics(ctx, params, (*compiler.Program).GetSyntacticDiagnostics) } +// @gen-proto-nullable func (s *Session) handleGetBindDiagnostics(ctx context.Context, params *GetDiagnosticsParams) ([]*DiagnosticResponse, error) { ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics) return s.getDiagnostics(ctx, params, (*compiler.Program).GetBindDiagnostics) } +// @gen-proto-nullable func (s *Session) handleGetSemanticDiagnostics(ctx context.Context, params *GetDiagnosticsParams) ([]*DiagnosticResponse, error) { ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics) return s.getDiagnostics(ctx, params, (*compiler.Program).GetSemanticDiagnostics) } +// @gen-proto-nullable func (s *Session) handleGetSuggestionDiagnostics(ctx context.Context, params *GetDiagnosticsParams) ([]*DiagnosticResponse, error) { ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics) return s.getDiagnostics(ctx, params, (*compiler.Program).GetSuggestionDiagnostics) } +// @gen-proto-nullable func (s *Session) handleGetDeclarationDiagnostics(ctx context.Context, params *GetDiagnosticsParams) ([]*DiagnosticResponse, error) { ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics) return s.getDiagnostics(ctx, params, (*compiler.Program).GetDeclarationDiagnostics) } // handleGetConfigFileParsingDiagnostics returns config file parsing diagnostics. +// @gen-proto-nullable func (s *Session) handleGetConfigFileParsingDiagnostics(ctx context.Context, params *GetProjectDiagnosticsParams) ([]*DiagnosticResponse, error) { sd, err := s.getSnapshotData(params.Snapshot) if err != nil { @@ -3649,6 +3705,7 @@ func (s *Session) handleGetConfigFileParsingDiagnostics(ctx context.Context, par } // handleGetProgramDiagnostics returns program-wide diagnostics, including options diagnostics. +// @gen-proto-nullable func (s *Session) handleGetProgramDiagnostics(ctx context.Context, params *GetProjectDiagnosticsParams) ([]*DiagnosticResponse, error) { sd, err := s.getSnapshotData(params.Snapshot) if err != nil { @@ -3665,6 +3722,7 @@ func (s *Session) handleGetProgramDiagnostics(ctx context.Context, params *GetPr } // handleGetGlobalDiagnostics returns global (non-file-specific) semantic diagnostics. +// @gen-proto-nullable func (s *Session) handleGetGlobalDiagnostics(ctx context.Context, params *GetProjectDiagnosticsParams) ([]*DiagnosticResponse, error) { ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics) sd, err := s.getSnapshotData(params.Snapshot) @@ -3737,6 +3795,7 @@ func (s *Session) handleGetReferencesToSymbolInFile(ctx context.Context, params return result, nil } +// @gen-proto-nullable func (s *Session) handleGetSignatureUsages(ctx context.Context, params *GetSignatureUsagesParams) ([]SignatureUsageResponse, error) { sd, err := s.getSnapshotData(params.Snapshot) if err != nil { @@ -3779,6 +3838,7 @@ func (s *Session) handleGetSignatureUsages(ctx context.Context, params *GetSigna } // handleGetCompletionsAtPosition returns completions at a position in a document. +// @gen-proto-nullable func (s *Session) handleGetCompletionsAtPosition(ctx context.Context, params *GetCompletionsAtPositionParams) (*CompletionInfoResponse, error) { if params.IncludeSymbol { ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeAPI) @@ -3835,6 +3895,7 @@ func (s *Session) handleGetCompletionsAtPosition(ctx context.Context, params *Ge } // handleGetReferencedSymbolsForNode returns node handles for all references found at a node. +// @gen-proto-nullable func (s *Session) handleGetReferencedSymbolsForNode(ctx context.Context, params *GetReferencedSymbolsForNodeParams) ([]ReferencedSymbolEntry, error) { sd, err := s.getSnapshotData(params.Snapshot) if err != nil { diff --git a/internal/core/compileroptions.go b/internal/core/compileroptions.go index 8f865e62490..051fe5d600f 100644 --- a/internal/core/compileroptions.go +++ b/internal/core/compileroptions.go @@ -13,8 +13,7 @@ import ( //go:generate go tool golang.org/x/tools/cmd/stringer -type=ScriptTarget -trimprefix=ScriptTarget -output=scripttarget_stringer_generated.go //go:generate npx dprint fmt modulekind_stringer_generated.go scripttarget_stringer_generated.go -// Keep in sync with the API's compilerOptions.ts - +// CompilerOptions contains the compiler options exposed by the API. type CompilerOptions struct { _ noCopy @@ -120,45 +119,45 @@ type CompilerOptions struct { MaxNodeModuleJsDepth *int `json:"maxNodeModuleJsDepth,omitzero"` // Deprecated: Do not use outside of options parsing and validation. - AllowSyntheticDefaultImports Tristate `json:"allowSyntheticDefaultImports,omitzero"` + AllowSyntheticDefaultImports Tristate `json:"allowSyntheticDefaultImports,omitzero" deprecated:"true"` // Deprecated: Do not use outside of options parsing and validation. - AlwaysStrict Tristate `json:"alwaysStrict,omitzero"` + AlwaysStrict Tristate `json:"alwaysStrict,omitzero" deprecated:"true"` // Deprecated: Do not use outside of options parsing and validation. - BaseUrl string `json:"baseUrl,omitzero"` + BaseUrl string `json:"baseUrl,omitzero" deprecated:"true"` // Deprecated: Do not use outside of options parsing and validation. - DownlevelIteration Tristate `json:"downlevelIteration,omitzero"` + DownlevelIteration Tristate `json:"downlevelIteration,omitzero" deprecated:"true"` // Deprecated: Do not use outside of options parsing and validation. - ESModuleInterop Tristate `json:"esModuleInterop,omitzero"` + ESModuleInterop Tristate `json:"esModuleInterop,omitzero" deprecated:"true"` // Deprecated: Do not use outside of options parsing and validation. - OutFile string `json:"outFile,omitzero"` + OutFile string `json:"outFile,omitzero" deprecated:"true"` // Internal fields - ConfigFilePath string `json:"configFilePath,omitzero"` - NoDtsResolution Tristate `json:"noDtsResolution,omitzero"` - PathsBasePath string `json:"pathsBasePath,omitzero"` - Diagnostics Tristate `json:"diagnostics,omitzero"` - ExtendedDiagnostics Tristate `json:"extendedDiagnostics,omitzero"` - GenerateCpuProfile string `json:"generateCpuProfile,omitzero"` - GenerateTrace string `json:"generateTrace,omitzero"` - ListEmittedFiles Tristate `json:"listEmittedFiles,omitzero"` - ListFiles Tristate `json:"listFiles,omitzero"` - ExplainFiles Tristate `json:"explainFiles,omitzero"` - ListFilesOnly Tristate `json:"listFilesOnly,omitzero"` - NoEmitForJsFiles Tristate `json:"noEmitForJsFiles,omitzero"` - PreserveWatchOutput Tristate `json:"preserveWatchOutput,omitzero"` - Pretty Tristate `json:"pretty,omitzero"` - Version Tristate `json:"version,omitzero"` - Watch Tristate `json:"watch,omitzero"` - ShowConfig Tristate `json:"showConfig,omitzero"` - Build Tristate `json:"build,omitzero"` - Help Tristate `json:"help,omitzero"` - All Tristate `json:"all,omitzero"` - RunExternalCode Tristate `json:"runExternalCode,omitzero"` - - PprofDir string `json:"pprofDir,omitzero"` - SingleThreaded Tristate `json:"singleThreaded,omitzero"` - Quiet Tristate `json:"quiet,omitzero"` - Checkers *int `json:"checkers,omitzero"` + ConfigFilePath string `json:"configFilePath,omitzero"` // internal, but intentionally exposed via API + NoDtsResolution Tristate `json:"noDtsResolution,omitzero" internal:"true"` + PathsBasePath string `json:"pathsBasePath,omitzero" internal:"true"` + Diagnostics Tristate `json:"diagnostics,omitzero" internal:"true"` + ExtendedDiagnostics Tristate `json:"extendedDiagnostics,omitzero" internal:"true"` + GenerateCpuProfile string `json:"generateCpuProfile,omitzero" internal:"true"` + GenerateTrace string `json:"generateTrace,omitzero" internal:"true"` + ListEmittedFiles Tristate `json:"listEmittedFiles,omitzero" internal:"true"` + ListFiles Tristate `json:"listFiles,omitzero" internal:"true"` + ExplainFiles Tristate `json:"explainFiles,omitzero" internal:"true"` + ListFilesOnly Tristate `json:"listFilesOnly,omitzero" internal:"true"` + NoEmitForJsFiles Tristate `json:"noEmitForJsFiles,omitzero" internal:"true"` + PreserveWatchOutput Tristate `json:"preserveWatchOutput,omitzero" internal:"true"` + Pretty Tristate `json:"pretty,omitzero" internal:"true"` + Version Tristate `json:"version,omitzero" internal:"true"` + Watch Tristate `json:"watch,omitzero" internal:"true"` + ShowConfig Tristate `json:"showConfig,omitzero" internal:"true"` + Build Tristate `json:"build,omitzero" internal:"true"` + Help Tristate `json:"help,omitzero" internal:"true"` + All Tristate `json:"all,omitzero" internal:"true"` + RunExternalCode Tristate `json:"runExternalCode,omitzero" internal:"true"` + + PprofDir string `json:"pprofDir,omitzero" internal:"true"` + SingleThreaded Tristate `json:"singleThreaded,omitzero" internal:"true"` + Quiet Tristate `json:"quiet,omitzero" internal:"true"` + Checkers *int `json:"checkers,omitzero" internal:"true"` } // noCopy may be embedded into structs which must not be copied diff --git a/internal/core/projectreference.go b/internal/core/projectreference.go index 85bfa82838b..bab89fdd391 100644 --- a/internal/core/projectreference.go +++ b/internal/core/projectreference.go @@ -3,9 +3,12 @@ package core import "github.com/microsoft/typescript-go/internal/tspath" type ProjectReference struct { - Path string `json:"path"` + // Path is a normalized path on disk. + Path string `json:"path"` + // OriginalPath is the path as it was originally written. OriginalPath string `json:"originalPath"` - Circular bool `json:"circular"` + // Circular indicates that this reference is intended to form a circularity. + Circular bool `json:"circular"` } func ResolveProjectReferencePath(ref *ProjectReference) string {