@@ -52,7 +52,10 @@ import {
5252 toPath ,
5353} from "../path.ts" ;
5454import type {
55+ APIFileChanges ,
5556 CompilerOptions ,
57+ CreateProgramOptions ,
58+ CreateProgramResponse ,
5659 Diagnostic ,
5760 DocumentIdentifier ,
5861 DocumentPosition ,
@@ -140,6 +143,7 @@ export { formatDiagnostics, formatDiagnosticsWithColorAndContext } from "../diag
140143export { documentURIToFileName , fileNameToDocumentURI } from "../path.ts" ;
141144export { CheckFlags , CompletionItemKind , DiagnosticCategory , ElementFlags , EmitOnly , JsxEmit , ModifierFlags , ModuleKind , ModuleResolutionKind , NodeBuilderFlags , ObjectFlags , SignatureFlags , SignatureKind , SymbolFlags , TypeFlags , TypeFormatFlags , TypePredicateKind } ;
142145export 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
443502type EnsureInitialized = ( ) => Promise < void > ; // @sync : type EnsureInitialized = (() => void) & { gen(): Generator<ProtocolRequest, void, ProtocolResponse["result"]>; };
@@ -1005,14 +1064,16 @@ export class LanguageService {
10051064}
10061065
10071066export 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
13371417function toEmitOutput ( response : ProtocolEmitOutputResponse ) : EmitOutput {
0 commit comments