Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 154 additions & 1 deletion packages/typescript/src/api/async/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
import { assertNever } from "../../internal/utils.ts";
import {
encodeNode,
sourceFileResponseToUint8Array,
uint8ArrayToBase64,
} from "../node/encoder.ts";
import {
Expand Down Expand Up @@ -95,13 +96,15 @@ import type {
ModuleResolutionSpec,
PackageId,
ParsedCommandLine,
ProgramStatisticsResponse,
ProjectId,
ProjectReference,
ProjectResponse,
ProtocolSymbolResponse,
RawCompilerOptions,
ReadConfigFileResponse,
ReconfigureSnapshotProgramParams as ProtocolReconfigureSnapshotProgramParams,
RelationCacheSizes,
ResolutionMode,
ResolvedModule,
ResolvedTypeReferenceDirective,
Expand Down Expand Up @@ -241,6 +244,7 @@ export type {
ProjectReference,
RawCompilerOptions,
ReadConfigFileResponse,
RelationCacheSizes,
RequestTiming,
ResolutionMode,
ResolvedModule,
Expand Down Expand Up @@ -343,7 +347,6 @@ export interface TranspileOutput {
// @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<FromLSP extends boolean = false> implements FormatDiagnosticsHost {
Expand Down Expand Up @@ -1575,11 +1578,13 @@ export class Project<Id extends ProjectId = ProjectId> {
readonly program: Program<Id>;
readonly checker: Checker;
readonly languageService: LanguageService;
private readonly toPath: (fileName: string, basePath?: string) => PathKey;

constructor(data: ProjectResponse, snapshotId: number, toPath: (fileName: string, basePath?: string) => PathKey, api: API<boolean>, snapshotRegistry: SnapshotObjectRegistry) {
this.id = data.id as Id;
this.handle = data.handle;
this.api = api;
this.toPath = toPath;
this.configFileName = data.configFileName;
this.currentDirectory = data.currentDirectory;
this.dirty = data.dirty;
Expand All @@ -1593,6 +1598,60 @@ export class Project<Id extends ProjectId = ProjectId> {
this.languageService = new LanguageService(snapshotId, this, objectRegistry);
}

async containsFile(file: DocumentIdentifier, requireOpen?: boolean): Promise<boolean> {
return this.api.client.apiRequest("containsProjectFile", {
snapshot: this.program.snapshotId,
project: this.handle,
file,
requireOpen,
});
}

async getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean): Promise<readonly RootedFilePath[]> {
const files = await this.program.getSourceFileNames();
const result: RootedFilePath[] = [];
for (const file of files) {
if (excludeFilesFromExternalLibraries) {
const metadata = await this.program.getSourceFileMetadata(file);
if (!metadata) {
throw new Error(`Program source file '${file}' has no metadata`);
}
if (metadata.isFromExternalLibrary) continue;
}
result.push(file);
}
if (!excludeConfigFiles) {
result.push(...await this.program.getConfigFileNames());
}
return result;
}

/** Returns existing root files, excluding missing root declarations. */
async getRootFiles(): Promise<readonly RootedFilePath[]> {
return this.api.client.apiRequest("getProjectRootFiles", {
snapshot: this.program.snapshotId,
project: this.handle,
});
}

async hasRoots(): Promise<boolean> {
return this.parsedCommandLine.fileNames.length > 0;
}

async isRoot(file: DocumentIdentifier): Promise<boolean> {
const path = this.toPath(resolveFileName(file), this.currentDirectory);
return (await this.getRootFiles()).some(root => this.toPath(root, this.currentDirectory) === path);
}

async hasConfigFile(file: DocumentIdentifier): Promise<boolean> {
const path = this.toPath(resolveFileName(file), this.currentDirectory);
return (await this.program.getConfigFileNames()).some(config => this.toPath(config, this.currentDirectory) === path);
}

async getSourceFile(file: DocumentIdentifier): Promise<SourceFile | undefined> {
return this.program.getSourceFile(file);
}

dispose(): void {
this.checker.dispose();
}
Expand Down Expand Up @@ -1725,6 +1784,12 @@ export class LanguageService {
}
}

export interface ResolvedProjectReference {
commandLine: ParsedCommandLine;
sourceFile: SourceFile;
references?: readonly (ResolvedProjectReference | undefined)[] | undefined;
}

export class Program<Id extends ProjectId = ProjectId> implements FormatDiagnosticsHost {
/** @internal */
readonly snapshotId: number;
Expand All @@ -1735,6 +1800,7 @@ export class Program<Id extends ProjectId = ProjectId> implements FormatDiagnost
private readonly sourceFileMetadataCache = new Map<PathKey, Promise<SourceFileMetadata | undefined>>();
private ownedSnapshot: Snapshot | undefined;
private disposePromise: Promise<void> | undefined;
private resolvedProjectReferences: Promise<readonly (ResolvedProjectReference | undefined)[] | undefined> | undefined;

private get client(): Client {
return this.project.api.client;
Expand Down Expand Up @@ -1786,6 +1852,85 @@ export class Program<Id extends ProjectId = ProjectId> implements FormatDiagnost
return this.project.parsedCommandLine.options;
}

async getRootFileNames(): Promise<readonly RootedFilePath[]> {
return this.project.parsedCommandLine.fileNames;
}

async getProjectReferences(): Promise<readonly ProjectReference[] | undefined> {
return this.project.parsedCommandLine.projectReferences;
}

async getTypeChecker(): Promise<Checker> {
await this.client.apiRequest("ensureTypeChecker", {
snapshot: this.snapshotId,
project: this.project.handle,
});
return this.project.checker;
}

async getSourceFiles(): Promise<readonly SourceFile[]> {
const files: SourceFile[] = [];
for (const fileName of await this.getSourceFileNames()) {
const file = await this.getSourceFile(fileName);
if (!file) {
throw new Error(`Program source file '${fileName}' could not be loaded`);
}
files.push(file);
}
return files;
}

private async getStatistics(): Promise<ProgramStatisticsResponse> {
return this.client.apiRequest("getProgramStatistics", {
snapshot: this.snapshotId,
project: this.project.handle,
});
}

async getIdentifierCount(): Promise<number> {
return (await this.getStatistics()).identifierCount;
}

async getInstantiationCount(): Promise<number> {
return (await this.getStatistics()).instantiationCount;
}

async getNodeCount(): Promise<number> {
return (await this.getStatistics()).nodeCount;
}

async getSymbolCount(): Promise<number> {
return (await this.getStatistics()).symbolCount;
}

async getTypeCount(): Promise<number> {
return (await this.getStatistics()).typeCount;
}

async getRelationCacheSizes(): Promise<RelationCacheSizes> {
return (await this.getStatistics()).relationCacheSizes;
}

getResolvedProjectReferences(): Promise<readonly (ResolvedProjectReference | undefined)[] | undefined> {
return this.resolvedProjectReferences ??= this.fetchResolvedProjectReferences();
}

private async fetchResolvedProjectReferences(): Promise<readonly (ResolvedProjectReference | undefined)[] | undefined> {
const data = await this.client.apiRequest("getResolvedProjectReferences", {
snapshot: this.snapshotId,
project: this.project.handle,
});
const projects: ResolvedProjectReference[] = data.projects.map(project => ({
commandLine: project.commandLine,
sourceFile: new RemoteSourceFile(sourceFileResponseToUint8Array(project.sourceFile)!, this.decoder, undefined, this.project.api) as unknown as SourceFile,
}));
const resolve = (indices: readonly number[] | null | undefined): (ResolvedProjectReference | undefined)[] | undefined => indices?.map(index => index === 0 ? undefined : projects[index - 1]);
for (let i = 0; i < projects.length; i++) {
projects[i].references = resolve(data.projects[i].references);
}
return resolve(data.references);
}

async getSourceFile(file: DocumentIdentifier): Promise<SourceFile | undefined> {
const fileName = resolveFileName(file);
const path = this.pathKeyForFileName(fileName);
Expand Down Expand Up @@ -2085,6 +2230,14 @@ export class Program<Id extends ProjectId = ProjectId> implements FormatDiagnost
return data ?? [];
}

async getOptionsDiagnostics(): Promise<readonly Diagnostic[]> {
const data = await this.client.apiRequest("getOptionsDiagnostics", {
snapshot: this.snapshotId,
project: this.project.handle,
});
return data ?? [];
}

/**
* Get global (non-file-specific) semantic diagnostics for the project.
*/
Expand Down
Loading
Loading