Skip to content
Draft
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
80 changes: 73 additions & 7 deletions packages/typescript/src/api/async/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ import {
toPath,
} from "../path.ts";
import type {
APIFileChanges,
CompilerOptions,
CreateProgramOptions,
CreateProgramResponse,
Diagnostic,
DocumentIdentifier,
DocumentPosition,
Expand Down Expand Up @@ -135,6 +138,7 @@ import type {
export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts";
export { CheckFlags, CompletionItemKind, DiagnosticCategory, ElementFlags, EmitOnly, ModifierFlags, ModuleKind, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypeFormatFlags, TypePredicateKind };
export type {
APIFileChanges,
APIImportAdderAction as ImportAdderAction,
APIOptions,
AssertsIdentifierTypePredicate,
Expand All @@ -148,6 +152,7 @@ export type {
CompletionInfo,
CompletionOptions,
ConditionalType,
CreateProgramOptions,
Diagnostic,
DocumentIdentifier,
DocumentPosition,
Expand Down Expand Up @@ -387,6 +392,46 @@ export class API<FromLSP extends boolean = false> {
resetTimingInfo(): Promise<void> {
return this.client.resetTimingInfo();
}

/**
* Creates a program from current filesystem state, or derives one from oldProgram after applying fileChanges.
*/
async createProgram(
rootFiles: readonly DocumentIdentifier[],
createProgramOptions: CreateProgramOptions,
oldProgram?: Program,
fileChanges?: APIFileChanges,
): Promise<Program> {
await this.ensureInitialized();

if (fileChanges && !oldProgram) {
throw new Error("fileChanges requires an oldProgram");
}

const data: CreateProgramResponse = await this.client.apiRequest("createProgram", {
rootFiles,
createProgramOptions,
...(oldProgram ? { oldProgram: { snapshot: oldProgram.snapshotId, project: oldProgram.getProject().id } } : {}),
...(fileChanges ? { fileChanges } : {}),
Comment on lines +411 to +415
});
if (!data.project) {
throw new Error("createProgram did not return a project");
}
const snapshot = new Snapshot(
{ snapshot: data.snapshot, projects: [data.project] },
this.client,
this.sourceFileCache,
this.toPath!,
() => {
this.activeSnapshots.delete(snapshot);
this.sourceFileCache.releaseSnapshot(snapshot.id);
},
);
const program = snapshot.getProjects()[0].program;
program.setOwnedSnapshot(snapshot);
this.activeSnapshots.add(snapshot);
return program;
}
}

export class InternalAPI {
Expand Down Expand Up @@ -947,13 +992,15 @@ export class LanguageService {
}

export class Program {
private snapshotId: number;
private project: Project;
private client: Client;
private sourceFileCache: SourceFileCache;
private toPath: (fileName: string) => Path;
private decoder = new Wtf8Decoder();
private sourceFileMetadataCache = new Map<Path, Promise<SourceFileMetadata | undefined>>();
/** @internal */
readonly snapshotId: number;
private readonly project: Project;
private readonly client: Client;
private readonly sourceFileCache: SourceFileCache;
private readonly toPath: (fileName: string) => Path;
private readonly decoder = new Wtf8Decoder();
private readonly sourceFileMetadataCache = new Map<Path, Promise<SourceFileMetadata | undefined>>();
private ownedSnapshot: Snapshot | undefined;

constructor(
snapshotId: number,
Expand All @@ -969,6 +1016,21 @@ export class Program {
this.toPath = toPath;
}

/** @internal */
setOwnedSnapshot(snapshot: Snapshot): void {
this.ownedSnapshot = snapshot;
}

[globalThis.Symbol.dispose](): void {
this.dispose();
}

async dispose(): Promise<void> {
const snapshot = this.ownedSnapshot;
this.ownedSnapshot = undefined;
await snapshot?.dispose();
}

getCompilerOptions(): CompilerOptions {
return this.project.compilerOptions;
}
Expand Down Expand Up @@ -1259,6 +1321,10 @@ export class Program {
});
return toEmitOutput(response);
}

getProject(): Project {
return this.project;
}
}

function toEmitOutput(response: ProtocolEmitOutputResponse): EmitOutput {
Expand Down
24 changes: 24 additions & 0 deletions packages/typescript/src/api/proto.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface APIMethodInfo {
initialize: APIMethod<null, InitializeResponse>;
updateSnapshot: APIMethod<UpdateSnapshotParams, UpdateSnapshotResponse>;
updateTemporarySnapshot: APIMethod<UpdateTemporarySnapshotParams, UpdateSnapshotResponse>;
createProgram: APIMethod<CreateProgramParams, CreateProgramResponse>;
parseCommandLine: APIMethod<ParseCommandLineParams, ConfigFileResponse>;
readConfigFile: APIMethod<ReadConfigFileParams, ReadConfigFileResponse>;
parseJsonConfigFileContent: APIMethod<ParseJsonConfigFileContentParams, ConfigFileResponse>;
Expand Down Expand Up @@ -224,6 +225,18 @@ export interface UpdateTemporarySnapshotParams {
newText: string;
}

export interface CreateProgramParams {
rootFiles: readonly DocumentIdentifier[] | null;
createProgramOptions: CreateProgramOptions;
oldProgram?: CreateProgramOldProgramParams;
fileChanges?: APIFileChanges;
}

export interface CreateProgramResponse {
snapshot: number;
project: ProjectResponse | null;
}

export interface ParseCommandLineParams {
commandLine: readonly string[] | null;
}
Expand Down Expand Up @@ -888,6 +901,17 @@ export interface SnapshotChanges {
removedProjects?: string[];
}

export interface CreateProgramOptions {
compilerOptions: CompilerOptions;
projectReferences?: ProjectReference[];
configFileParsingDiagnostics?: DiagnosticResponse[];
}

export interface CreateProgramOldProgramParams {
snapshot?: number;
project?: string;
}

/** CompilerOptions contains the compiler options exposed by the API. */
export interface CompilerOptions {
allowJs?: boolean;
Expand Down
80 changes: 73 additions & 7 deletions packages/typescript/src/api/sync/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ import {
toPath,
} from "../path.ts";
import type {
APIFileChanges,
CompilerOptions,
CreateProgramOptions,
CreateProgramResponse,
Diagnostic,
DocumentIdentifier,
DocumentPosition,
Expand Down Expand Up @@ -143,6 +146,7 @@ import type {
export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts";
export { CheckFlags, CompletionItemKind, DiagnosticCategory, ElementFlags, EmitOnly, ModifierFlags, ModuleKind, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypeFormatFlags, TypePredicateKind };
export type {
APIFileChanges,
APIImportAdderAction as ImportAdderAction,
APIOptions,
AssertsIdentifierTypePredicate,
Expand All @@ -156,6 +160,7 @@ export type {
CompletionInfo,
CompletionOptions,
ConditionalType,
CreateProgramOptions,
Diagnostic,
DocumentIdentifier,
DocumentPosition,
Expand Down Expand Up @@ -395,6 +400,46 @@ export class API<FromLSP extends boolean = false> {
resetTimingInfo(): void {
return this.client.resetTimingInfo();
}

/**
* Creates a program from current filesystem state, or derives one from oldProgram after applying fileChanges.
*/
createProgram(
rootFiles: readonly DocumentIdentifier[],
createProgramOptions: CreateProgramOptions,
oldProgram?: Program,
fileChanges?: APIFileChanges,
): Program {
this.ensureInitialized();

if (fileChanges && !oldProgram) {
throw new Error("fileChanges requires an oldProgram");
}

const data: CreateProgramResponse = this.client.apiRequest("createProgram", {
rootFiles,
createProgramOptions,
...(oldProgram ? { oldProgram: { snapshot: oldProgram.snapshotId, project: oldProgram.getProject().id } } : {}),
...(fileChanges ? { fileChanges } : {}),
Comment on lines +419 to +423
});
if (!data.project) {
throw new Error("createProgram did not return a project");
}
const snapshot = new Snapshot(
{ snapshot: data.snapshot, projects: [data.project] },
this.client,
this.sourceFileCache,
this.toPath!,
() => {
this.activeSnapshots.delete(snapshot);
this.sourceFileCache.releaseSnapshot(snapshot.id);
},
);
const program = snapshot.getProjects()[0].program;
program.setOwnedSnapshot(snapshot);
this.activeSnapshots.add(snapshot);
return program;
}
}

export class InternalAPI {
Expand Down Expand Up @@ -955,13 +1000,15 @@ export class LanguageService {
}

export class Program {
private snapshotId: number;
private project: Project;
private client: Client;
private sourceFileCache: SourceFileCache;
private toPath: (fileName: string) => Path;
private decoder = new Wtf8Decoder();
private sourceFileMetadataCache = new Map<Path, SourceFileMetadata | undefined>();
/** @internal */
readonly snapshotId: number;
private readonly project: Project;
private readonly client: Client;
private readonly sourceFileCache: SourceFileCache;
private readonly toPath: (fileName: string) => Path;
private readonly decoder = new Wtf8Decoder();
private readonly sourceFileMetadataCache = new Map<Path, SourceFileMetadata | undefined>();
private ownedSnapshot: Snapshot | undefined;

constructor(
snapshotId: number,
Expand All @@ -977,6 +1024,21 @@ export class Program {
this.toPath = toPath;
}

/** @internal */
setOwnedSnapshot(snapshot: Snapshot): void {
this.ownedSnapshot = snapshot;
}

[globalThis.Symbol.dispose](): void {
this.dispose();
}

dispose(): void {
const snapshot = this.ownedSnapshot;
this.ownedSnapshot = undefined;
snapshot?.dispose();
}

getCompilerOptions(): CompilerOptions {
return this.project.compilerOptions;
}
Expand Down Expand Up @@ -1267,6 +1329,10 @@ export class Program {
});
return toEmitOutput(response);
}

getProject(): Project {
return this.project;
}
}

function toEmitOutput(response: ProtocolEmitOutputResponse): EmitOutput {
Expand Down
Loading