UNPKG

typescript

Version:

TypeScript is a language for application scale JavaScript development

1,309 lines • 62.2 kB
/// <reference path="../node/node.ts" preserve="true" /> import { CompletionItemKind } from "#enums/completionItemKind"; import { DiagnosticCategory } from "#enums/diagnosticCategory"; import { ElementFlags } from "#enums/elementFlags"; import { ModuleKind } from "#enums/moduleKind"; import { NodeBuilderFlags } from "#enums/nodeBuilderFlags"; import { ObjectFlags } from "#enums/objectFlags"; import { SignatureFlags } from "#enums/signatureFlags"; import { SignatureKind } from "#enums/signatureKind"; import { SymbolFlags } from "#enums/symbolFlags"; import { TypeFlags } from "#enums/typeFlags"; import { TypePredicateKind } from "#enums/typePredicateKind"; import { ModifierFlags, unescapeLeadingUnderscores, } from "../../ast/index.js"; import { encodeNode, uint8ArrayToBase64, } from "../node/encoder.js"; import { decodeNode, getNodeId, parseNodeHandle, readParseOptionsKey, readSourceFileHash, RemoteSourceFile, } from "../node/node.js"; import { Wtf8Decoder } from "../node/wtf8.js"; import { createGetCanonicalFileName, toPath, } from "../path.js"; import { resolveFileName, toUpdateSnapshotRequest, } from "../proto.js"; import { SourceFileCache } from "../sourceFileCache.js"; import { Client, } from "./client.js"; export { documentURIToFileName, fileNameToDocumentURI } from "../path.js"; export { CompletionItemKind, DiagnosticCategory, ElementFlags, ModifierFlags, ModuleKind, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind }; export class API { client; sourceFileCache; toPath; initialized = false; activeSnapshots = new Set(); latestSnapshot; internal; constructor(options = {}) { this.client = new Client(options); this.sourceFileCache = new SourceFileCache(); this.internal = new InternalAPI(this.client, () => this.ensureInitialized()); } /** * Create an API instance from an existing LSP connection's API session. * Use this when connecting to an API pipe provided by an LSP server via custom/initializeAPISession. */ static async fromLSPConnection(options) { const api = new API(options); await api.ensureInitialized(); return api; } async ensureInitialized() { if (!this.initialized) { const response = await this.client.apiRequest("initialize", null); const getCanonicalFileName = createGetCanonicalFileName(response.useCaseSensitiveFileNames); const currentDirectory = response.currentDirectory; this.toPath = (fileName) => toPath(fileName, currentDirectory, getCanonicalFileName); this.initialized = true; } } async parseConfigFile(file) { await this.ensureInitialized(); return this.client.apiRequest("parseConfigFile", { file }); } async updateSnapshot(params) { await this.ensureInitialized(); const requestParams = toUpdateSnapshotRequest(params); const data = await this.client.apiRequest("updateSnapshot", requestParams); // Retain cached source files from previous snapshot for unchanged files if (this.latestSnapshot) { this.sourceFileCache.retainForSnapshot(data.snapshot, this.latestSnapshot.id, data.changes); if (this.latestSnapshot.isDisposed()) { this.sourceFileCache.releaseSnapshot(this.latestSnapshot.id); } } const snapshot = new Snapshot(data, this.client, this.sourceFileCache, this.toPath, () => { this.activeSnapshots.delete(snapshot); if (snapshot !== this.latestSnapshot) { this.sourceFileCache.releaseSnapshot(snapshot.id); } }); this.latestSnapshot = snapshot; this.activeSnapshots.add(snapshot); return snapshot; } async close() { // Dispose all active snapshots for (const snapshot of [...this.activeSnapshots]) { await snapshot.dispose(); } // Release the latest snapshot's cache refs if still held if (this.latestSnapshot) { this.sourceFileCache.releaseSnapshot(this.latestSnapshot.id); this.latestSnapshot = undefined; } await this.client.close(); this.sourceFileCache.clear(); } clearSourceFileCache() { this.sourceFileCache.clear(); } /** * Returns a snapshot of collected timing information for requests made * through this API instance: client-measured round-trip latency and bytes * transferred, folded together with the server's own per-request processing * time and an estimated transport overhead (round-trip minus server time). * * Fetching the snapshot issues a lightweight request to the server to * retrieve its timing collection. Collection must be enabled via the * `collectTiming` option; when it is not, the returned snapshot has * `enabled: false` and zeroed totals. */ getTimingInfo() { return this.client.getTimingInfo(); } /** Clears all accumulated timing totals and recent-request history, on both the client and the server. */ resetTimingInfo() { return this.client.resetTimingInfo(); } } export class InternalAPI { client; ensureInitialized; /** @internal */ constructor(client, ensureInitialized) { this.client = client; this.ensureInitialized = ensureInitialized; } async startCPUProfile(dir) { await this.ensureInitialized(); await this.client.apiRequest("startCPUProfile", { dir }); } async stopCPUProfile() { await this.ensureInitialized(); const result = await this.client.apiRequest("stopCPUProfile", null); return result.file; } async saveHeapProfile(dir) { await this.ensureInitialized(); const result = await this.client.apiRequest("saveHeapProfile", { dir }); return result.file; } } export class Snapshot { id; projectMap; toPath; client; disposed = false; onDispose; snapshotRegistry; constructor(data, client, sourceFileCache, toPath, onDispose) { this.id = data.snapshot; this.client = client; this.toPath = toPath; this.onDispose = onDispose; this.projectMap = new Map(); this.snapshotRegistry = new SnapshotObjectRegistry(client, this.id, projectId => this.projectMap.get(projectId)); for (const projData of data.projects) { const project = new Project(projData, this.id, client, sourceFileCache, toPath, this.snapshotRegistry); this.projectMap.set(toPath(projData.configFileName), project); } } getProjects() { this.ensureNotDisposed(); return [...this.projectMap.values()]; } getProject(configFileName) { this.ensureNotDisposed(); return this.projectMap.get(this.toPath(configFileName)); } async getDefaultProjectForFile(file) { this.ensureNotDisposed(); const data = await this.client.apiRequest("getDefaultProjectForFile", { snapshot: this.id, file, }); if (!data) return undefined; return this.projectMap.get(this.toPath(data.configFileName)); } [globalThis.Symbol.dispose]() { this.dispose(); } async dispose() { if (this.disposed) return; this.disposed = true; for (const project of this.projectMap.values()) { project.dispose(); } this.projectMap.clear(); this.snapshotRegistry.clear(); this.onDispose(); await this.client.apiRequest("release", { snapshot: this.id }); } isDisposed() { return this.disposed; } ensureNotDisposed() { if (this.disposed) { throw new Error("Snapshot is disposed"); } } } class SnapshotObjectRegistry { symbols = new Map(); client; snapshotId; resolveProject; constructor(client, snapshotId, resolveProject) { this.client = client; this.snapshotId = snapshotId; this.resolveProject = resolveProject; } /** Resolve a project id (a config file path) to its Project within this snapshot. */ getProject(projectId) { return this.resolveProject(projectId); } getOrCreateSymbol(data) { let symbol = this.symbols.get(data.id); if (!symbol) { symbol = new Symbol(data, this); this.symbols.set(data.id, symbol); } return symbol; } getSymbol(id) { return this.symbols.get(id); } clear() { this.symbols.clear(); } async fetchSymbol(source, method, handle, projectId) { if (!handle) return undefined; const cached = this.getSymbol(handle); if (cached) return cached; const data = await this.client.apiRequest(method, { snapshot: this.snapshotId, project: projectId, objectId: source.id, }); if (!data) throw new Error(`${method} returned null symbol for ${source.constructor.name} ${source.id}`); return this.getOrCreateSymbol(data); } async fetchSymbols(source, method, handles, projectId) { if (handles) { const result = new Array(handles.length); let allCached = true; for (let i = 0; i < handles.length; i++) { const cached = this.getSymbol(handles[i]); if (!cached) { allCached = false; break; } result[i] = cached; } if (allCached) return result; } const symbolData = await this.client.apiRequest(method, { snapshot: this.snapshotId, project: projectId, objectId: source.id, }); if (symbolData == null) return []; else return symbolData.map(data => this.getOrCreateSymbol(data)); } } class ProjectObjectRegistry { client; snapshotId; project; snapshotRegistry; types = new Map(); signatures = new Map(); constructor(client, snapshotId, project, snapshotRegistry) { this.client = client; this.snapshotId = snapshotId; this.project = project; this.snapshotRegistry = snapshotRegistry; } getOrCreateSymbol(data) { return this.snapshotRegistry.getOrCreateSymbol(data); } getSymbol(id) { return this.snapshotRegistry.getSymbol(id); } getOrCreateType(data) { let type = this.types.get(data.id); if (!type) { type = new TypeObject(data, this); this.types.set(data.id, type); } return type; } getType(id) { return this.types.get(id); } getOrCreateSignature(data) { let sig = this.signatures.get(data.id); if (!sig) { sig = new Signature(data, this.project, this); this.signatures.set(data.id, sig); } return sig; } getSignature(id) { return this.signatures.get(id); } clear() { this.types.clear(); this.signatures.clear(); } async fetchType(source, method, handle) { if (handle !== false) { if (!handle) return undefined; const cached = this.getType(handle); if (cached) return cached; } const data = await this.client.apiRequest(method, { snapshot: this.snapshotId, project: this.project.id, objectId: source.id, }); if (!data) throw new Error(`${method} returned null type for ${source.constructor.name} ${source.id}`); return this.getOrCreateType(data); } async fetchSymbol(source, method, handle) { return this.snapshotRegistry.fetchSymbol(source, method, handle, this.project.id); } async fetchSignature(source, method, handle) { if (!handle) return undefined; const cached = this.getSignature(handle); if (cached) return cached; const data = await this.client.apiRequest(method, { snapshot: this.snapshotId, project: this.project.id, objectId: source.id, }); if (!data) throw new Error(`${method} returned null signature for ${source.constructor.name} ${source.id}`); return this.getOrCreateSignature(data); } async fetchTypes(source, method, handles) { if (handles) { const result = new Array(handles.length); let allCached = true; for (let i = 0; i < handles.length; i++) { const cached = this.getType(handles[i]); if (!cached) { allCached = false; break; } result[i] = cached; } if (allCached) return result; } const typesData = await this.client.apiRequest(method, { snapshot: this.snapshotId, project: this.project.id, objectId: source.id, }); if (typesData == null) return []; else return typesData.map(data => this.getOrCreateType(data)); } async fetchSymbols(source, method, handles) { 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) { const typesData = await this.client.apiRequest("getBaseTypes", { snapshot: this.snapshotId, project: this.project.id, type: source.id, }); if (typesData == null) return []; return typesData.map(data => this.getOrCreateType(data)); } } export class Project { id; configFileName; compilerOptions; rootFiles; program; checker; emitter; client; constructor(data, snapshotId, client, sourceFileCache, toPath, snapshotRegistry) { this.id = data.id; this.configFileName = data.configFileName; this.compilerOptions = data.compilerOptions; this.rootFiles = data.rootFiles; this.client = client; this.program = new Program(snapshotId, this, client, sourceFileCache, toPath); const objectRegistry = new ProjectObjectRegistry(client, snapshotId, this, snapshotRegistry); this.checker = new Checker(snapshotId, this, client, objectRegistry); this.emitter = new Emitter(client); } dispose() { this.checker.dispose(); } } export class Program { snapshotId; project; client; sourceFileCache; toPath; decoder = new Wtf8Decoder(); sourceFileMetadataCache = new Map(); constructor(snapshotId, project, client, sourceFileCache, toPath) { this.snapshotId = snapshotId; this.project = project; this.client = client; this.sourceFileCache = sourceFileCache; this.toPath = toPath; } getCompilerOptions() { return this.project.compilerOptions; } async getSourceFile(file) { const fileName = resolveFileName(file); const path = this.toPath(fileName); // Check if we already have a retained cache entry for this (snapshot, project) pair const retained = this.sourceFileCache.getRetained(path, this.snapshotId, this.project.id); if (retained) { return retained; } // Fetch from server const binaryData = await this.client.apiRequestBinary("getSourceFile", { snapshot: this.snapshotId, project: this.project.id, file, }); if (!binaryData) { return undefined; } const view = new DataView(binaryData.buffer, binaryData.byteOffset, binaryData.byteLength); const contentHash = readSourceFileHash(view); const parseOptionsKey = readParseOptionsKey(view); // Create a new RemoteSourceFile and cache it (set returns existing if hash matches) const sourceFile = new RemoteSourceFile(binaryData, this.decoder, this.client.getTimingCollector()); return this.sourceFileCache.set(path, sourceFile, parseOptionsKey, contentHash, this.snapshotId, this.project.id); } async getSourceFileNames() { const data = await this.client.apiRequest("getSourceFileNames", { snapshot: this.snapshotId, project: this.project.id, }); return data ?? []; } /** * Returns program-stored metadata for the given source file, or `undefined` if the file * is not part of the program. Metadata is fetched lazily per file and cached on this * `Program` instance. */ getSourceFileMetadata(fileName) { return this.getSourceFileMetadataByPath(this.toPath(fileName)); } /** * Returns program-stored metadata for the source file at the given path, or `undefined` * if the file is not part of the program. Like {@link getSourceFileMetadata}, but skips * the file name to path conversion. Metadata is fetched lazily per file and cached on * this `Program` instance. */ getSourceFileMetadataByPath(path) { let metadata = this.sourceFileMetadataCache.get(path); if (metadata === undefined) { metadata = this.fetchSourceFileMetadata(path); this.sourceFileMetadataCache.set(path, metadata); } return metadata; } async fetchSourceFileMetadata(path) { const data = await this.client.apiRequest("getSourceFileMetadata", { snapshot: this.snapshotId, project: this.project.id, file: path, }); return data ?? undefined; } /** * Returns whether the given source file was loaded as part of an external library * (e.g. a dependency resolved from `node_modules`). The underlying program metadata is * fetched lazily per file and cached on this `Program` instance. */ async isSourceFileFromExternalLibrary(file) { const metadata = await this.getSourceFileMetadataByPath(file.path); return metadata?.isFromExternalLibrary ?? false; } /** * Returns whether the given source file is a default library file (e.g. `lib.d.ts`). * The underlying program metadata is fetched lazily per file and cached on this * `Program` instance. */ async isSourceFileDefaultLibrary(file) { const metadata = await this.getSourceFileMetadataByPath(file.path); return metadata?.isDefaultLibrary ?? false; } /** * 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. */ async getSyntacticDiagnostics(file) { const data = await this.client.apiRequest("getSyntacticDiagnostics", { snapshot: this.snapshotId, project: this.project.id, ...(file !== undefined ? { file } : {}), }); return data ?? []; } /** * Get binder diagnostics for a specific file or all files. * @param file - Optional file to get diagnostics for. If omitted, returns diagnostics for all files. */ async getBindDiagnostics(file) { const data = await this.client.apiRequest("getBindDiagnostics", { snapshot: this.snapshotId, project: this.project.id, ...(file !== undefined ? { file } : {}), }); return data ?? []; } /** * Get semantic (type-check) diagnostics for a specific file or all files. * @param file - Optional file to get diagnostics for. If omitted, returns diagnostics for all files. */ async getSemanticDiagnostics(file) { const data = await this.client.apiRequest("getSemanticDiagnostics", { snapshot: this.snapshotId, project: this.project.id, ...(file !== undefined ? { file } : {}), }); return data ?? []; } /** * Get suggestion diagnostics for a specific file or all files. * @param file - Optional file to get diagnostics for. If omitted, returns diagnostics for all files. */ async getSuggestionDiagnostics(file) { const data = await this.client.apiRequest("getSuggestionDiagnostics", { snapshot: this.snapshotId, project: this.project.id, ...(file !== undefined ? { file } : {}), }); return data ?? []; } /** * Get declaration emit diagnostics for a specific file or all files. * @param file - Optional file to get diagnostics for. If omitted, returns diagnostics for all files. */ async getDeclarationDiagnostics(file) { const data = await this.client.apiRequest("getDeclarationDiagnostics", { snapshot: this.snapshotId, project: this.project.id, ...(file !== undefined ? { file } : {}), }); return data ?? []; } /** * Get program-wide diagnostics for the project, including compiler options diagnostics. */ async getProgramDiagnostics() { const data = await this.client.apiRequest("getProgramDiagnostics", { snapshot: this.snapshotId, project: this.project.id, }); return data ?? []; } /** * Get global (non-file-specific) semantic diagnostics for the project. */ async getGlobalDiagnostics() { const data = await this.client.apiRequest("getGlobalDiagnostics", { snapshot: this.snapshotId, project: this.project.id, }); return data ?? []; } /** * Get config file parsing diagnostics for the project. */ async getConfigFileParsingDiagnostics() { const data = await this.client.apiRequest("getConfigFileParsingDiagnostics", { snapshot: this.snapshotId, project: this.project.id, }); return data ?? []; } } export class Checker { snapshotId; project; client; objectRegistry; wellKnownSymbols; constructor(snapshotId, project, client, objectRegistry) { this.snapshotId = snapshotId; this.project = project; this.client = client; this.objectRegistry = objectRegistry; } dispose() { this.objectRegistry.clear(); } async getSymbolAtLocation(nodeOrNodes) { if (Array.isArray(nodeOrNodes)) { const data = await this.client.apiRequest("getSymbolsAtLocations", { snapshot: this.snapshotId, project: this.project.id, locations: nodeOrNodes.map(node => getNodeId(node)), }); return data.map(d => d ? this.objectRegistry.getOrCreateSymbol(d) : undefined); } const data = await this.client.apiRequest("getSymbolAtLocation", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(nodeOrNodes), }); return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; } async getSymbolAtPosition(file, positionOrPositions) { if (typeof positionOrPositions === "number") { const data = await this.client.apiRequest("getSymbolAtPosition", { snapshot: this.snapshotId, project: this.project.id, file, position: positionOrPositions, }); return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; } const data = await this.client.apiRequest("getSymbolsAtPositions", { snapshot: this.snapshotId, project: this.project.id, file, positions: positionOrPositions, }); return data.map(d => d ? this.objectRegistry.getOrCreateSymbol(d) : undefined); } async getTypeOfSymbol(symbolOrSymbols) { if (Array.isArray(symbolOrSymbols)) { const data = await this.client.apiRequest("getTypesOfSymbols", { snapshot: this.snapshotId, project: this.project.id, symbols: symbolOrSymbols.map(s => s.id), }); return data.map(d => d ? this.objectRegistry.getOrCreateType(d) : undefined); } const data = await this.client.apiRequest("getTypeOfSymbol", { snapshot: this.snapshotId, project: this.project.id, symbol: symbolOrSymbols.id, }); return data ? this.objectRegistry.getOrCreateType(data) : undefined; } /** * Get the declared type of a symbol. Always returns a type; for symbols whose * declared type cannot be determined the checker yields the error type (use * {@link Type.isErrorType} to detect it). */ async getDeclaredTypeOfSymbol(symbol) { const data = await this.client.apiRequest("getDeclaredTypeOfSymbol", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, }); if (!data) throw new Error(`getDeclaredTypeOfSymbol returned no type for symbol ${symbol.id}`); return this.objectRegistry.getOrCreateType(data); } async getReferencesToSymbolInFile(file, symbol) { const data = await this.client.apiRequest("getReferencesToSymbolInFile", { snapshot: this.snapshotId, project: this.project.id, file, symbol: symbol.id, }); return (data ?? []).map(h => new NodeHandle(h, this.project)); } async getReferencedSymbolsForNode(node, position) { const data = await this.client.apiRequest("getReferencedSymbolsForNode", { snapshot: this.snapshotId, project: this.project.id, node: getNodeId(node), position, }); return (data ?? []).map(entry => ({ definition: new NodeHandle(entry.definition, this.project), symbol: entry.symbol ? this.objectRegistry.getOrCreateSymbol(entry.symbol) : undefined, references: (entry.references ?? []).map(h => new NodeHandle(h, this.project)), })); } async getSignatureUsage(signatureDecl) { const data = await this.client.apiRequest("getSignatureUsages", { snapshot: this.snapshotId, project: this.project.id, signatureDecl: getNodeId(signatureDecl), }); return (data ?? []).map(entry => ({ name: new NodeHandle(entry.name, this.project), call: entry.call ? new NodeHandle(entry.call, this.project) : undefined, })); } async getCompletionsAtPosition(document, position, options) { const data = await this.client.apiRequest("getCompletionsAtPosition", { snapshot: this.snapshotId, project: this.project.id, file: document, position, triggerCharacter: options?.triggerCharacter, includeSymbol: options?.includeSymbol, }); if (!data) return undefined; return { isIncomplete: data.isIncomplete, entries: data.entries.map(e => ({ ...e, symbol: e.symbol ? this.objectRegistry.getOrCreateSymbol(e.symbol) : undefined, })), }; } async getTypeAtLocation(nodeOrNodes) { if (Array.isArray(nodeOrNodes)) { const data = await this.client.apiRequest("getTypeAtLocations", { snapshot: this.snapshotId, project: this.project.id, locations: nodeOrNodes.map(node => getNodeId(node)), }); return data.map(d => d ? this.objectRegistry.getOrCreateType(d) : undefined); } const data = await this.client.apiRequest("getTypeAtLocation", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(nodeOrNodes), }); return data ? this.objectRegistry.getOrCreateType(data) : undefined; } async getSignaturesOfType(type, kind) { const data = await this.client.apiRequest("getSignaturesOfType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, kind, }); return data.map(d => this.objectRegistry.getOrCreateSignature(d)); } async getResolvedSignature(node) { const data = await this.client.apiRequest("getResolvedSignature", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), }); return data ? this.objectRegistry.getOrCreateSignature(data) : undefined; } async getTypeAtPosition(file, positionOrPositions) { if (typeof positionOrPositions === "number") { const data = await this.client.apiRequest("getTypeAtPosition", { snapshot: this.snapshotId, project: this.project.id, file, position: positionOrPositions, }); return data ? this.objectRegistry.getOrCreateType(data) : undefined; } const data = await this.client.apiRequest("getTypesAtPositions", { snapshot: this.snapshotId, project: this.project.id, file, positions: positionOrPositions, }); return data.map(d => d ? this.objectRegistry.getOrCreateType(d) : undefined); } async resolveName(name, meaning, location, excludeGlobals) { // Distinguish Node (has `kind`) from DocumentPosition (has `document` and `position`) const isNode = location && "kind" in location; const data = await this.client.apiRequest("resolveName", { snapshot: this.snapshotId, project: this.project.id, name, meaning, location: isNode ? getNodeId(location) : undefined, file: !isNode && location ? location.document : undefined, position: !isNode && location ? location.position : undefined, excludeGlobals, }); return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; } async getResolvedSymbol(node) { const text = node.text; if (!text) return undefined; return this.resolveName(text, SymbolFlags.Value | SymbolFlags.ExportValue, node); } async getContextualType(node) { const data = await this.client.apiRequest("getContextualType", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), }); return data ? this.objectRegistry.getOrCreateType(data) : undefined; } async getBaseTypeOfLiteralType(type) { const data = await this.client.apiRequest("getBaseTypeOfLiteralType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, }); return data ? this.objectRegistry.getOrCreateType(data) : undefined; } async getNonNullableType(type) { const data = await this.client.apiRequest("getNonNullableType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, }); return data ? this.objectRegistry.getOrCreateType(data) : undefined; } async getTypeFromTypeNode(node) { const data = await this.client.apiRequest("getTypeFromTypeNode", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), }); return data ? this.objectRegistry.getOrCreateType(data) : undefined; } async getWidenedType(type) { const data = await this.client.apiRequest("getWidenedType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, }); return data ? this.objectRegistry.getOrCreateType(data) : undefined; } async getParameterType(signature, index) { const data = await this.client.apiRequest("getParameterType", { snapshot: this.snapshotId, project: this.project.id, signature: signature.id, index, }); return data ? this.objectRegistry.getOrCreateType(data) : undefined; } async isArrayLikeType(type) { return this.client.apiRequest("isArrayLikeType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, }); } async isTypeAssignableTo(source, target) { return this.client.apiRequest("isTypeAssignableTo", { snapshot: this.snapshotId, project: this.project.id, source: source.id, target: target.id, }); } async getShorthandAssignmentValueSymbol(node) { const data = await this.client.apiRequest("getShorthandAssignmentValueSymbol", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), }); return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; } /** * Get the type of a symbol as narrowed at a specific location. Always returns * a type; for symbols whose type cannot be determined the checker yields the * error type (use {@link Type.isErrorType} to detect it). */ async getTypeOfSymbolAtLocation(symbol, location) { const data = await this.client.apiRequest("getTypeOfSymbolAtLocation", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, location: getNodeId(location), }); if (!data) throw new Error(`getTypeOfSymbolAtLocation returned no type for symbol ${symbol.id}`); return this.objectRegistry.getOrCreateType(data); } async getIntrinsicType(method) { const data = await this.client.apiRequest(method, { snapshot: this.snapshotId, project: this.project.id, }); return this.objectRegistry.getOrCreateType(data); } async getAnyType() { return this.getIntrinsicType("getAnyType"); } async getStringType() { return this.getIntrinsicType("getStringType"); } async getNumberType() { return this.getIntrinsicType("getNumberType"); } async getBooleanType() { return this.getIntrinsicType("getBooleanType"); } async getVoidType() { return this.getIntrinsicType("getVoidType"); } async getUndefinedType() { return this.getIntrinsicType("getUndefinedType"); } async getNullType() { return this.getIntrinsicType("getNullType"); } async getNeverType() { return this.getIntrinsicType("getNeverType"); } async getUnknownType() { return this.getIntrinsicType("getUnknownType"); } async getBigIntType() { return this.getIntrinsicType("getBigIntType"); } async getESSymbolType() { return this.getIntrinsicType("getESSymbolType"); } async typeToTypeNode(type, enclosingDeclaration, flags) { const binaryData = await this.client.apiRequestBinary("typeToTypeNode", { snapshot: this.snapshotId, project: this.project.id, type: type.id, location: enclosingDeclaration ? getNodeId(enclosingDeclaration) : undefined, flags, }); if (!binaryData) return undefined; return decodeNode(binaryData); } async signatureToSignatureDeclaration(signature, kind, enclosingDeclaration, flags) { const binaryData = await this.client.apiRequestBinary("signatureToSignatureDeclaration", { snapshot: this.snapshotId, project: this.project.id, signature: signature.id, kind, location: enclosingDeclaration ? getNodeId(enclosingDeclaration) : undefined, flags, }); if (!binaryData) return undefined; return decodeNode(binaryData); } async typeToString(type, enclosingDeclaration, flags) { return this.client.apiRequest("typeToString", { snapshot: this.snapshotId, project: this.project.id, type: type.id, location: enclosingDeclaration ? getNodeId(enclosingDeclaration) : undefined, flags, }); } async isContextSensitive(node) { return this.client.apiRequest("isContextSensitive", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), }); } async isArrayType(type) { return this.client.apiRequest("isArrayType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, }); } async isTupleType(type) { return this.client.apiRequest("isTupleType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, }); } async getReturnTypeOfSignature(signature) { const data = await this.client.apiRequest("getReturnTypeOfSignature", { snapshot: this.snapshotId, project: this.project.id, signature: signature.id, }); return data ? this.objectRegistry.getOrCreateType(data) : undefined; } async getRestTypeOfSignature(signature) { const data = await this.client.apiRequest("getRestTypeOfSignature", { snapshot: this.snapshotId, project: this.project.id, signature: signature.id, }); return data ? this.objectRegistry.getOrCreateType(data) : undefined; } async getTypePredicateOfSignature(signature) { const data = await this.client.apiRequest("getTypePredicateOfSignature", { snapshot: this.snapshotId, project: this.project.id, signature: signature.id, }); if (!data) return undefined; return { kind: data.kind, parameterIndex: data.parameterIndex, parameterName: data.parameterName, type: data.type ? this.objectRegistry.getOrCreateType(data.type) : undefined, }; } /** * Get the base types of a class or interface type. A type with no base types * yields an empty array. */ async getBaseTypes(type) { const data = await this.client.apiRequest("getBaseTypes", { snapshot: this.snapshotId, project: this.project.id, type: type.id, }); return data ? data.map(d => this.objectRegistry.getOrCreateType(d)) : []; } async getApparentType(type) { const data = await this.client.apiRequest("getApparentType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, }); return data ? this.objectRegistry.getOrCreateType(data) : undefined; } async getPropertiesOfType(type) { const data = await this.client.apiRequest("getPropertiesOfType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, }); return data ? data.map(d => this.objectRegistry.getOrCreateSymbol(d)) : []; } async getIndexInfosOfType(type) { const data = await this.client.apiRequest("getIndexInfosOfType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, }); if (!data) return []; return data.map(d => ({ keyType: this.objectRegistry.getOrCreateType(d.keyType), valueType: this.objectRegistry.getOrCreateType(d.valueType), isReadonly: d.isReadonly ?? false, declaration: d.declaration ? new NodeHandle(d.declaration, this.project) : undefined, })); } /** * Get the constraint of a type parameter (the `T` in `<U extends T>`), or * undefined if it has none. */ async getConstraintOfTypeParameter(type) { const data = await this.client.apiRequest("getConstraintOfTypeParameter", { snapshot: this.snapshotId, project: this.project.id, type: type.id, }); return data ? this.objectRegistry.getOrCreateType(data) : undefined; } async getBaseConstraintOfType(type) { const data = await this.client.apiRequest("getBaseConstraintOfType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, }); return data ? this.objectRegistry.getOrCreateType(data) : undefined; } async getPropertyOfType(type, name) { const data = await this.client.apiRequest("getPropertyOfType", { snapshot: this.snapshotId, project: this.project.id, type: type.id, name, }); return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; } async getConstantValue(node) { const data = await this.client.apiRequest("getConstantValue", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), }); return data ?? undefined; } async getSignatureFromDeclaration(node) { const data = await this.client.apiRequest("getSignatureFromDeclaration", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), }); return data ? this.objectRegistry.getOrCreateSignature(data) : undefined; } async getExportSpecifierLocalTargetSymbol(node) { const data = await this.client.apiRequest("getExportSpecifierLocalTargetSymbol", { snapshot: this.snapshotId, project: this.project.id, location: getNodeId(node), }); return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; } /** * Follow all aliases to get the original symbol. Always returns a symbol; for * an unresolved alias the checker yields the unknown symbol (use * {@link Checker.isUnknownSymbol} to detect it). */ async getAliasedSymbol(symbol) { const data = await this.client.apiRequest("getAliasedSymbol", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, }); if (!data) throw new Error(`getAliasedSymbol returned no symbol for symbol ${symbol.id}`); return this.objectRegistry.getOrCreateSymbol(data); } async getImmediateAliasedSymbol(symbol) { const data = await this.client.apiRequest("getImmediateAliasedSymbol", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, }); return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; } /** * Fetch (once, then cache) the handle ids of the per-checker singleton * symbols (unknown, undefined, arguments). These ids are stable for the life * of the project's checker, so identity checks against them are local after * the first call. */ getWellKnownSymbols() { return this.wellKnownSymbols ??= this.client.apiRequest("getWellKnownSymbols", { snapshot: this.snapshotId, project: this.project.id, }); } /** * Returns `true` if the symbol is the checker's "unknown" symbol (e.g. the * result of {@link Checker.getAliasedSymbol} on an unresolved alias). */ async isUnknownSymbol(symbol) { return symbol.id === (await this.getWellKnownSymbols()).unknown; } /** * Returns `true` if the symbol is the checker's "undefined" symbol. */ async isUndefinedSymbol(symbol) { return symbol.id === (await this.getWellKnownSymbols()).undefined; } /** * Returns `true` if the symbol is the checker's "arguments" symbol. */ async isArgumentsSymbol(symbol) { return symbol.id === (await this.getWellKnownSymbols()).arguments; } async getExportsOfModule(symbol) { const data = await this.client.apiRequest("getExportsOfModule", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, }); return data ? data.map(d => this.objectRegistry.getOrCreateSymbol(d)) : []; } async getMemberInModuleExports(symbol, name) { const data = await this.client.apiRequest("getMemberInModuleExports", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, name, }); return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; } async getJsDocTagsOfSymbol(symbol) { const data = await this.client.apiRequest("getJsDocTags", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, }); return data ?? []; } async getDocumentationCommentOfSymbol(symbol) { return this.client.apiRequest("getDocumentationComment", { snapshot: this.snapshotId, project: this.project.id, symbol: symbol.id, }); } /** * Get the type arguments of a type reference (e.g. the `string` in `Array<string>`). */ async getTypeArguments(type) { const data = await this.client.apiRequest("getTypeArguments", { snapshot: this.snapshotId, project: this.project.id, type: type.id, }); return data ? data.map(d => this.objectRegistry.getOrCreateType(d)) : []; } } export class Emitter { client; constructor(client) { this.client = client; } async printNode(node, options = {}) { const encoded = encodeNode(node); const base64 = uint8ArrayToBase64(encoded); return this.client.apiRequest("printNode", { data: base64, ...options, }); } } export class NodeHandle { /** * The project this handle was produced in, used as the default for {@link resolve}. * Node handles are only meaningful within a project's program, so the producing project * is remembered so callers don't have to pass it explicitly. */ canonicalProject; index; kind; path; constructor(handle, canonicalProject) { const parsed = parseNodeHandle(handle); this.index = parsed.index; this.kind = parsed.kind; this.path = parsed.path; this.canonicalProject = canonicalProject; } /** * Resolve this handle to the actual AST node by fetching the source file from a project * and looking up the node by index. If no project is passed, the project that produced * the handle is used. */ async resolve(project = this.canonicalProject) { const sourceFile = await project.program.getSourceFile(this.path); if (!sourceFile) { return undefined; } return sourceFile.getOrCreateNodeAtIndex(this.index); } } export class Symbol { objectRegistry; /** * The project this symbol was first observed in, used as the default project for * lookups that need a project context (members/exports/parent). Symbols are shared * snapshot-wide, so these lookups can otherwise be ambiguous about which project to use. */ canonicalProject; id; /** The escaped (`__String`) name, used as the key in member/export tables. */ escapedName; /** The display name (escaped underscores removed). */ name; flags; checkFlags; declarations; valueDeclaration; parent; exportSymbol; membersCache; exportsCache; constructor(data, objectRegistry) { this.objectRegistry = objectRegistry; this.id = data.id; this.escapedName = data.name; this.name = unescapeLeadingUnderscores(data.name); this.flags = data.flags; this.checkFlags = data.checkFlags; const canonicalProject = objectRegistry.getProject(data.project); if (!canonicalProject) { throw new Error(`Symbol ${data.id} references unknown canonical project '${data.project}'`); } this.canonicalProject = canonicalProject; this.declarations = (data.declarations ?? []).map(d => new NodeHandle(d, canonicalProject)); this.valueDeclaration = data.valueDeclaration ? new NodeHandle(data.valueDeclaration, canonicalProject) : undefined; if (data.parent !== undefined) this.parent = data.parent; if (data.exportSymbol !== undefined) this.exportSymbol = data.exportSymbol; } async getParent() { return this.objectRegistry.fetchSymbol(this, "getParentOfSymbol", this.parent, this.canonicalProject.id); } /** * Get this symbol's members keyed by escaped name. The result is cached on * the symbol, so repeated calls do not round-trip to the server. */ getMembers() { return this.membersCache ??= this.fetchSymbolTable("getMembersOfSymbol"); } /** * Get this symbol's exports keyed by escaped name. The result is cached on * the symbol, so repeated cal