Skip to content

Commit 54d81cf

Browse files
authored
Add createProgram to API (#63950)
1 parent 3afef7b commit 54d81cf

24 files changed

Lines changed: 1872 additions & 94 deletions

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
},
5050
"packageManager": "npm@11.17.0+sha512.3eeaf18997b11070d313849268b23766b9db0068997dec9471073170fe43fa17f2b4d0337bf0f52330ee2274e7f5754b21b01052742e48f5c9c74d8b1e32ef43",
5151
"volta": {
52-
"node": "22.22.0",
52+
"node": "24.20.0",
5353
"npm": "11.17.0"
5454
},
5555
"allowScripts": {

packages/typescript/src/api/async/api.ts

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ import {
5252
toPath,
5353
} from "../path.ts";
5454
import type {
55+
APIFileChanges,
5556
CompilerOptions,
57+
CreateProgramOptions,
58+
CreateProgramResponse,
5659
Diagnostic,
5760
DocumentIdentifier,
5861
DocumentPosition,
@@ -140,6 +143,7 @@ export { formatDiagnostics, formatDiagnosticsWithColorAndContext } from "../diag
140143
export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts";
141144
export { CheckFlags, CompletionItemKind, DiagnosticCategory, ElementFlags, EmitOnly, JsxEmit, ModifierFlags, ModuleKind, ModuleResolutionKind, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypeFormatFlags, TypePredicateKind };
142145
export type {
146+
APIFileChanges,
143147
APIImportAdderAction as ImportAdderAction,
144148
APIOptions,
145149
AssertsIdentifierTypePredicate,
@@ -153,6 +157,7 @@ export type {
153157
CompletionInfo,
154158
CompletionOptions,
155159
ConditionalType,
160+
CreateProgramOptions,
156161
Diagnostic,
157162
DocumentIdentifier,
158163
DocumentPosition,
@@ -438,6 +443,60 @@ export class API<FromLSP extends boolean = false> implements FormatDiagnosticsHo
438443
resetTimingInfo(): Promise<void> {
439444
return this.client.resetTimingInfo();
440445
}
446+
447+
private isProgramActive(program: Program): boolean {
448+
const project = program.getProject();
449+
for (const snapshot of this.activeSnapshots) {
450+
if (!snapshot.isDisposed() && snapshot.getProject(project.configFileName)?.program === program) {
451+
return true;
452+
}
453+
}
454+
return false;
455+
}
456+
457+
/**
458+
* Creates a program from current filesystem state, or derives one from oldProgram after applying fileChanges.
459+
*/
460+
async createProgram(
461+
rootFiles: readonly DocumentIdentifier[],
462+
createProgramOptions: CreateProgramOptions,
463+
oldProgram?: Program,
464+
fileChanges?: APIFileChanges,
465+
): Promise<Program> {
466+
await this.ensureInitialized();
467+
468+
if (fileChanges && !oldProgram) {
469+
throw new Error("fileChanges requires an oldProgram");
470+
}
471+
if (oldProgram && !this.isProgramActive(oldProgram)) {
472+
throw new Error("oldProgram must belong to this API instance and reference an active snapshot");
473+
}
474+
475+
const data: CreateProgramResponse = await this.client.apiRequest("createProgram", {
476+
rootFiles,
477+
createProgramOptions,
478+
...(oldProgram ? { oldProgram: { snapshot: oldProgram.snapshotId, project: oldProgram.getProject().id } } : {}),
479+
...(fileChanges ? { fileChanges } : {}),
480+
});
481+
if (!data.project) {
482+
throw new Error("createProgram did not return a project");
483+
}
484+
const snapshot = new Snapshot(
485+
{ snapshot: data.snapshot, projects: [data.project] },
486+
this.client,
487+
this.sourceFileCache,
488+
this.toPath!,
489+
this,
490+
() => {
491+
this.activeSnapshots.delete(snapshot);
492+
this.sourceFileCache.releaseSnapshot(snapshot.id);
493+
},
494+
);
495+
const program = snapshot.getProjects()[0].program;
496+
program.setOwnedSnapshot(snapshot);
497+
this.activeSnapshots.add(snapshot);
498+
return program;
499+
}
441500
}
442501

443502
type EnsureInitialized = () => Promise<void>; // @sync: type EnsureInitialized = (() => void) & { gen(): Generator<ProtocolRequest, void, ProtocolResponse["result"]>; };
@@ -1005,14 +1064,16 @@ export class LanguageService {
10051064
}
10061065

10071066
export class Program implements FormatDiagnosticsHost {
1008-
private snapshotId: number;
1009-
private project: Project;
1010-
private client: Client;
1011-
private sourceFileCache: SourceFileCache;
1012-
private toPath: (fileName: string) => Path;
1013-
private formatDiagnosticsHost: FormatDiagnosticsHost;
1014-
private decoder = new Wtf8Decoder();
1015-
private sourceFileMetadataCache = new Map<Path, Promise<SourceFileMetadata | undefined>>();
1067+
/** @internal */
1068+
readonly snapshotId: number;
1069+
private readonly project: Project;
1070+
private readonly client: Client;
1071+
private readonly sourceFileCache: SourceFileCache;
1072+
private readonly toPath: (fileName: string) => Path;
1073+
private readonly formatDiagnosticsHost: FormatDiagnosticsHost;
1074+
private readonly decoder = new Wtf8Decoder();
1075+
private readonly sourceFileMetadataCache = new Map<Path, Promise<SourceFileMetadata | undefined>>();
1076+
private ownedSnapshot: Snapshot | undefined;
10161077

10171078
constructor(
10181079
snapshotId: number,
@@ -1042,6 +1103,21 @@ export class Program implements FormatDiagnosticsHost {
10421103
return this.project.compilerOptions.newLine === NewLineKind.CRLF ? "\r\n" : "\n";
10431104
}
10441105

1106+
/** @internal */
1107+
setOwnedSnapshot(snapshot: Snapshot): void {
1108+
this.ownedSnapshot = snapshot;
1109+
}
1110+
1111+
[globalThis.Symbol.dispose](): void {
1112+
this.dispose();
1113+
}
1114+
1115+
async dispose(): Promise<void> {
1116+
const snapshot = this.ownedSnapshot;
1117+
this.ownedSnapshot = undefined;
1118+
if (snapshot) await snapshot.dispose();
1119+
}
1120+
10451121
getCompilerOptions(): CompilerOptions {
10461122
return this.project.compilerOptions;
10471123
}
@@ -1332,6 +1408,10 @@ export class Program implements FormatDiagnosticsHost {
13321408
});
13331409
return toEmitOutput(response);
13341410
}
1411+
1412+
getProject(): Project {
1413+
return this.project;
1414+
}
13351415
}
13361416

13371417
function toEmitOutput(response: ProtocolEmitOutputResponse): EmitOutput {

packages/typescript/src/api/proto.generated.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export interface APIMethodInfo {
2222
initialize: APIMethod<null, InitializeResponse>;
2323
updateSnapshot: APIMethod<UpdateSnapshotParams, UpdateSnapshotResponse>;
2424
updateTemporarySnapshot: APIMethod<UpdateTemporarySnapshotParams, UpdateSnapshotResponse>;
25+
createProgram: APIMethod<CreateProgramParams, CreateProgramResponse>;
2526
parseCommandLine: APIMethod<ParseCommandLineParams, ConfigFileResponse>;
2627
readConfigFile: APIMethod<ReadConfigFileParams, ReadConfigFileResponse>;
2728
parseJsonConfigFileContent: APIMethod<ParseJsonConfigFileContentParams, ConfigFileResponse>;
@@ -243,6 +244,18 @@ export interface UpdateTemporarySnapshotParams {
243244
newText: string;
244245
}
245246

247+
export interface CreateProgramParams {
248+
rootFiles: readonly DocumentIdentifier[] | null;
249+
createProgramOptions: CreateProgramOptions;
250+
oldProgram?: CreateProgramOldProgramParams;
251+
fileChanges?: APIFileChanges;
252+
}
253+
254+
export interface CreateProgramResponse {
255+
snapshot: number;
256+
project: ProjectResponse | null;
257+
}
258+
246259
export interface ParseCommandLineParams {
247260
commandLine: readonly string[] | null;
248261
}
@@ -889,6 +902,7 @@ export interface ProfileResult {
889902
export interface BatchRequest {
890903
method:
891904
| "batchRequests"
905+
| "createProgram"
892906
| "emit"
893907
| "emitToString"
894908
| "formatNodeForInsertion"
@@ -1036,6 +1050,7 @@ export interface BatchRequest {
10361050
export interface BatchResponse {
10371051
method:
10381052
| "batchRequests"
1053+
| "createProgram"
10391054
| "emit"
10401055
| "emitToString"
10411056
| "formatNodeForInsertion"
@@ -1211,6 +1226,17 @@ export interface SnapshotChanges {
12111226
removedProjects?: string[];
12121227
}
12131228

1229+
export interface CreateProgramOptions {
1230+
compilerOptions: CompilerOptions;
1231+
projectReferences?: ProjectReference[];
1232+
configFileParsingDiagnostics?: DiagnosticResponse[];
1233+
}
1234+
1235+
export interface CreateProgramOldProgramParams {
1236+
snapshot?: number;
1237+
project?: string;
1238+
}
1239+
12141240
/** CompilerOptions contains the compiler options exposed by the API. */
12151241
export interface CompilerOptions {
12161242
allowJs?: boolean;

0 commit comments

Comments
 (0)