typescript
Version:
TypeScript is a language for application scale JavaScript development
3,156 lines • 123 kB
JavaScript
import { CheckFlags } from "../../../enums/checkFlags.js";
import { CompletionItemKind } from "../../../enums/completionItemKind.js";
import { DiagnosticCategory } from "../../../enums/diagnosticCategory.js";
import { ElementFlags } from "../../../enums/elementFlags.js";
import { EmitOnly } from "../../../enums/emitOnly.js";
import { IndexKind } from "../../../enums/indexKind.js";
import { JsxEmit } from "../../../enums/jsxEmit.js";
import { ModuleKind } from "../../../enums/moduleKind.js";
import { ModuleResolutionKind } from "../../../enums/moduleResolutionKind.js";
import { NewLineKind } from "../../../enums/newLineKind.js";
import { NodeBuilderFlags } from "../../../enums/nodeBuilderFlags.js";
import { ObjectFlags } from "../../../enums/objectFlags.js";
import { ScriptKind } from "../../../enums/scriptKind.js";
import { SignatureFlags } from "../../../enums/signatureFlags.js";
import { SignatureKind } from "../../../enums/signatureKind.js";
import { SymbolFlags } from "../../../enums/symbolFlags.js";
import { SymbolOwnerKind } from "../../../enums/symbolOwnerKind.js";
import { TypeFlags } from "../../../enums/typeFlags.js";
import { TypeFormatFlags } from "../../../enums/typeFormatFlags.js";
import { TypePredicateKind } from "../../../enums/typePredicateKind.js";
import { ModifierFlags, unescapeLeadingUnderscores, } from "../../ast/index.js";
import { assertNever } from "../../internal/utils.js";
import { encodeNode, uint8ArrayToBase64, } from "../node/encoder.js";
import { decodeNode, getNodeId, getNodeRequestHandle, parseNodeHandleFromCompiler, readParseOptionsKey, readSourceFileHash, readSourceFileLease, RemoteNode, RemoteSourceFile, } from "../node/node.js";
import { Wtf8Decoder } from "../node/wtf8.js";
import { canonicalize, CaseSensitivity, pathKey, toRootedDirectoryPath, toRootedPath, } from "../path.js";
import { prepareCompilerOptions } from "../prepareCompilerOptions.generated.js";
import { resolveFileName, validateSymbolResponse, } from "../proto.js";
import { SourceFileCache, } from "../sourceFileCache.js";
import { Client, } from "./client.js";
export { formatDiagnostics, formatDiagnosticsWithColorAndContext } from "../diagnosticFormatter.js";
export { documentURIToFileName, fileNameToDocumentURI } from "../path.js";
export { CheckFlags, CompletionItemKind, DiagnosticCategory, ElementFlags, EmitOnly, IndexKind, JsxEmit, ModifierFlags, ModuleKind, ModuleResolutionKind, NodeBuilderFlags, ObjectFlags, ScriptKind, SignatureFlags, SignatureKind, SymbolFlags, SymbolOwnerKind, TypeFlags, TypeFormatFlags, TypePredicateKind };
let nextModuleResolutionCallbackId = 0;
function registerModuleResolutionCallback(client, callback, getSnapshot) {
const name = `resolveModuleName/${++nextModuleResolutionCallbackId}`;
const dispose = client.registerCallback(name, params => {
const { moduleName, containingDirectory, resolutionMode, snapshot: snapshotId, inProgressSnapshot } = params;
let snapshot = snapshotId === undefined ? undefined : getSnapshot(snapshotId);
if (snapshotId !== undefined && snapshot === undefined) {
throw new Error(`Snapshot ${snapshotId} is inactive`);
}
if (inProgressSnapshot !== undefined) {
snapshot = -inProgressSnapshot;
}
return callback(moduleName, containingDirectory, resolutionMode, { snapshot });
});
return { name, dispose };
}
// @sync-only-start
// export { all, defer, type APIRequestGenerator, type AnyAPIRequestGenerator, type AllAPIRequestGenerator, type DeferredAPIRequestGenerator, type ExecutedGeneratorsResults } from "./generatorSupport.ts";
// import {executeRequestGenerators, type ExecutedGeneratorsResults, type AnyAPIRequestGenerator} from "./generatorSupport.ts";
// import { sourceFileResponseToUint8Array } from "../node/encoder.ts";
// @sync-only-end
export class API {
/** @internal */
client;
/** @internal */
sourceFileCache;
toPath;
decoder = new Wtf8Decoder();
currentDirectory;
caseSensitivity;
initialized = false;
initializing;
activeSnapshots = new Map();
activeBuildOrchestrators = new Set();
activeSourceFileLeases = new Map();
printer;
debug;
constructor(options = {}) {
this.client = new Client(options);
this.sourceFileCache = new SourceFileCache();
this.printer = new Printer(this.client);
this.debug = new DebugHandlers(this.client, () => this.ensureInitialized()); // @sync: this.debug = new DebugHandlers(this.client, this.ensureInitialized);
}
/**
* Looks up a remote declaration's binder symbol. Throws for synthesized or configuration ASTs.
*/
async getSymbol(declaration) {
const file = getRemoteSourceFile(declaration);
if (!file)
throw new Error(`Source file not found for declaration`);
const record = file.symbolCache;
if (!record)
throw new Error(`Cached source file not found for declaration`);
const index = parseNodeHandleFromCompiler(getNodeId(declaration)).index;
const cached = record.symbolsByDeclarationNodeIndex.get(index);
if (cached)
return cached;
// @sync-only-start
// return this.fetchDeclarationSymbol(record, index);
// @sync-only-end
// @sync-skip-block-start
let request = record.declarationSymbolRequests.get(index);
if (!request) {
request = this.fetchDeclarationSymbol(record, index);
record.declarationSymbolRequests.set(index, request);
}
try {
return await request;
}
finally {
record.declarationSymbolRequests.delete(index);
}
// @sync-skip-block-end
}
async fetchDeclarationSymbol(record, index) {
const data = await this.client.apiRequest("getSymbolOfDeclaration", {
file: record.descriptor,
index,
});
validateSymbolResponse(data);
const reference = data.reference;
if (reference.kind !== SymbolOwnerKind.File) {
throw new Error(`Symbol ${reference.id} is not file-owned`);
}
const fileOwner = { record, api: this };
const symbol = this.sourceFileCache.getOrCreateSymbol(record, reference.file, reference.id, () => new Symbol(data, { kind: SymbolOwnerKind.File, owner: fileOwner }));
record.symbolsByDeclarationNodeIndex.set(index, symbol);
return symbol;
}
/**
* 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;
}
// @sync-skip-block-start
batchContext() {
return this.client.batchContext();
}
// @sync-skip-block-end
// @sync-only-start
// batch<T extends readonly AnyAPIRequestGenerator[]>(...requestGenerators: T): ExecutedGeneratorsResults<T> {
// return executeRequestGenerators(requestGenerators, requests => this.client.batchRequests(requests).responses);
// }
// @sync-only-end
async ensureInitialized() {
if (this.initialized)
return;
return this.initializing ??= this.initializeWorker();
}
async initializeWorker() {
try {
const response = await this.client.apiRequest("initialize", null);
this.currentDirectory = response.currentDirectory;
this.caseSensitivity = response.caseSensitivity;
this.toPath = (fileName, basePath = response.currentDirectory) => pathKey(toRootedPath(fileName, toRootedDirectoryPath(basePath, response.currentDirectory)), response.caseSensitivity);
this.initialized = true;
}
catch (error) {
this.initializing = undefined;
throw error;
}
}
getCurrentDirectory() {
if (this.currentDirectory === undefined) {
throw new Error("API has not been initialized");
}
return this.currentDirectory;
}
getCanonicalFileName(fileName) {
return canonicalize(fileName, this.getCaseSensitivity());
}
getCaseSensitivity() {
if (this.caseSensitivity === undefined) {
throw new Error("API has not been initialized");
}
return this.caseSensitivity;
}
getNewLine() {
return "\n";
}
/** @internal */
async createBuildOrchestrator(rootNames, buildOrchestratorOptions) {
await this.ensureInitialized();
const orchestratorResponse = await this.client.apiRequest("createBuildOrchestrator", {
...buildOrchestratorOptions,
...buildOrchestratorOptions.overrideCompilerOptions,
cwd: buildOrchestratorOptions.cwd === undefined
? undefined
: toRootedDirectoryPath(buildOrchestratorOptions.cwd, this.getCurrentDirectory()),
rootNames,
});
const orchestrator = new BuildOrchestrator(this.client, orchestratorResponse, () => {
this.activeBuildOrchestrators.delete(orchestrator);
});
this.activeBuildOrchestrators.add(orchestrator);
return orchestrator;
}
async parseConfigFile(file) {
await this.ensureInitialized();
return this.client.apiRequest("parseConfigFile", { file });
}
async parseCommandLine(commandLine) {
await this.ensureInitialized();
return this.client.apiRequest("parseCommandLine", { commandLine });
}
async readConfigFile(file) {
await this.ensureInitialized();
return this.client.apiRequest("readConfigFile", { file });
}
async parseJsonConfigFileContent(json, options) {
await this.ensureInitialized();
return this.client.apiRequest("parseJsonConfigFileContent", { json, ...options });
}
/**
* Create and retain a source file independently of a program.
* Dispose the returned lease when the source file no longer needs to remain available remotely.
*/
async createSourceFile(fileName, sourceText, options = {}) {
await this.ensureInitialized();
const data = await this.client.apiRequestBinary("createSourceFile", { fileName, sourceText, options });
if (!data) {
throw new Error("createSourceFile returned no source file");
}
return this.retainSourceFileResponse(data);
}
/**
* Read, create, and retain a source file independently of a program.
* Dispose the returned lease when the source file no longer needs to remain available remotely.
*/
async createSourceFileFromFile(file, options = {}) {
await this.ensureInitialized();
const data = await this.client.apiRequestBinary("createSourceFileFromFile", { fileName: resolveFileName(file), options });
if (!data) {
throw new Error("createSourceFileFromFile returned no source file");
}
return this.retainSourceFileResponse(data);
}
/**
* Retain an ordinary remote source file independently of the snapshot or lease that produced it.
*/
async retainSourceFile(sourceFile) {
await this.ensureInitialized();
if (!(sourceFile instanceof RemoteSourceFile)) {
throw new TypeError("Only remote source files can be retained");
}
const cached = this.sourceFileCache.get(sourceFile);
if (cached && cached !== sourceFile) {
throw new Error("Source file is no longer the canonical cached instance");
}
const result = await this.client.apiRequest("retainSourceFile", { file: sourceFileDescriptor(sourceFile) });
return this.addSourceFileLease(sourceFile, result.lease);
}
retainSourceFileResponse(data) {
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
const lease = readSourceFileLease(view);
try {
const decoded = new RemoteSourceFile(data, this.decoder, this.client.getTimingCollector(), this);
return this.addSourceFileLease(decoded, lease);
}
catch (error) {
// @sync-skip-block-start
void this.client.apiRequest("releaseSourceFile", { lease }).catch(() => { });
// @sync-skip-block-end
// @sync-only-start
// try {
// this.client.apiRequest("releaseSourceFile", { lease });
// }
// catch {}
// @sync-only-end
throw error;
}
}
addSourceFileLease(sourceFile, lease) {
const cached = this.sourceFileCache.setForLease(sourceFile, lease);
const retained = new RetainedSourceFile(cached, lease, () => {
this.activeSourceFileLeases.delete(lease);
this.sourceFileCache.releaseLease(lease);
});
this.activeSourceFileLeases.set(lease, retained);
return retained;
}
async transpileModule(input, options = {}) {
await this.ensureInitialized();
return this.client.apiRequest("transpileModule", { input, options });
}
async transpileModuleFromFile(file, options = {}) {
await this.ensureInitialized();
return this.client.apiRequest("transpileModuleFromFile", { fileName: resolveFileName(file), options });
}
async transpileDeclaration(input, options = {}) {
await this.ensureInitialized();
return this.client.apiRequest("transpileDeclaration", { input, options });
}
async transpileDeclarationFromFile(file, options = {}) {
await this.ensureInitialized();
return this.client.apiRequest("transpileDeclarationFromFile", { fileName: resolveFileName(file), options });
}
async createSnapshot(params) {
await this.ensureInitialized();
const requestParams = this.prepareCreateSnapshotParams(params);
const data = await this.client.apiRequest("createSnapshot", requestParams);
const snapshot = new Snapshot(data, this.toPath, this, () => {
this.activeSnapshots.delete(snapshot.id);
this.sourceFileCache.releaseSnapshot(snapshot.id);
}, this.createSnapshotUpdater(() => snapshot), undefined);
this.activeSnapshots.set(snapshot.id, snapshot);
return snapshot;
}
async updateSnapshot(baseSnapshot, params) {
await this.ensureInitialized();
if (this.activeSnapshots.get(baseSnapshot.id) !== baseSnapshot || baseSnapshot.isDisposed()) {
throw new Error("Cannot update an inactive snapshot");
}
const data = await this.client.apiRequest("updateSnapshot", {
snapshot: baseSnapshot.id,
changes: this.prepareCreateSnapshotParams(params),
});
if (data.snapshot === baseSnapshot.id) {
await this.client.apiRequest("release", { snapshot: data.snapshot });
return baseSnapshot;
}
this.sourceFileCache.retainForSnapshot(data.snapshot, baseSnapshot.id, data.changes);
const snapshot = new Snapshot(data, this.toPath, this, () => {
this.activeSnapshots.delete(snapshot.id);
this.sourceFileCache.releaseSnapshot(snapshot.id);
}, this.createSnapshotUpdater(() => snapshot), baseSnapshot);
this.activeSnapshots.set(snapshot.id, snapshot);
return snapshot;
}
prepareCreateSnapshotParams(params) {
params ??= {};
const prepareOptions = (options) => {
if (!options)
return undefined;
const { moduleResolver, projectReferences, ...rest } = options;
moduleResolver?.ensureNotDisposed();
return {
...rest,
moduleResolver: moduleResolver?.id,
projectReferences: projectReferences?.map(reference => ({
...reference,
path: toRootedPath(reference.path, this.getCurrentDirectory()),
})),
};
};
return {
...params,
createPrograms: params.createPrograms?.map(program => ({ ...program, options: prepareOptions(program.options) })),
reconfigurePrograms: params.reconfigurePrograms?.map(program => ({ ...program, options: prepareOptions(program.options) })),
};
}
prepareLanguageServerSnapshotChanges(changes) {
if (!changes)
return undefined;
const prepared = this.prepareCreateSnapshotParams(changes);
return prepared;
}
createSnapshotUpdater(getSnapshot) {
const update = params => this.updateSnapshot(getSnapshot(), params); // @sync: const update = ((params: CreateSnapshotParams) => this.updateSnapshot(getSnapshot(), params)) as SnapshotUpdater;
// @sync-only-start
// const owner = this;
// update.gen = function* (params: CreateSnapshotParams) { return yield* owner.updateSnapshot.gen(getSnapshot(), params); };
// @sync-only-end
return update;
}
async getCurrentLanguageServerSnapshot(...args) {
await this.ensureInitialized();
const changes = args[0];
const baseSnapshot = args[1];
if (baseSnapshot && (this.activeSnapshots.get(baseSnapshot.id) !== baseSnapshot || baseSnapshot.isDisposed())) {
throw new Error("Cannot use an inactive snapshot as a response base");
}
const data = await this.client.apiRequest("getCurrentLanguageServerSnapshot", {
baseSnapshot: baseSnapshot?.id,
changes: this.prepareLanguageServerSnapshotChanges(changes),
});
if (baseSnapshot) {
this.sourceFileCache.retainForSnapshot(data.snapshot, baseSnapshot.id, data.changes);
}
const snapshot = new Snapshot(data, this.toPath, this, () => {
this.activeSnapshots.delete(snapshot.id);
this.sourceFileCache.releaseSnapshot(snapshot.id);
}, this.createSnapshotUpdater(() => snapshot), baseSnapshot);
this.activeSnapshots.set(snapshot.id, snapshot);
return snapshot;
}
async [globalThis.Symbol.asyncDispose]() {
await this.close(); // @sync: this.close();
}
async close() {
await this.initializing?.catch(() => { }); // @sync-skip
try {
for (const retained of [...this.activeSourceFileLeases.values()]) {
await retained.dispose();
}
}
finally {
try {
for (const orchestrator of [...this.activeBuildOrchestrators]) {
await orchestrator.dispose();
}
for (const snapshot of [...this.activeSnapshots.values()]) {
await snapshot.dispose();
}
this.sourceFileCache.clear();
}
finally {
await this.client.close(); // always close the underlying connection
}
}
}
async createModuleResolver(compilerOptions, options) {
await this.ensureInitialized();
const callback = options?.resolveModuleName
? registerModuleResolutionCallback(this.client, options.resolveModuleName, id => this.activeSnapshots.get(id))
: undefined;
try {
const id = await this.client.apiRequest("createModuleResolver", {
compilerOptions: prepareCompilerOptions(compilerOptions, this.getCurrentDirectory()),
moduleResolutions: options?.moduleResolutions,
resolveModuleNameCallback: callback?.name,
});
return new ModuleResolver(id, this.client, callback?.dispose);
}
catch (error) {
callback?.dispose();
throw error;
}
}
clearSourceFileCache() {
this.sourceFileCache.clear();
}
async runWithTemporaryFileUpdate(baseSnapshot, file, newText, cb) {
await this.ensureInitialized();
if (this.activeSnapshots.get(baseSnapshot.id) !== baseSnapshot || baseSnapshot.isDisposed()) {
throw new Error("Cannot run a temporary file update on an inactive snapshot");
}
const snapshot = await baseSnapshot.update({
fileSystem: {
kind: "layer",
files: { [resolveFileName(file)]: newText },
},
ensurePrograms: true,
});
try {
await cb(snapshot);
}
finally {
await snapshot.dispose();
}
}
/**
* 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 or `TS_API_TIMING_DIR` environment variable; 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();
}
/** Creates a program from current filesystem state. */
async createProgram(rootFiles, compilerOptions, createProgramOptions) {
await this.ensureInitialized();
const snapshot = await this.createSnapshot({
createPrograms: [{ rootFiles, compilerOptions, options: createProgramOptions }],
});
const program = snapshot.operation.createdPrograms[0];
if (!program) {
await snapshot.dispose();
throw new Error("createProgram did not return a project");
}
program.setOwnedSnapshot(snapshot);
return program;
}
}
function sourceFileDescriptor(sourceFile) {
return {
fileName: sourceFile.fileName,
path: sourceFile.path,
contentHash: sourceFile.contentHash,
parseOptionsKey: sourceFile.parseOptionsKey,
scriptKind: sourceFile.scriptKind,
nodeId: sourceFile.nodeId,
};
}
/** Returns whether a node is backed by a remote compiler response. */
export function isRemoteNode(node) {
return node instanceof RemoteNode;
}
function getRemoteSourceFile(node) {
if (!isRemoteNode(node))
return undefined;
const file = node.getSourceFile();
return file instanceof RemoteSourceFile && file.api ? file : undefined;
}
function getNodeAPI(node) {
const api = getRemoteSourceFile(node)?.api;
if (!api)
return undefined;
if (!(api instanceof API))
throw new Error("Source file does not belong to the async API"); // @sync: if (!(api instanceof API)) throw new Error("Source file does not belong to the sync API");
return api;
}
/** Looks up a remote declaration's binder symbol through its API. Throws for non-remote declarations. */
export async function getSymbol(declaration) {
const api = getNodeAPI(declaration);
if (!api)
throw new Error("Source file not found for declaration");
return api.getSymbol(declaration);
}
// @sync-only-start
// export declare namespace getSymbol {
// function gen(declaration: Declaration): Generator<ProtocolRequest, Symbol, ProtocolResponse["result"]>;
// }
// getSymbol.gen = function* (declaration: Declaration): Generator<ProtocolRequest, Symbol, ProtocolResponse["result"]> {
// const api = getNodeAPI(declaration);
// if (!api) throw new Error("Source file not found for declaration");
// return yield* api.getSymbol.gen(declaration);
// };
// @sync-only-end
/** An independently retained source file and its disposable remote-lifetime lease. */
export class RetainedSourceFile {
sourceFile;
lease;
onDispose;
disposed = false;
disposePromise;
constructor(sourceFile, lease, onDispose) {
this.sourceFile = sourceFile;
this.lease = lease;
this.onDispose = onDispose;
}
[globalThis.Symbol.asyncDispose]() {
return this.dispose(); // @sync: this.dispose();
}
dispose() {
return this.disposePromise ??= this.disposeWorker();
}
async disposeWorker() {
if (this.disposed)
return;
const api = getNodeAPI(this.sourceFile);
if (!api)
throw new Error("Retained source file has no owning API");
this.disposed = true;
try {
await api.client.apiRequest("releaseSourceFile", { lease: this.lease });
}
finally {
this.onDispose();
}
}
}
export class DebugHandlers {
client;
ensureInitialized;
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;
operation;
projectMap;
toPath;
api;
disposed = false;
disposePromise;
onDispose;
snapshotRegistry;
projectDataMap;
updateSnapshot;
get client() {
return this.api.client;
}
constructor(data, toPath, api, onDispose, updateSnapshot, baseSnapshot) {
this.id = data.snapshot;
this.api = api;
this.toPath = toPath;
this.onDispose = onDispose;
this.updateSnapshot = updateSnapshot;
this.projectMap = new Map();
const projectDataMap = new Map(baseSnapshot?.projectDataMap);
for (const projectId of data.changes?.removedProjects ?? []) {
projectDataMap.delete(projectId);
}
for (const projectData of data.projects) {
projectDataMap.set(projectData.id, projectData);
}
this.projectDataMap = new Map([...projectDataMap].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
this.snapshotRegistry = new SnapshotObjectRegistry(this.id);
for (const projData of this.projectDataMap.values()) {
const project = new Project(projData, this.id, toPath, api, this.snapshotRegistry);
this.projectMap.set(projData.id, project);
}
this.operation = {
createdPrograms: data.operation.createdPrograms?.map(projectId => this.requireProject(projectId).program),
openedFiles: data.operation.openedFiles?.map(result => ({ project: this.requireProject(result.project) })),
};
}
getProjects() {
this.ensureNotDisposed();
return [...this.projectMap.values()];
}
getConfiguredProject(configFileName) {
this.ensureNotDisposed();
const id = this.toPath(configFileName);
return this.projectMap.get(id);
}
getProject(projectId) {
this.ensureNotDisposed();
return this.projectMap.get(projectId);
}
getProgram(projectId) {
return this.getProject(projectId)?.program;
}
update(params) {
this.ensureNotDisposed();
return this.updateSnapshot(params);
}
/**
* Gets the default project for a given file from the configured projects and
* inferred project already loaded in the snapshot. Synthetic projects are not
* considered. Files that have been opened with `openFiles` are guaranteed to
* have a result.
*/
async getDefaultProjectForFile(file) {
this.ensureNotDisposed();
const data = await this.client.apiRequest("getDefaultProjectForFile", {
snapshot: this.id,
file,
});
if (!data)
return undefined;
return this.projectMap.get(data.id);
}
[globalThis.Symbol.asyncDispose]() {
return this.dispose(); // @sync: this.dispose();
}
dispose() {
return this.disposePromise ??= this.disposeWorker();
}
async disposeWorker() {
if (this.disposed)
return;
this.disposed = true;
for (const project of this.projectMap.values()) {
project.dispose();
}
this.projectMap.clear();
this.snapshotRegistry.clear();
try {
await this.client.apiRequest("release", { snapshot: this.id });
}
finally {
this.onDispose();
}
}
isDisposed() {
return this.disposed;
}
ensureNotDisposed() {
if (this.disposed) {
throw new Error("Snapshot is disposed");
}
}
requireProject(projectId) {
const project = this.projectMap.get(projectId);
if (!project) {
throw new Error(`Snapshot operation returned unknown project '${projectId}'`);
}
return project;
}
}
export class ModuleResolver {
id;
client;
disposeCallback;
disposed = false;
constructor(id, client, disposeCallback) {
this.id = id;
this.client = client;
this.disposeCallback = disposeCallback;
}
async resolveModuleName(moduleName, containingDirectory, resolutionMode, options) {
this.ensureNotDisposed();
if (options?.snapshot instanceof Snapshot && options.snapshot.isDisposed()) {
throw new Error("Snapshot is disposed");
}
return this.client.apiRequest("resolveModuleName", {
snapshot: options?.snapshot instanceof Snapshot ? options.snapshot.id : undefined,
inProgressSnapshot: typeof options?.snapshot === "number" ? -options.snapshot : undefined,
resolver: this.id,
moduleName,
containingDirectory,
resolutionMode,
});
}
[globalThis.Symbol.asyncDispose]() {
return this.dispose();
}
async dispose() {
if (this.disposed)
return;
await this.client.apiRequest("releaseModuleResolver", { resolver: this.id });
this.disposed = true;
this.disposeCallback?.();
}
/** @internal */
ensureNotDisposed() {
if (this.disposed)
throw new Error("ModuleResolver is disposed");
}
}
class SnapshotObjectRegistry {
symbols = new Map();
nodeHandles = new Map();
projectRegistries = new Map();
snapshotId;
disposed = false;
constructor(snapshotId) {
this.snapshotId = snapshotId;
}
addProjectRegistry(registry) {
this.projectRegistries.set(registry.project.handle, registry);
}
getOrCreateSymbol(data) {
const reference = data.reference;
if (reference.kind !== SymbolOwnerKind.Snapshot)
throw new Error(`Symbol ${reference.id} is not snapshot-owned`);
let symbol = this.symbols.get(reference.id);
if (!symbol) {
if (reference.snapshot !== this.snapshotId) {
throw new Error(`Symbol ${reference.id} belongs to snapshot ${reference.snapshot}, not ${this.snapshotId}`);
}
const registry = this.projectRegistries.get(reference.project);
if (!registry)
throw new Error(`Symbol ${reference.id} references unknown project '${reference.project}'`);
symbol = new Symbol(data, { kind: SymbolOwnerKind.Snapshot, registry });
this.symbols.set(reference.id, symbol);
}
return symbol;
}
getSymbol(id) {
return this.symbols.get(id);
}
getOrCreateNodeHandle(handle, project) {
let node = this.nodeHandles.get(handle);
if (!node) {
node = new NodeHandle(handle, project);
if (!this.disposed)
this.nodeHandles.set(handle, node);
}
return node;
}
clear() {
this.disposed = true;
this.symbols.clear();
this.nodeHandles.clear();
}
}
class ProjectObjectRegistry {
snapshotId;
project;
snapshotRegistry;
types = new Map();
signatures = new Map();
disposed = false;
get client() {
return this.project.api.client;
}
get sourceFileCache() {
return this.project.api.sourceFileCache;
}
constructor(snapshotId, project, snapshotRegistry) {
this.snapshotId = snapshotId;
this.project = project;
this.snapshotRegistry = snapshotRegistry;
snapshotRegistry.addProjectRegistry(this);
}
getOrCreateSymbol(data) {
this.ensureNotDisposed();
validateSymbolResponse(data);
const reference = data.reference;
if (reference.kind === SymbolOwnerKind.Snapshot) {
return this.snapshotRegistry.getOrCreateSymbol(data);
}
const record = this.sourceFileCache.getOrCreateRecord(reference.file, this.snapshotId, this.project.id);
return this.sourceFileCache.getOrCreateSymbol(record, reference.file, reference.id, () => new Symbol(data, {
kind: SymbolOwnerKind.File,
owner: { record, api: this.project.api },
}));
}
/** Find an already-interned symbol and retain its file record for this registry. */
getCachedSymbol(reference) {
this.ensureNotDisposed();
if (reference.file === undefined) {
return this.snapshotRegistry.getSymbol(reference.id);
}
const record = this.sourceFileCache.findRecord(reference.file);
const symbol = record?.symbolsById.get(reference.id);
if (record && symbol) {
this.sourceFileCache.retainRecord(record, this.snapshotId, this.project.id);
}
return symbol;
}
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);
}
createNodeHandle(handle) {
return this.snapshotRegistry.getOrCreateNodeHandle(handle, this.project);
}
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.disposed = true;
this.types.clear();
this.signatures.clear();
}
ensureNotDisposed() {
if (this.disposed)
throw new Error("Project object registry is disposed");
}
async fetchOptionalType(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.handle,
objectId: source.id,
});
if (!data)
return undefined;
return this.getOrCreateType(data);
}
async fetchType(source, method, handle) {
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, method, reference) {
if (!reference)
return undefined;
const cached = this.getCachedSymbol(reference);
if (cached)
return cached;
const data = source instanceof Symbol
? await this.client.apiRequest(method, { symbol: source.reference })
: await this.client.apiRequest(method, {
snapshot: this.snapshotId,
project: this.project.handle,
objectId: source.id,
});
if (!data)
throw new Error(`${method} returned null symbol for ${source.constructor.name} ${source.id}`);
return this.getOrCreateSymbol(data);
}
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.handle,
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.handle,
objectId: source.id,
});
if (typesData == null)
return [];
else
return typesData.map(data => this.getOrCreateType(data));
}
async fetchSymbols(source, method, references) {
if (references) {
const result = new Array(references.length);
for (let i = 0; i < references.length; i++) {
const cached = this.getCachedSymbol(references[i]);
if (!cached) {
return this.fetchSymbolsFromServer(source, method);
}
result[i] = cached;
}
return result;
}
return this.fetchSymbolsFromServer(source, method);
}
async fetchSymbolsFromServer(source, method) {
const data = source instanceof Symbol
? await this.client.apiRequest(method, { symbol: source.reference })
: await this.client.apiRequest(method, {
snapshot: this.snapshotId,
project: this.project.handle,
objectId: source.id,
});
return data?.map(symbol => this.getOrCreateSymbol(symbol)) ?? [];
}
// 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.handle,
type: source.id,
});
if (typesData == null)
return [];
return typesData.map(data => this.getOrCreateType(data));
}
async fetchPropertiesOfType(source) {
const data = await this.client.apiRequest("getPropertiesOfType", {
snapshot: this.snapshotId,
project: this.project.handle,
type: source.id,
});
return data ? data.map(symbol => this.getOrCreateSymbol(symbol)) : [];
}
async fetchApparentPropertiesOfType(source) {
const data = await this.client.apiRequest("getApparentPropertiesOfType", {
snapshot: this.snapshotId,
project: this.project.handle,
objectId: source.id,
});
return data ? data.map(symbol => this.getOrCreateSymbol(symbol)) : [];
}
async fetchPropertyOfType(source, name) {
const data = await this.client.apiRequest("getPropertyOfType", {
snapshot: this.snapshotId,
project: this.project.handle,
type: source.id,
name,
});
return data ? this.getOrCreateSymbol(data) : undefined;
}
async fetchSignaturesOfType(source, kind) {
const data = await this.client.apiRequest("getSignaturesOfType", {
snapshot: this.snapshotId,
project: this.project.handle,
type: source.id,
kind,
});
return data.map(signature => this.getOrCreateSignature(signature));
}
async fetchIndexInfosOfType(source) {
const data = await this.client.apiRequest("getIndexInfosOfType", {
snapshot: this.snapshotId,
project: this.project.handle,
type: source.id,
});
if (!data)
return [];
return data.map(info => ({
keyType: this.getOrCreateType(info.keyType),
valueType: this.getOrCreateType(info.valueType),
isReadonly: info.isReadonly ?? false,
declaration: info.declaration ? this.createNodeHandle(info.declaration) : undefined,
}));
}
async fetchTypeParameterAtPosition(source, pos) {
const data = await this.client.apiRequest("getTypeParameterAtPosition", {
snapshot: this.snapshotId,
project: this.project.handle,
signature: source.id,
index: pos,
});
return this.getOrCreateType(data);
}
}
export class Project {
/** @internal */
api;
id;
/**
* Server identity for this project's lifetime across snapshots.
* @internal
*/
handle;
configFileName;
currentDirectory;
dirty;
parsedCommandLine;
program;
checker;
languageService;
constructor(data, snapshotId, toPath, api, snapshotRegistry) {
this.id = data.id;
this.handle = data.handle;
this.api = api;
this.configFileName = data.configFileName;
this.currentDirectory = data.currentDirectory;
this.dirty = data.dirty;
if (!data.parsedCommandLine?.options) {
throw new Error(`Project '${data.configFileName}' has no parsed command line`);
}
this.parsedCommandLine = data.parsedCommandLine;
this.program = new Program(snapshotId, this, toPath);
const objectRegistry = new ProjectObjectRegistry(snapshotId, this, snapshotRegistry);
this.checker = new Checker(snapshotId, this, objectRegistry);
this.languageService = new LanguageService(snapshotId, this, objectRegistry);
}
dispose() {
this.checker.dispose();
}
}
export class LanguageService {
snapshotId;
project;
objectRegistry;
get client() {
return this.project.api.client;
}
constructor(snapshotId, project, objectRegistry) {
this.snapshotId = snapshotId;
this.project = project;
this.objectRegistry = objectRegistry;
}
async getImportAdderEdits(file, actions) {
const requestActions = actions.map(action => {
switch (action.kind) {
case "importSymbol":
const importSymbolAction = {
kind: "importSymbol",
symbol: action.symbol.reference,
};
if (action.isValidTypeOnlyUseSite !== undefined) {
importSymbolAction.isValidTypeOnlyUseSite = action.isValidTypeOnlyUseSite;
}
return importSymbolAction;
default:
return assertNever(action.kind);
}
});
const data = await this.client.apiRequest("getImportAdderEdits", {
snapshot: this.snapshotId,
project: this.project.handle,
file,
actions: requestActions,
});
return data ?? [];
}
async getImportEditsForSymbols(file, symbols, options = {}) {
return this.getImportAdderEdits(file, symbols.map((symbol) => {
if (options.isValidTypeOnlyUseSite !== undefined) {
return {
kind: "importSymbol",
symbol,
isValidTypeOnlyUseSite: options.isValidTypeOnlyUseSite,
};
}
return {
kind: "importSymbol",
symbol,
};
}));
}
async getReferencedSymbolsForNode(node, position) {
const data = await this.client.apiRequest("getReferencedSymbolsForNode", {
snapshot: this.snapshotId,
project: this.project.handle,
node: getNodeRequestHandle(node),
position,
});
return (data ?? []).map(entry => ({
definition: this.objectRegistry.createNodeHandle(entry.definition),
symbol: entry.symbol ? this.objectRegistry.getOrCreateSymbol(entry.symbol) : undefined,
references: (entry.references ?? []).map(h => this.objectRegistry.createNodeHandle(h)),
}));
}
async getSignatureUsage(signatureDecl) {
const data = await this.client.apiRequest("getSignatureUsages", {
snapshot: this.snapshotId,
project: this.project.handle,
signatureDecl: getNodeRequestHandle(signatureDecl),
});
return (data ?? []).map(entry => ({
name: this.objectRegistry.createNodeHandle(entry.name),
call: entry.call ? this.objectRegistry.createNodeHandle(entry.call) : undefined,
}));
}
async getCompletionsAtPosition(document, position, options) {
const data = await this.client.apiRequest("getCompletionsAtPosition", {
snapshot: this.snapshotId,
project: this.project.handle,
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,
})),
};
}
/**
* Format a synthesized node with the correct indentation for insertion at a
* specific position in an existing source file.
*
* @param node The synthesized AST node to format.
* @param file The target file where the node will be inserted.
* @param position The UTF-16 code-unit offset in the target file for insertion.
* @returns The formatted text of the node, indented for the insertion position.
*/
async formatNodeForInsertion(node, file, position) {
const encoded = encodeNode(node);
const base64 = uint8ArrayToBase64(encoded);
return this.client.apiRequest("formatNodeForInsertion", {
snapshot: this.snapshotId,
project: this.project.handle,
file,
position,
data: base64,
});
}
}
export class Program {
/** @internal */
snapshotId;
id;
project;
toPath;
decoder = new Wtf8Decoder();
sourceFileMetadataCache = new Map();
ownedSnapshot;
disposePromise;
get client() {
return this.project.api.client;
}
get sourceFileCache() {
return this.project.api.sourceFileCache;
}
constructor(snapshotId, project, toPath) {
this.snapshotId = snapshotId;
this.id = project.id;
this.project = project;
this.toPath = toPath;
}
getCurrentDirectory() {
return this.project.currentDirectory;
}
getCanonicalFileName(fileName) {
return this.project.api.getCanonicalFileName(fileName);
}
getNewLine() {
return this.project.parsedCommandLine.options.newLine === NewLineKind.CRLF ? "\r\n" : "\n";
}
/** @internal */
setOwnedSnapshot(snapshot) {
this.ownedSnapshot = snapshot;
}
[globalThis.Symbol.asyncDispose]() {
return this.dispose(); // @sync: this.dispose();
}
dispose() {
return this.disposePromise ??= this.disposeWorker();
}
async disposeWorker() {
const snapshot = this.ownedSnapshot;
this.ownedSnapshot = undefined;
if (snapshot)
await snapshot.dispose();
}
getCompilerOptions() {
return this.project.parsedCommandLine.options;
}
async getSourceFile(file) {
const fileName = resolveFileName(file);
const path = this.pathKeyForFileName(fileName);
return this.getSourceFileWorker(file, path);
}
/**
* Returns the source file for an already-canonical path.
*/
getSourceFileByPath(path) {
// The wire format is a string, but the cache key remains the supplied
// PathKey and is never treated as a RootedPath.
return this.getSourceFileWorker(path, path);
}
async getSourceFileWorker(file, path) {
// 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.handle,
file,
});
if (!binaryData) {
return undefined;
}
// Create a new RemoteSourceFile and cache it (set returns existing if hash matches)
const decoded = new RemoteSourceFile(binaryData, this.decoder, this.client.getTimingCollector(), this.project.api);
return this.sourceFileCache.set(decoded, this.snapshotId, this.project.id);
}
async getResolvedModule(file, moduleName, mode) {
const result = await this.client.apiRequest("getResolvedModule", {
snapshot: this.snapshotId,
project: this.project.handle,
file,
moduleName,
mode,
});
return result ?? undefined;
}
async getModeForUsageLocation(file, usage) {
return this.client.apiRequest("getModeForUsageLocation", {
snapshot: this.snapshotId,
project: this.project.handle,
file,
usage: getNodeRequestHandle(usage),
});
}
async getModeForResolutionAtIndex(file, index) {
return this.client.apiRequest("getModeForResolutionAtIndex", {
snapshot: this.snapshotId,
project: this.project.handle,
file,
index,
});
}
async getResolvedModuleFromModuleSpecifier(moduleSpecifier, sourceFile) {
const result = await this.client.apiRequest("getResolvedModuleFromModuleSpecifier", {
snapshot: this.snapshotId,
project: this.project.handle,
moduleSpecifier: getNodeRequestHandle(moduleSpecifier),
sourceFile,
});
return result ?? undefined;
}
async getResolvedTypeReferenceDirective(file, typeDirectiveName, mode) {
const result = await this.client.apiRequest("getResolvedTypeReferenceDirective", {
snapshot: this.snapshotId,
project: this.project.handle,
file,
typeDirectiveName,
mode,
});
return result ?? undefined;
}
async getResolvedTypeReferenceDirectiveFromTypeReferenceDirective(typeReferenceDirective, sourceFile) {
const result = await this.client.apiRequest("getResolvedTypeReferenceDirectiveFromTypeReferenceDirective", {
snapshot: this.snapshotId,
project: this.project.handle,
sourceFile,
typeDirectiveName: typeReferenceDirective.fileName,
resolutionMode: typeReferenceDirective.resolutionMode,
});
return result ?? undefined;
}
async getSourceFileNames() {
const data = await this.client.apiRequest("getSourceFileNames", {
snapshot: this.snapshotId,
project: this.project.handle,
});
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(file) {
return this.getSourceFileMetadataByPath(this.pathKeyForFileName(resolveFileName(file)));
}
/**
* 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) {
// PathKey is serialized as a string; the server deliberately treats all
// client-provided path text as untrusted input.
const data = await this.client.apiRequest("getSourceFileMetadata", {
snapshot: this.snapshotId,
project: this.project.handle,
file: path,
});
return data ?? undefined;
}
pathKeyForFileName(fileName) {
return this.toPath(fileName, this.project.currentDirectory);
}
/**
* 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 all config source file names associated with this program's project config.
* Includes the root config file and any extended config files.
*/
async getConfigFileNames() {
const data = await this.client.apiRequest("getConfigFileNames", {
snapshot: this.snapshotId,
project: this.project.handle,
});
return data ?? [];
}
/**
* Get a config source file by file name/URI.
* This can return the project's root tsconfig file or one of its extended config files.
*/
async getConfigSourceFile(file) {
const binaryData = await this.client.apiRequestBinary("getConfigSourceFile", {
snapshot: this.snapshotId,
project: this.project.handle,
file,
});
if (!binaryData) {
return undefined;
}
return new RemoteSourceFile(binaryData, this.decoder, undefined, this.project.api);
}
/**
* 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.
*/
async getSyntacticDiagnostics(file) {
const files = file === undefined ? undefined
: Array.isArray(file) ? file
: [file];
const data = await this.client.apiRequest("getSyntacticDiagnostics", {
snapshot: this.snapshotId,
project: this.project.handle,
files,
});
return data ?? [];
}
/**
* Get binder diagnostics for specific files or all files.
* @param file - Optional file(s) to get diagnostics for. If omitted, returns diagnostics for all files.
*/
async getBindDiagnostics(file) {
const files = file === undefined ? undefined
: Array.isArray(file) ? file
: [file];
const data = await this.client.apiRequest("getBindDiagnostics", {
snapshot: this.snapshotId,
project: this.project.handle,
files,
});
return data ?? [];
}
/**
* Get semantic (type-check) diagnostics for specific files or all files.
* @param file - Optional file(s) to get diagnostics for. If omitted, returns diagnostics for all files.
*/
async getSemanticDiagnostics(file) {
const files = file === undefined ? undefined
: Array.isArray(file) ? file
: [file];
const data = await this.client.apiRequest("getSemanticDiagnostics", {
snapshot: this.snapshotId,
project: this.project.handle,
files,
});
return data ?? [];
}
/**
* Get suggestion diagnostics for specific files or all files.
* @param file - Optional file(s) to get diagnostics for. If omitted, returns diagnostics for all files.
*/
async getSuggestionDiagnostics(file) {
const files = file === undefined ? undefined
: Array.isArray(file) ? file
: [file];
const data = await this.client.apiRequest("getSuggestionDiagnostics", {
snapshot: this.snapshotId,
project: this.project.handle,
files,
});
return data ?? [];
}
/**
* Get declaration emit diagnostics for specific files or all files.
* @param file - Optional file(s) to get diagnostics for. If omitted, returns diagnostics for all files.
*/
async getDeclarationDiagnostics(file) {
const files = file === undefined ? undefined
: Array.isArray(file) ? file
: [file];
const data = await this.client.apiRequest("getDeclarationDiagnostics", {
snapshot: this.snapshotId,
project: this.project.handle,
files,
});
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.handle,
});
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.handle,
});
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.handle,
});
return data ?? [];
}
/**
* Emits files to the configured filesystem. Layer and host filesystems are
* written through; full filesystems remain immutable and return emitted
* files in {@link EmitResult.fileSystem}.
*/
async emit(emitOnly) {
const response = await this.client.apiRequest("emit", {
snapshot: this.snapshotId,
project: this.project.handle,
emitOnly,
});
const fileSystem = response.emittedFilesContents.length
? {
kind: "layer",
files: Object.fromEntries(response.emittedFiles.map((fileName, index) => [fileName, response.emittedFilesContents[index]])),
}
: undefined;
return {
emitSkipped: response.emitSkipped,
diagnostics: response.diagnostics,
emittedFiles: response.emittedFiles,
fileSystem,
};
}
/**
* Emits files and returns their contents without writing to the filesystem.
*/
async emitToString(emitOnly) {
const response = await this.client.apiRequest("emitToString", {
snapshot: this.snapshotId,
project: this.project.handle,
emitOnly,
});
return toEmitOutput(response);
}
/**
* Gets JavaScript output for selected files regardless of project `noEmit`, `emitDeclarationOnly`, and `noEmitOnError` settings.
*/
async getJavaScriptEmit(files) {
const response = await this.client.apiRequest("getJavaScriptEmit", {
snapshot: this.snapshotId,
project: this.project.handle,
files,
});
return toEmitOutput(response);
}
/**
* Gets declaration output for selected files regardless of project `noEmit`, `declaration`, `emitDeclarationOnly`, and `noEmitOnError` settings.
*/
async getDeclarationEmit(files) {
const response = await this.client.apiRequest("getDeclarationEmit", {
snapshot: this.snapshotId,
project: this.project.handle,
files,
});
return toEmitOutput(response);
}
getProject() {
return this.project;
}
}
export class BuildOrchestrator {
client;
id;
disposed = false;
disposePromise;
onDispose;
constructor(client, orchestratorResponse, onDispose) {
this.client = client;
this.id = orchestratorResponse.buildOrchestratorID;
this.onDispose = onDispose;
}
[globalThis.Symbol.asyncDispose]() {
return this.dispose(); // @sync: this.dispose();
}
dispose() {
return this.disposePromise ??= this.disposeWorker();
}
async disposeWorker() {
if (this.disposed)
return;
this.disposed = true;
try {
await this.client.apiRequest("disposeBuildOrchestrator", {
buildOrchestratorID: this.id,
});
}
finally {
this.onDispose();
}
}
async build(project) {
this.ensureNotDisposed();
const response = await this.client.apiRequest("build", {
buildOrchestratorID: this.id,
...(project !== undefined ? { project } : {}),
});
return response;
}
async buildReferences(project) {
this.ensureNotDisposed();
const response = await this.client.apiRequest("buildReferences", {
buildOrchestratorID: this.id,
project,
});
return response;
}
async clean(project) {
this.ensureNotDisposed();
const response = await this.client.apiRequest("cleanBuild", {
buildOrchestratorID: this.id,
...(project !== undefined ? { project } : {}),
});
return response;
}
async cleanReferences(project) {
this.ensureNotDisposed();
const response = await this.client.apiRequest("cleanReferences", {
buildOrchestratorID: this.id,
...(project !== undefined ? { project } : {}),
});
return response;
}
isDisposed() {
return this.disposed;
}
ensureNotDisposed() {
if (this.disposed) {
throw new Error("Build orchestrator is disposed");
}
}
}
function toEmitOutput(response) {
const outputFiles = new Map();
for (const { fileName, ...outputFile } of response.outputFiles) {
outputFiles.set(fileName, outputFile);
}
return {
emitSkipped: response.emitSkipped,
diagnostics: response.diagnostics,
outputFiles,
};
}
export class Checker {
snapshotId;
project;
objectRegistry;
wellKnownSymbols;
wellKnownSignatures;
get client() {
return this.project.api.client;
}
constructor(snapshotId, project, objectRegistry) {
this.snapshotId = snapshotId;
this.project = project;
this.objectRegistry = objectRegistry;
}
dispose() {
this.objectRegistry.clear();
}
/** Get the merged symbol, or the original symbol if it has not been merged. */
async getMergedSymbol(symbol) {
const data = await this.client.apiRequest("getMergedSymbol", {
snapshot: this.snapshotId,
project: this.project.handle,
symbol: symbol.reference,
});
return this.objectRegistry.getOrCreateSymbol(data);
}
/**
* Get a node's merged symbol. For a declaration, prefer
* {@link Checker.getSymbolOfDeclaration} for a non-nullable result.
*/
async getSymbolOfNode(node) {
const data = await this.client.apiRequest("getSymbolOfNode", {
snapshot: this.snapshotId,
project: this.project.handle,
location: getNodeRequestHandle(node),
});
return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined;
}
/** Get a declaration's merged symbol. */
async getSymbolOfDeclaration(node) {
const data = await this.client.apiRequest("getSymbolOfDeclarationForChecker", {
snapshot: this.snapshotId,
project: this.project.handle,
location: getNodeRequestHandle(node),
});
// Declarations normally have symbols, but preserve unexpected null results at runtime.
return (data && this.objectRegistry.getOrCreateSymbol(data));
}
/** Get a symbol's merged parent, if any. */
async getParentOfSymbol(symbol) {
const data = await this.client.apiRequest("getParentOfSymbolForChecker", {
snapshot: this.snapshotId,
project: this.project.handle,
symbol: symbol.reference,
});
return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined;
}
async getSymbolAtLocation(nodeOrNodes) {
if (Array.isArray(nodeOrNodes)) {
const data = await this.client.apiRequest("getSymbolsAtLocations", {
snapshot: this.snapshotId,
project: this.project.handle,
locations: nodeOrNodes.map(node => getNodeRequestHandle(node)),
});
return data.map(d => d ? this.objectRegistry.getOrCreateSymbol(d) : undefined);
}
const data = await this.client.apiRequest("getSymbolAtLocation", {
snapshot: this.snapshotId,
project: this.project.handle,
location: getNodeRequestHandle(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.handle,
file,
position: positionOrPositions,
});
return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined;
}
const data = await this.client.apiRequest("getSymbolsAtPositions", {
snapshot: this.snapshotId,
project: this.project.handle,
file,
positions: positionOrPositions,
});
return data.map(d => d ? this.objectRegistry.getOrCreateSymbol(d) : undefined);
}
async getSymbolOfSourceFile(fileOrFiles) {
if (Array.isArray(fileOrFiles)) {
const data = await this.client.apiRequest("getSymbolsOfSourceFiles", {
snapshot: this.snapshotId,
project: this.project.handle,
files: fileOrFiles,
});
return data.map(d => d ? this.objectRegistry.getOrCreateSymbol(d) : undefined);
}
const data = await this.client.apiRequest("getSymbolOfSourceFile", {
snapshot: this.snapshotId,
project: this.project.handle,
file: fileOrFiles,
});
return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined;
}
async getTypeOfSymbol(symbolOrSymbols) {
if (Array.isArray(symbolOrSymbols)) {
const data = await this.client.apiRequest("getTypesOfSymbols", {
snapshot: this.snapshotId,
project: this.project.handle,
symbols: symbolOrSymbols.map(symbol => symbol.reference),
});
return data.map(d => this.objectRegistry.getOrCreateType(d));
}
const data = await this.client.apiRequest("getTypeOfSymbol", {
snapshot: this.snapshotId,
project: this.project.handle,
symbol: symbolOrSymbols.reference,
});
return this.objectRegistry.getOrCreateType(data);
}
/**
* 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.handle,
symbol: symbol.reference,
});
return this.objectRegistry.getOrCreateType(data);
}
/**
* Get the type of a symbol, excluding the missing type when
* `exactOptionalPropertyTypes: true` is set; for symbols whose
* type cannot be determined the checker yields the error type
* (use {@link Type.isErrorType} to detect it).
*/
async getNonMissingTypeOfSymbol(symbol) {
const data = await this.client.apiRequest("getNonMissingTypeOfSymbol", {
snapshot: this.snapshotId,
project: this.project.handle,
symbol: symbol.reference,
});
return this.objectRegistry.getOrCreateType(data);
}
async getReferencesToSymbolInFile(file, symbol) {
const data = await this.client.apiRequest("getReferencesToSymbolInFile", {
snapshot: this.snapshotId,
project: this.project.handle,
file,
symbol: symbol.reference,
});
return (data ?? []).map(h => this.objectRegistry.createNodeHandle(h));
}
async getTypeAtLocation(nodeOrNodes) {
if (Array.isArray(nodeOrNodes)) {
const data = await this.client.apiRequest("getTypeAtLocations", {
snapshot: this.snapshotId,
project: this.project.handle,
locations: nodeOrNodes.map(node => getNodeRequestHandle(node)),
});
return data.map(d => this.objectRegistry.getOrCreateType(d));
}
const data = await this.client.apiRequest("getTypeAtLocation", {
snapshot: this.snapshotId,
project: this.project.handle,
location: getNodeRequestHandle(nodeOrNodes),
});
return this.objectRegistry.getOrCreateType(data);
}
async getSignaturesOfType(type, kind) {
return kind === SignatureKind.Call ? type.getCallSignatures() : type.getConstructSignatures();
}
/**
* Get the resolved signature of a call-like expression. Always returns a
* signature; when a call cannot be resolved the checker yields the unknown
* signature (use {@link Checker.isUnknownSignature} to detect it).
*/
async getResolvedSignature(node) {
const data = await this.client.apiRequest("getResolvedSignature", {
snapshot: this.snapshotId,
project: this.project.handle,
location: getNodeRequestHandle(node),
});
return this.objectRegistry.getOrCreateSignature(data);
}
async getTypeAtPosition(file, positionOrPositions) {
if (typeof positionOrPositions === "number") {
const data = await this.client.apiRequest("getTypeAtPosition", {
snapshot: this.snapshotId,
project: this.project.handle,
file,
position: positionOrPositions,
});
return data ? this.objectRegistry.getOrCreateType(data) : undefined;
}
const data = await this.client.apiRequest("getTypesAtPositions", {
snapshot: this.snapshotId,
project: this.project.handle,
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.handle,
name,
meaning,
location: isNode ? getNodeRequestHandle(location) : undefined,
file: !isNode && location ? location.document : undefined,
position: !isNode && location ? location.position : undefined,
excludeGlobals,
});
return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined;
}
/**
* Returns all symbols with the given meaning that are visible at `location`.
*/
async getSymbolsInScope(location, meaning) {
// Distinguish Node (has `kind`) from DocumentPosition (has `document` and `position`)
const isNode = "kind" in location;
const data = await this.client.apiRequest("getSymbolsInScope", {
snapshot: this.snapshotId,
project: this.project.handle,
meaning,
location: isNode ? getNodeRequestHandle(location) : undefined,
file: isNode ? undefined : location.document,
position: isNode ? undefined : location.position,
});
return data ? data.map(d => this.objectRegistry.getOrCreateSymbol(d)) : [];
}
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.handle,
location: getNodeRequestHandle(node),
});
return data ? this.objectRegistry.getOrCreateType(data) : undefined;
}
async getContextualTypeForArgumentAtIndex(node, argIndex) {
const data = await this.client.apiRequest("getContextualTypeForArgument", {
snapshot: this.snapshotId,
project: this.project.handle,
location: getNodeRequestHandle(node),
index: argIndex,
});
return data ? this.objectRegistry.getOrCreateType(data) : undefined;
}
async getAwaitedType(type) {
const data = await this.client.apiRequest("getAwaitedType", {
snapshot: this.snapshotId,
project: this.project.handle,
type: type.id,
});
return data ? this.objectRegistry.getOrCreateType(data) : undefined;
}
/** Get the base type of a literal type (e.g. `number` for `42`). Always returns a type. */
async getBaseTypeOfLiteralType(type) {
const data = await this.client.apiRequest("getBaseTypeOfLiteralType", {
snapshot: this.snapshotId,
project: this.project.handle,
type: type.id,
});
return this.objectRegistry.getOrCreateType(data);
}
/** Get the type with `null` and `undefined` removed. Always returns a type. */
async getNonNullableType(type) {
return type.getNonNullableType();
}
/**
* Get the type for a type node. Always returns a type; for type nodes whose
* type cannot be determined the checker yields the error type (use
* {@link Type.isErrorType} to detect it).
*/
async getTypeFromTypeNode(node) {
const data = await this.client.apiRequest("getTypeFromTypeNode", {
snapshot: this.snapshotId,
project: this.project.handle,
location: getNodeRequestHandle(node),
});
return this.objectRegistry.getOrCreateType(data);
}
/** Get the widened type. Always returns a type. */
async getWidenedType(type) {
const data = await this.client.apiRequest("getWidenedType", {
snapshot: this.snapshotId,
project: this.project.handle,
type: type.id,
});
return this.objectRegistry.getOrCreateType(data);
}
/**
* Get the type of the parameter at the given index in a signature. Always
* returns a type; an out-of-range index yields the `any` type.
*/
async getParameterType(signature, index) {
const data = await this.client.apiRequest("getParameterType", {
snapshot: this.snapshotId,
project: this.project.handle,
signature: signature.id,
index,
});
return this.objectRegistry.getOrCreateType(data);
}
async isArrayLikeType(type) {
return this.client.apiRequest("isArrayLikeType", {
snapshot: this.snapshotId,
project: this.project.handle,
type: type.id,
});
}
async isTypeAssignableTo(source, target) {
return this.client.apiRequest("isTypeAssignableTo", {
snapshot: this.snapshotId,
project: this.project.handle,
source: source.id,
target: target.id,
});
}
async getShorthandAssignmentValueSymbol(node) {
const data = await this.client.apiRequest("getShorthandAssignmentValueSymbol", {
snapshot: this.snapshotId,
project: this.project.handle,
location: getNodeRequestHandle(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.handle,
symbol: symbol.reference,
location: getNodeRequestHandle(location),
});
return this.objectRegistry.getOrCreateType(data);
}
async getIntrinsicType(method) {
const data = await this.client.apiRequest(method, {
snapshot: this.snapshotId,
project: this.project.handle,
});
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 getNonPrimitiveType() {
return this.getIntrinsicType("getNonPrimitiveType");
}
async typeToTypeNode(type, enclosingDeclaration, flags) {
const binaryData = await this.client.apiRequestBinary("typeToTypeNode", {
snapshot: this.snapshotId,
project: this.project.handle,
type: type.id,
location: enclosingDeclaration ? getNodeRequestHandle(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.handle,
signature: signature.id,
kind,
location: enclosingDeclaration ? getNodeRequestHandle(enclosingDeclaration) : undefined,
flags,
});
if (!binaryData)
return undefined;
return decodeNode(binaryData);
}
async typeToString(type, enclosingDeclaration, flags) {
const result = await this.client.apiRequest("typeToString", {
snapshot: this.snapshotId,
project: this.project.handle,
type: type.id,
location: enclosingDeclaration ? getNodeRequestHandle(enclosingDeclaration) : undefined,
flags,
});
if (typeof result !== "string")
throw new TypeError("typeToString returned a non-string result");
return result;
}
async isContextSensitive(node) {
return this.client.apiRequest("isContextSensitive", {
snapshot: this.snapshotId,
project: this.project.handle,
location: getNodeRequestHandle(node),
});
}
async isArrayType(type) {
return this.client.apiRequest("isArrayType", {
snapshot: this.snapshotId,
project: this.project.handle,
type: type.id,
});
}
async isTupleType(type) {
return type.isTupleType();
}
async isTupleTypeTarget(type) {
return type.isTupleTypeTarget();
}
/**
* The following symbols are considered read-only:
* - Properties with a `readonly` modifier
* - Variables declared with `const`
* - Get accessors without matching set accessors
* - Enum members
* - `Object.defineProperty` assignments with `writable: false` or no setter
* - Unions and intersections of the above
*/
async isReadonlySymbol(symbol) {
return this.client.apiRequest("isReadonlySymbol", {
snapshot: this.snapshotId,
project: this.project.handle,
symbol: symbol.reference,
});
}
/** Get the return type of a signature. Always returns a type. */
async getReturnTypeOfSignature(signature) {
return signature.getReturnType();
}
/**
* Get the rest type of a signature. Always returns a type; a signature with
* no rest parameter yields the `any` type.
*/
async getRestTypeOfSignature(signature) {
const data = await this.client.apiRequest("getRestTypeOfSignature", {
snapshot: this.snapshotId,
project: this.project.handle,
signature: signature.id,
});
return this.objectRegistry.getOrCreateType(data);
}
async getTypePredicateOfSignature(signature) {
const data = await this.client.apiRequest("getTypePredicateOfSignature", {
snapshot: this.snapshotId,
project: this.project.handle,
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) {
return await type.getBaseTypes() ?? [];
}
/** Get the apparent type of a type. Always returns a type. */
async getApparentType(type) {
return type.getApparentType();
}
/** Get the reduced type of a type. Always returns a type. */
async getReducedType(type) {
return type.getReducedType();
}
async getPropertiesOfType(type) {
return type.getProperties();
}
async getIndexInfosOfType(type) {
return type.getIndexInfos();
}
async getIndexInfoOfType(type, kind) {
const data = await this.client.apiRequest("getIndexInfoOfType", {
snapshot: this.snapshotId,
project: this.project.handle,
type: type.id,
kind,
});
return data ? {
keyType: this.objectRegistry.getOrCreateType(data.keyType),
valueType: this.objectRegistry.getOrCreateType(data.valueType),
isReadonly: data.isReadonly ?? false,
declaration: data.declaration ? this.objectRegistry.createNodeHandle(data.declaration) : undefined,
} : undefined;
}
async getIndexTypeOfType(type, kind) {
return kind === IndexKind.String ? type.getStringIndexType() : type.getNumberIndexType();
}
async getTypeOfPropertyOfType(type, propertyName) {
const data = await this.client.apiRequest("getTypeOfPropertyOfType", {
snapshot: this.snapshotId,
project: this.project.handle,
type: type.id,
name: propertyName,
});
return data ? this.objectRegistry.getOrCreateType(data) : undefined;
}
/**
* Get the constraint of a type parameter (the `T` in `<U extends T>`), or
* undefined if it has none.
*/
async getConstraintOfTypeParameter(type) {
return type.getConstraint();
}
async getDefaultFromTypeParameter(type) {
return type.getDefault();
}
async getBaseConstraintOfType(type) {
const data = await this.client.apiRequest("getBaseConstraintOfType", {
snapshot: this.snapshotId,
project: this.project.handle,
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.handle,
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.handle,
location: getNodeRequestHandle(node),
});
if (!data || (typeof data.value !== "string" && typeof data.value !== "number")) {
return undefined;
}
if (data.isNumber && typeof data.value === "string") {
if (data.value === "+Infinity") {
return Infinity;
}
else if (data.value === "-Infinity") {
return -Infinity;
}
return NaN;
}
return data.value;
}
/** Get the signature of a function-like declaration. Always returns a signature. */
async getSignatureFromDeclaration(node) {
const data = await this.client.apiRequest("getSignatureFromDeclaration", {
snapshot: this.snapshotId,
project: this.project.handle,
location: getNodeRequestHandle(node),
});
return this.objectRegistry.getOrCreateSignature(data);
}
async getExportSpecifierLocalTargetSymbol(node) {
const data = await this.client.apiRequest("getExportSpecifierLocalTargetSymbol", {
snapshot: this.snapshotId,
project: this.project.handle,
location: getNodeRequestHandle(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.handle,
symbol: symbol.reference,
});
return this.objectRegistry.getOrCreateSymbol(data);
}
/**
* Get the fully qualified name of a symbol, walking up its parent chain
* (e.g. `"/path/to/module".Namespace.Name`).
*/
async getFullyQualifiedName(symbol) {
return this.client.apiRequest("getFullyQualifiedName", {
snapshot: this.snapshotId,
project: this.project.handle,
symbol: symbol.reference,
});
}
async getImmediateAliasedSymbol(symbol) {
const data = await this.client.apiRequest("getImmediateAliasedSymbol", {
snapshot: this.snapshotId,
project: this.project.handle,
symbol: symbol.reference,
});
return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined;
}
/**
* Get the target symbol if instantiated, or the provided symbol otherwise.
*/
async getTargetSymbol(symbol) {
if (symbol.checkFlags & CheckFlags.Instantiated) {
const data = await this.client.apiRequest("getTargetSymbol", {
snapshot: this.snapshotId,
project: this.project.handle,
symbol: symbol.reference,
});
return this.objectRegistry.getOrCreateSymbol(data);
}
return symbol;
}
async getExportSymbolOfSymbol(symbol) {
const data = await this.client.apiRequest("getExportSymbolOfSymbolForChecker", {
snapshot: this.snapshotId,
project: this.project.handle,
symbol: symbol.reference,
});
return this.objectRegistry.getOrCreateSymbol(data);
}
/**
* 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.handle,
});
}
/**
* 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;
}
/**
* Fetch (once, then cache) the handle id of the per-checker unknown
* signature. This id is stable for the life of the project's checker, so
* identity checks against it are local after the first call.
*/
getWellKnownSignatures() {
return this.wellKnownSignatures ??= this.client.apiRequest("getWellKnownSignatures", {
snapshot: this.snapshotId,
project: this.project.handle,
});
}
/**
* Returns `true` if the signature is the checker's "unknown" signature (e.g.
* the result of {@link Checker.getResolvedSignature} on a call that cannot be
* resolved).
*/
async isUnknownSignature(signature) {
return signature.id === (await this.getWellKnownSignatures()).unknown;
}
async getExportsOfModule(symbol) {
const data = await this.client.apiRequest("getExportsOfModule", {
snapshot: this.snapshotId,
project: this.project.handle,
symbol: symbol.reference,
});
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.handle,
symbol: symbol.reference,
name,
});
return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined;
}
async getJsDocTagsOfSymbol(symbol) {
const data = await this.client.apiRequest("getJsDocTags", {
snapshot: this.snapshotId,
project: this.project.handle,
symbol: symbol.reference,
});
return data ?? [];
}
async getDocumentationCommentOfSymbol(symbol) {
return this.client.apiRequest("getDocumentationComment", {
snapshot: this.snapshotId,
project: this.project.handle,
symbol: symbol.reference,
});
}
/**
* 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.handle,
type: type.id,
});
return data ? data.map(d => this.objectRegistry.getOrCreateType(d)) : [];
}
}
export class Printer {
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,
preserveSourceNewlines: options.preserveSourceNewlines,
neverAsciiEscape: options.neverAsciiEscape,
terminateUnterminatedLiterals: options.terminateUnterminatedLiterals,
});
}
async printFile(sourceFile, options = {}) {
const encoded = encodeNode(sourceFile);
const base64 = uint8ArrayToBase64(encoded);
return this.client.apiRequest("printNode", {
data: base64,
preserveSourceNewlines: options.preserveSourceNewlines,
neverAsciiEscape: options.neverAsciiEscape,
terminateUnterminatedLiterals: options.terminateUnterminatedLiterals,
});
}
}
export class NodeHandle {
/**
* The project where this handle was first materialized, 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;
/** The owning source file of a file-owned symbol's declaration. */
fileOwner;
index;
kind;
/** Identity of the exact parsed source file, shared across programs that reuse that AST. */
sourceFileId;
/** Retrieval metadata; never used to substitute a different source-file AST. */
path;
constructor(handle, canonicalProject, fileOwner) {
const parsed = parseNodeHandleFromCompiler(handle);
this.index = parsed.index;
this.kind = parsed.kind;
this.sourceFileId = parsed.sourceFileId;
this.path = parsed.path;
this.canonicalProject = canonicalProject;
this.fileOwner = fileOwner;
}
/**
* 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. Declarations of file-owned symbols identify an exact source file and
* resolve through it, independently of any project. A different AST at the
* same path is never substituted.
*/
async resolve(project = this.canonicalProject) {
if (this.fileOwner) {
const sourceFile = this.fileOwner.record.file ?? await this.fetchOwnerFile(this.fileOwner);
return this.resolveInFile(sourceFile);
}
if (!project)
throw new Error(`Node handle for source file '${this.sourceFileId}' has no project context`);
const sourceFile = await project.program.getSourceFileByPath(this.path);
if (!sourceFile) {
throw new Error("Node handle source file is not part of the requested program");
}
return this.resolveInFile(sourceFile);
}
resolveInFile(sourceFile) {
if (sourceFile.nodeId !== this.sourceFileId) {
throw new Error(`Node handle source file is not part of the requested program`);
}
const node = sourceFile.getOrCreateNodeAtIndex(this.index);
if (!node || node.kind !== this.kind) {
throw new Error("Node handle does not identify a node of the expected kind");
}
return node;
}
async fetchOwnerFile(fileOwner) {
const data = await fileOwner.api.client.apiRequestBinary("getCachedSourceFile", { file: fileOwner.record.descriptor });
if (!data)
throw new Error(`Source file '${fileOwner.record.descriptor.fileName}' is not available`);
return fileOwner.api.sourceFileCache.attachFile(fileOwner.record, new RemoteSourceFile(data, new Wtf8Decoder(), fileOwner.api.client.getTimingCollector(), fileOwner.api));
}
}
export class Symbol {
storage;
get id() {
return this.reference.id;
}
/** The escaped (`__String`) name, used as the key in member/export tables. */
escapedName;
/** The display name (escaped underscores removed). */
name;
flags;
checkFlags;
/** @internal */
reference;
// Declaration handles are kept in their wire form until first accessed.
declarationHandles;
valueDeclarationHandle;
declarationsCache;
valueDeclarationCache;
parent;
exportSymbol;
membersCache;
exportsCache;
constructor(data, storage) {
if (data.reference.kind !== storage.kind)
throw new Error(`Symbol ${data.reference.id} has mismatched ownership and storage`);
this.storage = storage;
this.reference = data.reference;
this.escapedName = data.name;
this.name = unescapeLeadingUnderscores(data.name);
this.flags = data.flags;
this.checkFlags = data.checkFlags ?? 0;
this.declarationHandles = data.declarations;
this.valueDeclarationHandle = data.valueDeclaration;
this.declarationsCache = undefined;
this.valueDeclarationCache = undefined;
if (data.parent !== undefined)
this.parent = data.parent;
if (data.exportSymbol !== undefined)
this.exportSymbol = data.exportSymbol;
}
get declarations() {
return this.declarationsCache ??= this.declarationHandles?.map(handle => this.createDeclarationHandle(handle)) ?? [];
}
get valueDeclaration() {
if (this.valueDeclarationCache === undefined && this.valueDeclarationHandle) {
this.valueDeclarationCache = this.createDeclarationHandle(this.valueDeclarationHandle);
}
return this.valueDeclarationCache;
}
createDeclarationHandle(handle) {
// A file-owned symbol has no canonical project; its declarations resolve through its file.
return this.storage.kind === SymbolOwnerKind.Snapshot
? this.storage.registry.createNodeHandle(handle)
: new NodeHandle(handle, undefined, this.storage.owner);
}
async getParent() {
if (!this.parent)
return undefined;
return this.fetchSymbol("getParentOfSymbol", this.parent);
}
/**
* 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 calls do not round-trip to the server.
*/
getExports() {
return this.exportsCache ??= this.fetchSymbolTable("getExportsOfSymbol");
}
async fetchSymbolTable(method) {
const symbols = await this.fetchSymbols(method);
const table = new Map();
for (const symbol of symbols) {
table.set(symbol.escapedName, symbol);
}
return table;
}
async getExportSymbol() {
if (!this.exportSymbol)
return this;
return this.fetchSymbol("getExportSymbolOfSymbol", this.exportSymbol);
}
async getJsDocTags(checker) {
return checker.getJsDocTagsOfSymbol(this);
}
async getDocumentationComment(checker) {
return checker.getDocumentationCommentOfSymbol(this);
}
async fetchSymbol(method, reference) {
if (this.storage.kind === SymbolOwnerKind.Snapshot) {
return this.storage.registry.fetchSymbol(this, method, reference);
}
const fileOwner = this.storage.owner;
// A file-owned symbol's relationships are always owned by the same file.
const cached = reference.file === fileOwner.record.descriptor.nodeId ? fileOwner.record.symbolsById.get(reference.id) : undefined;
if (cached)
return cached;
const data = await fileOwner.api.client.apiRequest(method, { symbol: this.reference });
if (!data)
throw new Error(`${method} returned null symbol for Symbol ${this.id}`);
return this.internFileSymbol(fileOwner, data);
}
async fetchSymbols(method) {
if (this.storage.kind === SymbolOwnerKind.Snapshot) {
return this.storage.registry.fetchSymbols(this, method);
}
const fileOwner = this.storage.owner;
const data = await fileOwner.api.client.apiRequest(method, { symbol: this.reference });
return data?.map(symbol => this.internFileSymbol(fileOwner, symbol)) ?? [];
}
internFileSymbol(fileOwner, data) {
validateSymbolResponse(data);
const reference = data.reference;
if (reference.kind !== SymbolOwnerKind.File)
throw new Error(`Symbol ${reference.id} is not file-owned`);
return fileOwner.api.sourceFileCache.getOrCreateSymbol(fileOwner.record, reference.file, reference.id, () => new Symbol(data, { kind: SymbolOwnerKind.File, owner: fileOwner }));
}
}
class TypeObject {
objectRegistry;
// Fields included in TypeResponse. References to other objects are stored as IDs
// and resolved lazily via the object registry.
id;
flags;
objectFlags;
symbol;
value;
intrinsicName;
isThisType;
freshType;
regularType;
target;
tupleType;
typeParameters;
outerTypeParameters;
localTypeParameters;
thisType;
aliasTypeArguments;
aliasSymbol;
elementFlags;
fixedLength;
readonly;
labeledElementDeclarations;
texts;
objectType;
indexType;
checkType;
extendsType;
baseType;
substConstraint;
typeParameter;
constraintType;
nameType;
templateType;
// Cached results of lazy fetches, not included in TypeResponse
// (typically because they require some amount of computation or
// could cause an arbitrarily large number of types to be cached
// on the server for ID-based lookup). `false` is a sentinel value
// indicating a fetch has not yet occurred.
trueType;
falseType;
constraint;
default;
nonNullableType;
apparentType;
reducedType;
properties;
apparentProperties;
callSignatures;
constructSignatures;
indexInfos;
baseTypes;
types;
stringIndexType;
numberIndexType;
constructor(data, objectRegistry) {
this.objectRegistry = objectRegistry;
this.id = data.id;
this.flags = data.flags;
this.objectFlags = data.objectFlags ?? 0;
if (data.symbol !== undefined)
this.symbol = data.symbol;
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.
const value = data.value;
if (data.flags & TypeFlags.BigIntLiteral) {
this.value = BigInt(value);
}
// JSON cannot represent infinities, so the API serializes them as strings.
else if (data.flags & TypeFlags.NumberLiteral && typeof value === "string") {
if (value === "+Infinity") {
this.value = Infinity;
}
else if (value === "-Infinity") {
this.value = -Infinity;
}
else {
this.value = NaN;
}
}
else {
this.value = value;
}
}
if (data.intrinsicName !== undefined)
this.intrinsicName = data.intrinsicName;
this.isThisType = data.isThisType ?? false;
if (data.freshType !== undefined)
this.freshType = data.freshType;
if (data.regularType !== undefined)
this.regularType = data.regularType;
if (data.target !== undefined)
this.target = data.target;
this.tupleType = data.isTupleType ?? false;
this.typeParameters = data.typeParameters ?? [];
this.outerTypeParameters = data.outerTypeParameters ?? [];
this.localTypeParameters = data.localTypeParameters ?? [];
if (data.thisType !== undefined)
this.thisType = data.thisType;
this.aliasTypeArguments = data.aliasTypeArguments ?? [];
if (data.aliasSymbol !== undefined)
this.aliasSymbol = data.aliasSymbol;
if (data.fixedLength !== undefined) {
this.elementFlags = data.elementFlags ?? [];
this.fixedLength = data.fixedLength;
}
if (data.readonly !== undefined)
this.readonly = data.readonly;
if (data.labeledElementDeclarations !== undefined) {
this.labeledElementDeclarations = data.labeledElementDeclarations.map(handle => handle ? objectRegistry.createNodeHandle(handle) : undefined);
}
if (data.texts !== undefined)
this.texts = data.texts;
if (data.objectType !== undefined)
this.objectType = data.objectType;
if (data.indexType !== undefined)
this.indexType = data.indexType;
if (data.checkType !== undefined)
this.checkType = data.checkType;
if (data.extendsType !== undefined)
this.extendsType = data.extendsType;
if (data.baseType !== undefined)
this.baseType = data.baseType;
if (data.substConstraint !== undefined)
this.substConstraint = data.substConstraint;
if (data.typeParameter !== undefined)
this.typeParameter = data.typeParameter;
if (data.constraintType !== undefined)
this.constraintType = data.constraintType;
if (data.nameType !== undefined)
this.nameType = data.nameType;
if (data.templateType !== undefined)
this.templateType = data.templateType;
this.trueType = false;
this.falseType = false;
this.constraint = false;
this.default = false;
this.nonNullableType = false;
this.apparentType = false;
this.reducedType = false;
this.properties = false;
this.apparentProperties = false;
this.callSignatures = false;
this.constructSignatures = false;
this.indexInfos = false;
this.baseTypes = false;
this.types = false;
this.stringIndexType = false;
this.numberIndexType = false;
}
async getSymbol() {
return this.objectRegistry.fetchSymbol(this, "getSymbolOfType", this.symbol);
}
async getProperties() {
if (this.properties === false) {
this.properties = await this.objectRegistry.fetchPropertiesOfType(this);
}
return this.properties;
}
getProperty(propertyName) {
return this.objectRegistry.fetchPropertyOfType(this, propertyName);
}
async getApparentProperties() {
if (this.apparentProperties === false) {
this.apparentProperties = await this.objectRegistry.fetchApparentPropertiesOfType(this);
}
return this.apparentProperties;
}
async getCallSignatures() {
if (this.callSignatures === false) {
this.callSignatures = await this.objectRegistry.fetchSignaturesOfType(this, SignatureKind.Call);
}
return this.callSignatures;
}
async getConstructSignatures() {
if (this.constructSignatures === false) {
this.constructSignatures = await this.objectRegistry.fetchSignaturesOfType(this, SignatureKind.Construct);
}
return this.constructSignatures;
}
async getNonNullableType() {
const result = await this.objectRegistry.fetchType(this, "getNonNullableType", this.nonNullableType);
this.nonNullableType = result.id;
return result;
}
async getStringIndexType() {
if (this.stringIndexType === false) {
this.stringIndexType = await this.getStringIndexTypeWorker();
}
return this.stringIndexType;
}
async getStringIndexTypeWorker() {
const infos = await this.getIndexInfos();
return infos.find(info => (info.keyType.flags & TypeFlags.String) !== 0)?.valueType;
}
async getNumberIndexType() {
if (this.numberIndexType === false) {
this.numberIndexType = await this.getNumberIndexTypeWorker();
}
return this.numberIndexType;
}
async getNumberIndexTypeWorker() {
const infos = await this.getIndexInfos();
return infos.find(info => (info.keyType.flags & TypeFlags.Number) !== 0)?.valueType;
}
async getApparentType() {
const result = await this.objectRegistry.fetchType(this, "getApparentType", this.apparentType);
this.apparentType = result.id;
return result;
}
async getReducedType() {
const result = await this.objectRegistry.fetchType(this, "getReducedType", this.reducedType);
this.reducedType = result.id;
return result;
}
async getIndexInfos() {
if (this.indexInfos === false) {
this.indexInfos = await this.objectRegistry.fetchIndexInfosOfType(this);
}
return this.indexInfos;
}
async getAliasSymbol() {
return this.objectRegistry.fetchSymbol(this, "getAliasSymbolOfType", this.aliasSymbol);
}
async getTarget() {
return this.objectRegistry.fetchType(this, "getTargetOfType", this.target);
}
async getFreshType() {
return this.objectRegistry.fetchOptionalType(this, "getFreshTypeOfType", this.freshType);
}
async getRegularType() {
return this.objectRegistry.fetchOptionalType(this, "getRegularTypeOfType", this.regularType);
}
async getTypes() {
// Only union, intersection, and template literal types have constituent
// types; any other kind has none, so return undefined rather than sending
// a request the server cannot satisfy.
if (!(this.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral))) {
return undefined;
}
if (this.types === false) {
this.types = await this.objectRegistry.fetchTypes(this, "getTypesOfType");
}
return this.types;
}
async getTypeParameters() {
return this.objectRegistry.fetchTypes(this, "getTypeParametersOfType", this.typeParameters);
}
async getOuterTypeParameters() {
return this.objectRegistry.fetchTypes(this, "getOuterTypeParametersOfType", this.outerTypeParameters);
}
async getLocalTypeParameters() {
return this.objectRegistry.fetchTypes(this, "getLocalTypeParametersOfType", this.localTypeParameters);
}
async getThisType() {
return this.objectRegistry.fetchOptionalType(this, "getThisTypeOfType", this.thisType);
}
async getAliasTypeArguments() {
return this.objectRegistry.fetchTypes(this, "getAliasTypeArgumentsOfType", this.aliasTypeArguments);
}
async getTypeParameter() {
return this.objectRegistry.fetchType(this, "getTypeParameterOfMappedType", this.typeParameter);
}
async getConstraintType() {
return this.objectRegistry.fetchType(this, "getConstraintTypeOfMappedType", this.constraintType);
}
async getNameType() {
return this.objectRegistry.fetchOptionalType(this, "getNameTypeOfMappedType", this.nameType);
}
async getTemplateType() {
return this.objectRegistry.fetchType(this, "getTemplateTypeOfMappedType", this.templateType);
}
async getObjectType() {
return this.objectRegistry.fetchType(this, "getObjectTypeOfType", this.objectType);
}
async getIndexType() {
return this.objectRegistry.fetchType(this, "getIndexTypeOfType", this.indexType);
}
async getCheckType() {
return this.objectRegistry.fetchType(this, "getCheckTypeOfType", this.checkType);
}
async getExtendsType() {
return this.objectRegistry.fetchType(this, "getExtendsTypeOfType", this.extendsType);
}
async getBaseType() {
return this.objectRegistry.fetchType(this, "getBaseTypeOfType", this.baseType);
}
async getConstraint() {
// Type parameters resolve their constraint lazily through the checker,
// whereas substitution types carry a preloaded constraint handle.
if (this.flags & TypeFlags.TypeParameter) {
const result = await this.objectRegistry.fetchOptionalType(this, "getConstraintOfTypeParameter", this.constraint);
this.constraint = result ? result.id : 0;
return result;
}
return this.objectRegistry.fetchType(this, "getConstraintOfType", this.substConstraint);
}
async getDefault() {
const result = await this.objectRegistry.fetchOptionalType(this, "getDefaultFromTypeParameter", this.default);
this.default = result ? result.id : 0;
return result;
}
async getTrueType() {
const result = await this.objectRegistry.fetchType(this, "getTrueTypeOfConditionalType", this.trueType);
this.trueType = result.id;
return result;
}
async getFalseType() {
const result = await this.objectRegistry.fetchType(this, "getFalseTypeOfConditionalType", this.falseType);
this.falseType = result.id;
return result;
}
/**
* Get the base types of this type. Returns `undefined` for any type that is
* not a class or interface.
*/
async getBaseTypes() {
if (!this.isClassOrInterface()) {
return undefined;
}
if (this.baseTypes === false) {
this.baseTypes = await this.objectRegistry.fetchBaseTypes(this);
}
return this.baseTypes;
}
isClassOrInterface() {
return isClassOrInterfaceType(this);
}
isUnionType() {
return isUnionType(this);
}
isIntersectionType() {
return isIntersectionType(this);
}
isObjectType() {
return isObjectType(this);
}
isIntrinsicType() {
return isIntrinsicType(this);
}
isErrorType() {
return isErrorType(this);
}
isLiteralType() {
return isLiteralType(this);
}
isStringLiteralType() {
return isStringLiteralType(this);
}
isNumberLiteralType() {
return isNumberLiteralType(this);
}
isBigIntLiteralType() {
return isBigIntLiteralType(this);
}
isBooleanLiteralType() {
return isBooleanLiteralType(this);
}
isTypeReference() {
return isTypeReference(this);
}
isTupleType() {
return this.tupleType;
}
isTupleTypeTarget() {
return this.fixedLength !== undefined;
}
isIndexType() {
return isIndexType(this);
}
isIndexedAccessType() {
return isIndexedAccessType(this);
}
isConditionalType() {
return isConditionalType(this);
}
isSubstitutionType() {
return isSubstitutionType(this);
}
isTemplateLiteralType() {
return isTemplateLiteralType(this);
}
isStringMappingType() {
return isStringMappingType(this);
}
isTypeParameter() {
return isTypeParameter(this);
}
isMappedType() {
return !!(this.flags & TypeFlags.Object) && !!(this.objectFlags & ObjectFlags.Mapped);
}
}
export function isUnionType(type) {
return (type.flags & TypeFlags.Union) !== 0;
}
export function isIntersectionType(type) {
return (type.flags & TypeFlags.Intersection) !== 0;
}
export function isObjectType(type) {
return (type.flags & TypeFlags.Object) !== 0;
}
export function isClassOrInterfaceType(type) {
return isObjectType(type) && (type.objectFlags & ObjectFlags.ClassOrInterface) !== 0;
}
export function isIntrinsicType(type) {
return (type.flags & TypeFlags.Intrinsic) !== 0;
}
/**
* Whether this is the error type — the placeholder the checker produces when a
* type cannot be determined (e.g. an unresolved reference). It is an intrinsic
* type named `"error"` (this covers both the singleton error type and the
* per-alias error types manufactured for unresolved type alias references).
*/
export function isErrorType(type) {
return isIntrinsicType(type) && type.intrinsicName === "error";
}
export function isLiteralType(type) {
return (type.flags & TypeFlags.Literal) !== 0;
}
export function isStringLiteralType(type) {
return (type.flags & TypeFlags.StringLiteral) !== 0;
}
export function isNumberLiteralType(type) {
return (type.flags & TypeFlags.NumberLiteral) !== 0;
}
export function isBigIntLiteralType(type) {
return (type.flags & TypeFlags.BigIntLiteral) !== 0;
}
export function isBooleanLiteralType(type) {
return (type.flags & TypeFlags.BooleanLiteral) !== 0;
}
export function isTypeReference(type) {
return isObjectType(type) && (type.objectFlags & ObjectFlags.Reference) !== 0;
}
export function isTupleType(type) {
return type.isTupleType();
}
export function isTupleTypeTarget(type) {
return type.isTupleTypeTarget();
}
export function isIndexType(type) {
return (type.flags & TypeFlags.Index) !== 0;
}
export function isIndexedAccessType(type) {
return (type.flags & TypeFlags.IndexedAccess) !== 0;
}
export function isConditionalType(type) {
return (type.flags & TypeFlags.Conditional) !== 0;
}
export function isSubstitutionType(type) {
return (type.flags & TypeFlags.Substitution) !== 0;
}
export function isTemplateLiteralType(type) {
return (type.flags & TypeFlags.TemplateLiteral) !== 0;
}
export function isStringMappingType(type) {
return (type.flags & TypeFlags.StringMapping) !== 0;
}
export function isTypeParameter(type) {
return (type.flags & TypeFlags.TypeParameter) !== 0;
}
export class Signature {
flags;
objectRegistry;
id;
project;
declarationHandle;
declarationCache;
typeParameters;
parameters;
thisParameter;
target;
returnType;
constructor(data, project, objectRegistry) {
this.id = data.id;
this.flags = data.flags ?? 0;
this.objectRegistry = objectRegistry;
this.project = project;
this.declarationHandle = data.declaration;
this.declarationCache = undefined;
this.typeParameters = data.typeParameters ?? [];
this.parameters = data.parameters ?? [];
this.thisParameter = data.thisParameter;
this.target = data.target;
this.returnType = false;
}
get declaration() {
if (this.declarationCache === undefined && this.declarationHandle) {
this.declarationCache = this.objectRegistry.createNodeHandle(this.declarationHandle);
}
return this.declarationCache;
}
async getTypeParameters() {
return this.objectRegistry.fetchTypes(this, "getTypeParametersOfSignature", this.typeParameters);
}
async getParameters() {
return this.objectRegistry.fetchSymbols(this, "getParametersOfSignature", this.parameters);
}
async getThisParameter() {
return this.objectRegistry.fetchSymbol(this, "getThisParameterOfSignature", this.thisParameter);
}
async getTarget() {
return this.objectRegistry.fetchSignature(this, "getTargetOfSignature", this.target);
}
async getReturnType() {
const result = await this.objectRegistry.fetchType(this, "getReturnTypeOfSignature", this.returnType);
this.returnType = result.id;
return result;
}
getTypeParameterAtPosition(pos) {
return this.objectRegistry.fetchTypeParameterAtPosition(this, pos);
}
get hasRestParameter() {
return (this.flags & SignatureFlags.HasRestParameter) !== 0;
}
get isConstruct() {
return (this.flags & SignatureFlags.Construct) !== 0;
}
get isAbstract() {
return (this.flags & SignatureFlags.Abstract) !== 0;
}
}
//# sourceMappingURL=api.js.map