• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*@internal*/
2namespace ts {
3    export interface ReusableDiagnostic extends ReusableDiagnosticRelatedInformation {
4        /** May store more in future. For now, this will simply be `true` to indicate when a diagnostic is an unused-identifier diagnostic. */
5        reportsUnnecessary?: {};
6        reportDeprecated?: {}
7        source?: string;
8        relatedInformation?: ReusableDiagnosticRelatedInformation[];
9        skippedOn?: keyof CompilerOptions;
10    }
11
12    export interface ReusableDiagnosticRelatedInformation {
13        category: DiagnosticCategory;
14        code: number;
15        file: string | undefined;
16        start: number | undefined;
17        length: number | undefined;
18        messageText: string | ReusableDiagnosticMessageChain;
19    }
20
21    export type ReusableDiagnosticMessageChain = DiagnosticMessageChain;
22
23    export interface ReusableBuilderProgramState extends BuilderState {
24        /**
25         * Cache of bind and check diagnostics for files with their Path being the key
26         */
27        semanticDiagnosticsPerFile?: ESMap<Path, readonly ReusableDiagnostic[] | readonly Diagnostic[]> | undefined;
28        /**
29         * The map has key by source file's path that has been changed
30         */
31        changedFilesSet?: Set<Path>;
32        /**
33         * program corresponding to this state
34         */
35        program?: Program | undefined;
36        /**
37         * compilerOptions for the program
38         */
39        compilerOptions: CompilerOptions;
40        /**
41         * Files pending to be emitted
42         */
43        affectedFilesPendingEmit?: readonly Path[] | undefined;
44        /**
45         * Files pending to be emitted kind.
46         */
47        affectedFilesPendingEmitKind?: ESMap<Path, BuilderFileEmit> | undefined;
48        /**
49         * Current index to retrieve pending affected file
50         */
51        affectedFilesPendingEmitIndex?: number | undefined;
52        /*
53         * true if semantic diagnostics are ReusableDiagnostic instead of Diagnostic
54         */
55        hasReusableDiagnostic?: true;
56        /**
57         * Hash of d.ts emitted for the file, use to track when emit of d.ts changes
58         */
59        emitSignatures?: ESMap<Path, string>;
60        /**
61         * Hash of d.ts emit with --out
62         */
63        outSignature?: string;
64        /**
65         * Name of the file whose dts was the latest to change
66         */
67        latestChangedDtsFile: string | undefined;
68    }
69
70    export const enum BuilderFileEmit {
71        DtsOnly,
72        Full
73    }
74
75    /**
76     * State to store the changed files, affected files and cache semantic diagnostics
77     */
78    // TODO: GH#18217 Properties of this interface are frequently asserted to be defined.
79    export interface BuilderProgramState extends BuilderState, ReusableBuilderProgramState {
80        /**
81         * Cache of bind and check diagnostics for files with their Path being the key
82         */
83        semanticDiagnosticsPerFile: ESMap<Path, readonly Diagnostic[]> | undefined;
84        /**
85         * The map has key by source file's path that has been changed
86         */
87        changedFilesSet: Set<Path>;
88        /**
89         * Set of affected files being iterated
90         */
91        affectedFiles?: readonly SourceFile[] | undefined;
92        /**
93         * Current index to retrieve affected file from
94         */
95        affectedFilesIndex: number | undefined;
96        /**
97         * Current changed file for iterating over affected files
98         */
99        currentChangedFilePath?: Path | undefined;
100        /**
101         * Already seen affected files
102         */
103        seenAffectedFiles: Set<Path> | undefined;
104        /**
105         * whether this program has cleaned semantic diagnostics cache for lib files
106         */
107        cleanedDiagnosticsOfLibFiles?: boolean;
108        /**
109         * True if the semantic diagnostics were copied from the old state
110         */
111        semanticDiagnosticsFromOldState?: Set<Path>;
112        /**
113         * Records if change in dts emit was detected
114         */
115        hasChangedEmitSignature?: boolean;
116        /**
117         * Files pending to be emitted
118         */
119        affectedFilesPendingEmit: Path[] | undefined;
120        /**
121         * true if build info is emitted
122         */
123        buildInfoEmitPending: boolean;
124        /**
125         * Already seen emitted files
126         */
127        seenEmittedFiles: ESMap<Path, BuilderFileEmit> | undefined;
128        /**
129         * true if program has been emitted
130         */
131        programEmitComplete?: true;
132        /** Stores list of files that change signature during emit - test only */
133        filesChangingSignature?: Set<Path>;
134    }
135
136    export type SavedBuildProgramEmitState = Pick<BuilderProgramState,
137        "affectedFilesPendingEmit" |
138        "affectedFilesPendingEmitIndex" |
139        "affectedFilesPendingEmitKind" |
140        "seenEmittedFiles" |
141        "programEmitComplete" |
142        "emitSignatures" |
143        "outSignature" |
144        "latestChangedDtsFile" |
145        "hasChangedEmitSignature"
146    > & { changedFilesSet: BuilderProgramState["changedFilesSet"] | undefined };
147
148    function hasSameKeys(map1: ReadonlyCollection<string> | undefined, map2: ReadonlyCollection<string> | undefined): boolean {
149        // Has same size and every key is present in both maps
150        return map1 === map2 || map1 !== undefined && map2 !== undefined && map1.size === map2.size && !forEachKey(map1, key => !map2.has(key));
151    }
152
153    /**
154     * Create the state so that we can iterate on changedFiles/affected files
155     */
156    function createBuilderProgramState(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState: Readonly<ReusableBuilderProgramState> | undefined, disableUseFileVersionAsSignature: boolean | undefined): BuilderProgramState {
157        const state = BuilderState.create(newProgram, getCanonicalFileName, oldState, disableUseFileVersionAsSignature) as BuilderProgramState;
158        state.program = newProgram;
159        const compilerOptions = newProgram.getCompilerOptions();
160        state.compilerOptions = compilerOptions;
161        const outFilePath = outFile(compilerOptions);
162        // With --out or --outFile, any change affects all semantic diagnostics so no need to cache them
163        if (!outFilePath) {
164            state.semanticDiagnosticsPerFile = new Map();
165        }
166        else if (compilerOptions.composite && oldState?.outSignature && outFilePath === outFile(oldState?.compilerOptions)) {
167            state.outSignature = oldState?.outSignature;
168        }
169        state.changedFilesSet = new Set();
170        state.latestChangedDtsFile = compilerOptions.composite ? oldState?.latestChangedDtsFile : undefined;
171
172        const useOldState = BuilderState.canReuseOldState(state.referencedMap, oldState);
173        const oldCompilerOptions = useOldState ? oldState!.compilerOptions : undefined;
174        const canCopySemanticDiagnostics = useOldState && oldState!.semanticDiagnosticsPerFile && !!state.semanticDiagnosticsPerFile &&
175            !compilerOptionsAffectSemanticDiagnostics(compilerOptions, oldCompilerOptions!);
176        // We can only reuse emit signatures (i.e. .d.ts signatures) if the .d.ts file is unchanged,
177        // which will eg be depedent on change in options like declarationDir and outDir options are unchanged.
178        // We need to look in oldState.compilerOptions, rather than oldCompilerOptions (i.e.we need to disregard useOldState) because
179        // oldCompilerOptions can be undefined if there was change in say module from None to some other option
180        // which would make useOldState as false since we can now use reference maps that are needed to track what to emit, what to check etc
181        // but that option change does not affect d.ts file name so emitSignatures should still be reused.
182        const canCopyEmitSignatures = compilerOptions.composite &&
183            oldState?.emitSignatures &&
184            !outFilePath &&
185            !compilerOptionsAffectDeclarationPath(compilerOptions, oldState.compilerOptions);
186        if (useOldState) {
187            // Copy old state's changed files set
188            oldState!.changedFilesSet?.forEach(value => state.changedFilesSet.add(value));
189            if (!outFilePath && oldState!.affectedFilesPendingEmit) {
190                state.affectedFilesPendingEmit = oldState!.affectedFilesPendingEmit.slice();
191                state.affectedFilesPendingEmitKind = oldState!.affectedFilesPendingEmitKind && new Map(oldState!.affectedFilesPendingEmitKind);
192                state.affectedFilesPendingEmitIndex = oldState!.affectedFilesPendingEmitIndex;
193                state.seenAffectedFiles = new Set();
194            }
195        }
196
197        // Update changed files and copy semantic diagnostics if we can
198        const referencedMap = state.referencedMap;
199        const oldReferencedMap = useOldState ? oldState!.referencedMap : undefined;
200        const copyDeclarationFileDiagnostics = canCopySemanticDiagnostics && !compilerOptions.skipLibCheck === !oldCompilerOptions!.skipLibCheck;
201        const copyLibFileDiagnostics = copyDeclarationFileDiagnostics && !compilerOptions.skipDefaultLibCheck === !oldCompilerOptions!.skipDefaultLibCheck;
202        state.fileInfos.forEach((info, sourceFilePath) => {
203            let oldInfo: Readonly<BuilderState.FileInfo> | undefined;
204            let newReferences: ReadonlySet<Path> | undefined;
205
206            // if not using old state, every file is changed
207            if (!useOldState ||
208                // File wasn't present in old state
209                !(oldInfo = oldState!.fileInfos.get(sourceFilePath)) ||
210                // versions dont match
211                oldInfo.version !== info.version ||
212                // Implied formats dont match
213                oldInfo.impliedFormat !== info.impliedFormat ||
214                // Referenced files changed
215                !hasSameKeys(newReferences = referencedMap && referencedMap.getValues(sourceFilePath), oldReferencedMap && oldReferencedMap.getValues(sourceFilePath)) ||
216                // Referenced file was deleted in the new program
217                newReferences && forEachKey(newReferences, path => !state.fileInfos.has(path) && oldState!.fileInfos.has(path))) {
218                // Register file as changed file and do not copy semantic diagnostics, since all changed files need to be re-evaluated
219                state.changedFilesSet.add(sourceFilePath);
220            }
221            else if (canCopySemanticDiagnostics) {
222                const sourceFile = newProgram.getSourceFileByPath(sourceFilePath)!;
223
224                if (sourceFile.isDeclarationFile && !copyDeclarationFileDiagnostics) return;
225                if (sourceFile.hasNoDefaultLib && !copyLibFileDiagnostics) return;
226
227                // Unchanged file copy diagnostics
228                const diagnostics = oldState!.semanticDiagnosticsPerFile!.get(sourceFilePath);
229                if (diagnostics) {
230                    state.semanticDiagnosticsPerFile!.set(sourceFilePath, oldState!.hasReusableDiagnostic ? convertToDiagnostics(diagnostics as readonly ReusableDiagnostic[], newProgram, getCanonicalFileName) : diagnostics as readonly Diagnostic[]);
231                    if (!state.semanticDiagnosticsFromOldState) {
232                        state.semanticDiagnosticsFromOldState = new Set();
233                    }
234                    state.semanticDiagnosticsFromOldState.add(sourceFilePath);
235                }
236            }
237            if (canCopyEmitSignatures) {
238                const oldEmitSignature = oldState.emitSignatures.get(sourceFilePath);
239                if (oldEmitSignature) (state.emitSignatures ||= new Map()).set(sourceFilePath, oldEmitSignature);
240            }
241        });
242
243        // If the global file is removed, add all files as changed
244        if (useOldState && forEachEntry(oldState!.fileInfos, (info, sourceFilePath) => info.affectsGlobalScope && !state.fileInfos.has(sourceFilePath))) {
245            BuilderState.getAllFilesExcludingDefaultLibraryFile(state, newProgram, /*firstSourceFile*/ undefined)
246                .forEach(file => state.changedFilesSet.add(file.resolvedPath));
247        }
248        else if (oldCompilerOptions && !outFilePath && compilerOptionsAffectEmit(compilerOptions, oldCompilerOptions)) {
249            // Add all files to affectedFilesPendingEmit since emit changed
250            newProgram.getSourceFiles().forEach(f => addToAffectedFilesPendingEmit(state, f.resolvedPath, BuilderFileEmit.Full));
251            Debug.assert(!state.seenAffectedFiles || !state.seenAffectedFiles.size);
252            state.seenAffectedFiles = state.seenAffectedFiles || new Set();
253        }
254        // Since old states change files set is copied, any additional change means we would need to emit build info
255        state.buildInfoEmitPending = !useOldState || state.changedFilesSet.size !== (oldState!.changedFilesSet?.size || 0);
256        return state;
257    }
258
259    function convertToDiagnostics(diagnostics: readonly ReusableDiagnostic[], newProgram: Program, getCanonicalFileName: GetCanonicalFileName): readonly Diagnostic[] {
260        if (!diagnostics.length) return emptyArray;
261        const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(newProgram.getCompilerOptions())!, newProgram.getCurrentDirectory()));
262        return diagnostics.map(diagnostic => {
263            const result: Diagnostic = convertToDiagnosticRelatedInformation(diagnostic, newProgram, toPath);
264            result.reportsUnnecessary = diagnostic.reportsUnnecessary;
265            result.reportsDeprecated = diagnostic.reportDeprecated;
266            result.source = diagnostic.source;
267            result.skippedOn = diagnostic.skippedOn;
268            const { relatedInformation } = diagnostic;
269            result.relatedInformation = relatedInformation ?
270                relatedInformation.length ?
271                    relatedInformation.map(r => convertToDiagnosticRelatedInformation(r, newProgram, toPath)) :
272                    [] :
273                undefined;
274            return result;
275        });
276
277        function toPath(path: string) {
278            return ts.toPath(path, buildInfoDirectory, getCanonicalFileName);
279        }
280    }
281
282    function convertToDiagnosticRelatedInformation(diagnostic: ReusableDiagnosticRelatedInformation, newProgram: Program, toPath: (path: string) => Path): DiagnosticRelatedInformation {
283        const { file } = diagnostic;
284        return {
285            ...diagnostic,
286            file: file ? newProgram.getSourceFileByPath(toPath(file)) : undefined
287        };
288    }
289
290    /**
291     * Releases program and other related not needed properties
292     */
293    function releaseCache(state: BuilderProgramState) {
294        BuilderState.releaseCache(state);
295        state.program = undefined;
296    }
297
298    function backupBuilderProgramEmitState(state: Readonly<BuilderProgramState>): SavedBuildProgramEmitState {
299        const outFilePath = outFile(state.compilerOptions);
300        // Only in --out changeFileSet is kept around till emit
301        Debug.assert(!state.changedFilesSet.size || outFilePath);
302        return {
303            affectedFilesPendingEmit: state.affectedFilesPendingEmit && state.affectedFilesPendingEmit.slice(),
304            affectedFilesPendingEmitKind: state.affectedFilesPendingEmitKind && new Map(state.affectedFilesPendingEmitKind),
305            affectedFilesPendingEmitIndex: state.affectedFilesPendingEmitIndex,
306            seenEmittedFiles: state.seenEmittedFiles && new Map(state.seenEmittedFiles),
307            programEmitComplete: state.programEmitComplete,
308            emitSignatures: state.emitSignatures && new Map(state.emitSignatures),
309            outSignature: state.outSignature,
310            latestChangedDtsFile: state.latestChangedDtsFile,
311            hasChangedEmitSignature: state.hasChangedEmitSignature,
312            changedFilesSet: outFilePath ? new Set(state.changedFilesSet) : undefined,
313        };
314    }
315
316    function restoreBuilderProgramEmitState(state: BuilderProgramState, savedEmitState: SavedBuildProgramEmitState) {
317        state.affectedFilesPendingEmit = savedEmitState.affectedFilesPendingEmit;
318        state.affectedFilesPendingEmitKind = savedEmitState.affectedFilesPendingEmitKind;
319        state.affectedFilesPendingEmitIndex = savedEmitState.affectedFilesPendingEmitIndex;
320        state.seenEmittedFiles = savedEmitState.seenEmittedFiles;
321        state.programEmitComplete = savedEmitState.programEmitComplete;
322        state.emitSignatures = savedEmitState.emitSignatures;
323        state.outSignature = savedEmitState.outSignature;
324        state.latestChangedDtsFile = savedEmitState.latestChangedDtsFile;
325        state.hasChangedEmitSignature = savedEmitState.hasChangedEmitSignature;
326        if (savedEmitState.changedFilesSet) state.changedFilesSet = savedEmitState.changedFilesSet;
327    }
328
329    /**
330     * Verifies that source file is ok to be used in calls that arent handled by next
331     */
332    function assertSourceFileOkWithoutNextAffectedCall(state: BuilderProgramState, sourceFile: SourceFile | undefined) {
333        Debug.assert(!sourceFile || !state.affectedFiles || state.affectedFiles[state.affectedFilesIndex! - 1] !== sourceFile || !state.semanticDiagnosticsPerFile!.has(sourceFile.resolvedPath));
334    }
335
336    /**
337     * This function returns the next affected file to be processed.
338     * Note that until doneAffected is called it would keep reporting same result
339     * This is to allow the callers to be able to actually remove affected file only when the operation is complete
340     * eg. if during diagnostics check cancellation token ends up cancelling the request, the affected file should be retained
341     */
342    function getNextAffectedFile(
343        state: BuilderProgramState,
344        cancellationToken: CancellationToken | undefined,
345        computeHash: BuilderState.ComputeHash,
346        getCanonicalFileName: GetCanonicalFileName,
347        host: BuilderProgramHost
348    ): SourceFile | Program | undefined {
349        while (true) {
350            const { affectedFiles } = state;
351            if (affectedFiles) {
352                const seenAffectedFiles = state.seenAffectedFiles!;
353                let affectedFilesIndex = state.affectedFilesIndex!; // TODO: GH#18217
354                while (affectedFilesIndex < affectedFiles.length) {
355                    const affectedFile = affectedFiles[affectedFilesIndex];
356                    if (!seenAffectedFiles.has(affectedFile.resolvedPath)) {
357                        // Set the next affected file as seen and remove the cached semantic diagnostics
358                        state.affectedFilesIndex = affectedFilesIndex;
359                        handleDtsMayChangeOfAffectedFile(
360                            state,
361                            affectedFile,
362                            cancellationToken,
363                            computeHash,
364                            getCanonicalFileName,
365                            host
366                        );
367                        return affectedFile;
368                    }
369                    affectedFilesIndex++;
370                }
371
372                // Remove the changed file from the change set
373                state.changedFilesSet.delete(state.currentChangedFilePath!);
374                state.currentChangedFilePath = undefined;
375                // Commit the changes in file signature
376                state.oldSignatures?.clear();
377                state.oldExportedModulesMap?.clear();
378                state.affectedFiles = undefined;
379            }
380
381            // Get next changed file
382            const nextKey = state.changedFilesSet.keys().next();
383            if (nextKey.done) {
384                // Done
385                return undefined;
386            }
387
388            // With --out or --outFile all outputs go into single file
389            // so operations are performed directly on program, return program
390            const program = Debug.checkDefined(state.program);
391            const compilerOptions = program.getCompilerOptions();
392            if (outFile(compilerOptions)) {
393                Debug.assert(!state.semanticDiagnosticsPerFile);
394                return program;
395            }
396
397            // Get next batch of affected files
398            state.affectedFiles = BuilderState.getFilesAffectedByWithOldState(
399                state,
400                program,
401                nextKey.value,
402                cancellationToken,
403                computeHash,
404                getCanonicalFileName,
405            );
406            state.currentChangedFilePath = nextKey.value;
407            state.affectedFilesIndex = 0;
408            if (!state.seenAffectedFiles) state.seenAffectedFiles = new Set();
409        }
410    }
411
412    function clearAffectedFilesPendingEmit(state: BuilderProgramState) {
413        state.affectedFilesPendingEmit = undefined;
414        state.affectedFilesPendingEmitKind = undefined;
415        state.affectedFilesPendingEmitIndex = undefined;
416    }
417
418    /**
419     * Returns next file to be emitted from files that retrieved semantic diagnostics but did not emit yet
420     */
421    function getNextAffectedFilePendingEmit(state: BuilderProgramState) {
422        const { affectedFilesPendingEmit } = state;
423        if (affectedFilesPendingEmit) {
424            const seenEmittedFiles = (state.seenEmittedFiles || (state.seenEmittedFiles = new Map()));
425            for (let i = state.affectedFilesPendingEmitIndex!; i < affectedFilesPendingEmit.length; i++) {
426                const affectedFile = Debug.checkDefined(state.program).getSourceFileByPath(affectedFilesPendingEmit[i]);
427                if (affectedFile) {
428                    const seenKind = seenEmittedFiles.get(affectedFile.resolvedPath);
429                    const emitKind = Debug.checkDefined(Debug.checkDefined(state.affectedFilesPendingEmitKind).get(affectedFile.resolvedPath));
430                    if (seenKind === undefined || seenKind < emitKind) {
431                        // emit this file
432                        state.affectedFilesPendingEmitIndex = i;
433                        return { affectedFile, emitKind };
434                    }
435                }
436            }
437            clearAffectedFilesPendingEmit(state);
438        }
439        return undefined;
440    }
441
442    function removeDiagnosticsOfLibraryFiles(state: BuilderProgramState) {
443        if (!state.cleanedDiagnosticsOfLibFiles) {
444            state.cleanedDiagnosticsOfLibFiles = true;
445            const program = Debug.checkDefined(state.program);
446            const options = program.getCompilerOptions();
447            forEach(program.getSourceFiles(), f =>
448                program.isSourceFileDefaultLibrary(f) &&
449                !(skipTypeChecking(f, options, program) || (f.isDeclarationFile && !!options.needDoArkTsLinter)) &&
450                removeSemanticDiagnosticsOf(state, f.resolvedPath)
451            );
452        }
453    }
454
455    /**
456     *  Handles semantic diagnostics and dts emit for affectedFile and files, that are referencing modules that export entities from affected file
457     *  This is because even though js emit doesnt change, dts emit / type used can change resulting in need for dts emit and js change
458     */
459    function handleDtsMayChangeOfAffectedFile(
460        state: BuilderProgramState,
461        affectedFile: SourceFile,
462        cancellationToken: CancellationToken | undefined,
463        computeHash: BuilderState.ComputeHash,
464        getCanonicalFileName: GetCanonicalFileName,
465        host: BuilderProgramHost,
466    ) {
467        removeSemanticDiagnosticsOf(state, affectedFile.resolvedPath);
468
469        // If affected files is everything except default library, then nothing more to do
470        if (state.allFilesExcludingDefaultLibraryFile === state.affectedFiles) {
471            removeDiagnosticsOfLibraryFiles(state);
472            // When a change affects the global scope, all files are considered to be affected without updating their signature
473            // That means when affected file is handled, its signature can be out of date
474            // To avoid this, ensure that we update the signature for any affected file in this scenario.
475            BuilderState.updateShapeSignature(
476                state,
477                Debug.checkDefined(state.program),
478                affectedFile,
479                cancellationToken,
480                computeHash,
481                getCanonicalFileName,
482            );
483            return;
484        }
485        if (state.compilerOptions.assumeChangesOnlyAffectDirectDependencies) return;
486        handleDtsMayChangeOfReferencingExportOfAffectedFile(
487            state,
488            affectedFile,
489            cancellationToken,
490            computeHash,
491            getCanonicalFileName,
492            host,
493        );
494    }
495
496    /**
497     * Handle the dts may change, so they need to be added to pending emit if dts emit is enabled,
498     * Also we need to make sure signature is updated for these files
499     */
500    function handleDtsMayChangeOf(
501        state: BuilderProgramState,
502        path: Path,
503        cancellationToken: CancellationToken | undefined,
504        computeHash: BuilderState.ComputeHash,
505        getCanonicalFileName: GetCanonicalFileName,
506        host: BuilderProgramHost
507    ): void {
508        removeSemanticDiagnosticsOf(state, path);
509
510        if (!state.changedFilesSet.has(path)) {
511            const program = Debug.checkDefined(state.program);
512            const sourceFile = program.getSourceFileByPath(path);
513            if (sourceFile) {
514                // Even though the js emit doesnt change and we are already handling dts emit and semantic diagnostics
515                // we need to update the signature to reflect correctness of the signature(which is output d.ts emit) of this file
516                // This ensures that we dont later during incremental builds considering wrong signature.
517                // Eg where this also is needed to ensure that .tsbuildinfo generated by incremental build should be same as if it was first fresh build
518                // But we avoid expensive full shape computation, as using file version as shape is enough for correctness.
519                BuilderState.updateShapeSignature(
520                    state,
521                    program,
522                    sourceFile,
523                    cancellationToken,
524                    computeHash,
525                    getCanonicalFileName,
526                    !host.disableUseFileVersionAsSignature
527                );
528                // If not dts emit, nothing more to do
529                if (getEmitDeclarations(state.compilerOptions)) {
530                    addToAffectedFilesPendingEmit(state, path, BuilderFileEmit.DtsOnly);
531                }
532            }
533        }
534    }
535
536    /**
537     * Removes semantic diagnostics for path and
538     * returns true if there are no more semantic diagnostics from the old state
539     */
540    function removeSemanticDiagnosticsOf(state: BuilderProgramState, path: Path) {
541        if (!state.semanticDiagnosticsFromOldState) {
542            return true;
543        }
544        state.semanticDiagnosticsFromOldState.delete(path);
545        state.semanticDiagnosticsPerFile!.delete(path);
546        return !state.semanticDiagnosticsFromOldState.size;
547    }
548
549    function isChangedSignature(state: BuilderProgramState, path: Path) {
550        const oldSignature = Debug.checkDefined(state.oldSignatures).get(path) || undefined;
551        const newSignature = Debug.checkDefined(state.fileInfos.get(path)).signature;
552        return newSignature !== oldSignature;
553    }
554
555    function handleDtsMayChangeOfGlobalScope(
556        state: BuilderProgramState,
557        filePath: Path,
558        cancellationToken: CancellationToken | undefined,
559        computeHash: BuilderState.ComputeHash,
560        getCanonicalFileName: GetCanonicalFileName,
561        host: BuilderProgramHost,
562    ): boolean {
563        if (!state.fileInfos.get(filePath)?.affectsGlobalScope) return false;
564        // Every file needs to be handled
565        BuilderState.getAllFilesExcludingDefaultLibraryFile(state, state.program!, /*firstSourceFile*/ undefined)
566            .forEach(file => handleDtsMayChangeOf(
567                state,
568                file.resolvedPath,
569                cancellationToken,
570                computeHash,
571                getCanonicalFileName,
572                host,
573            ));
574        removeDiagnosticsOfLibraryFiles(state);
575        return true;
576    }
577
578    /**
579     * Iterate on referencing modules that export entities from affected file and delete diagnostics and add pending emit
580     */
581    function handleDtsMayChangeOfReferencingExportOfAffectedFile(
582        state: BuilderProgramState,
583        affectedFile: SourceFile,
584        cancellationToken: CancellationToken | undefined,
585        computeHash: BuilderState.ComputeHash,
586        getCanonicalFileName: GetCanonicalFileName,
587        host: BuilderProgramHost
588    ) {
589        // If there was change in signature (dts output) for the changed file,
590        // then only we need to handle pending file emit
591        if (!state.exportedModulesMap || !state.changedFilesSet.has(affectedFile.resolvedPath)) return;
592        if (!isChangedSignature(state, affectedFile.resolvedPath)) return;
593
594        // Since isolated modules dont change js files, files affected by change in signature is itself
595        // But we need to cleanup semantic diagnostics and queue dts emit for affected files
596        if (state.compilerOptions.isolatedModules) {
597            const seenFileNamesMap = new Map<Path, true>();
598            seenFileNamesMap.set(affectedFile.resolvedPath, true);
599            const queue = BuilderState.getReferencedByPaths(state, affectedFile.resolvedPath);
600            while (queue.length > 0) {
601                const currentPath = queue.pop()!;
602                if (!seenFileNamesMap.has(currentPath)) {
603                    seenFileNamesMap.set(currentPath, true);
604                    if (handleDtsMayChangeOfGlobalScope(state, currentPath, cancellationToken, computeHash, getCanonicalFileName, host)) return;
605                    handleDtsMayChangeOf(state, currentPath, cancellationToken, computeHash, getCanonicalFileName, host);
606                    if (isChangedSignature(state, currentPath)) {
607                        const currentSourceFile = Debug.checkDefined(state.program).getSourceFileByPath(currentPath)!;
608                        queue.push(...BuilderState.getReferencedByPaths(state, currentSourceFile.resolvedPath));
609                    }
610                }
611            }
612        }
613
614        const seenFileAndExportsOfFile = new Set<string>();
615        // Go through exported modules from cache first
616        // If exported modules has path, all files referencing file exported from are affected
617        state.exportedModulesMap.getKeys(affectedFile.resolvedPath)?.forEach(exportedFromPath => {
618            if (handleDtsMayChangeOfGlobalScope(state, exportedFromPath, cancellationToken, computeHash, getCanonicalFileName, host)) return true;
619            const references = state.referencedMap!.getKeys(exportedFromPath);
620            return references && forEachKey(references, filePath =>
621                handleDtsMayChangeOfFileAndExportsOfFile(
622                    state,
623                    filePath,
624                    seenFileAndExportsOfFile,
625                    cancellationToken,
626                    computeHash,
627                    getCanonicalFileName,
628                    host,
629                )
630            );
631        });
632    }
633
634    /**
635     * handle dts and semantic diagnostics on file and iterate on anything that exports this file
636     * return true when all work is done and we can exit handling dts emit and semantic diagnostics
637     */
638    function handleDtsMayChangeOfFileAndExportsOfFile(
639        state: BuilderProgramState,
640        filePath: Path,
641        seenFileAndExportsOfFile: Set<string>,
642        cancellationToken: CancellationToken | undefined,
643        computeHash: BuilderState.ComputeHash,
644        getCanonicalFileName: GetCanonicalFileName,
645        host: BuilderProgramHost,
646    ): boolean | undefined {
647        if (!tryAddToSet(seenFileAndExportsOfFile, filePath)) return undefined;
648
649        if (handleDtsMayChangeOfGlobalScope(state, filePath, cancellationToken, computeHash, getCanonicalFileName, host)) return true;
650        handleDtsMayChangeOf(state, filePath, cancellationToken, computeHash, getCanonicalFileName, host);
651
652        // If exported modules has path, all files referencing file exported from are affected
653        state.exportedModulesMap!.getKeys(filePath)?.forEach(exportedFromPath =>
654            handleDtsMayChangeOfFileAndExportsOfFile(
655                state,
656                exportedFromPath,
657                seenFileAndExportsOfFile,
658                cancellationToken,
659                computeHash,
660                getCanonicalFileName,
661                host,
662            )
663        );
664
665        // Remove diagnostics of files that import this file (without going to exports of referencing files)
666        state.referencedMap!.getKeys(filePath)?.forEach(referencingFilePath =>
667            !seenFileAndExportsOfFile.has(referencingFilePath) && // Not already removed diagnostic file
668            handleDtsMayChangeOf( // Dont add to seen since this is not yet done with the export removal
669                state,
670                referencingFilePath,
671                cancellationToken,
672                computeHash,
673                getCanonicalFileName,
674                host,
675            )
676        );
677        return undefined;
678    }
679
680    /**
681     * This is called after completing operation on the next affected file.
682     * The operations here are postponed to ensure that cancellation during the iteration is handled correctly
683     */
684    function doneWithAffectedFile(
685        state: BuilderProgramState,
686        affected: SourceFile | Program,
687        emitKind?: BuilderFileEmit,
688        isPendingEmit?: boolean,
689        isBuildInfoEmit?: boolean
690    ) {
691        if (isBuildInfoEmit) {
692            state.buildInfoEmitPending = false;
693        }
694        else if (affected === state.program) {
695            state.changedFilesSet.clear();
696            state.programEmitComplete = true;
697        }
698        else {
699            state.seenAffectedFiles!.add((affected as SourceFile).resolvedPath);
700            // Change in changeSet/affectedFilesPendingEmit, buildInfo needs to be emitted
701            state.buildInfoEmitPending = true;
702            if (emitKind !== undefined) {
703                (state.seenEmittedFiles || (state.seenEmittedFiles = new Map())).set((affected as SourceFile).resolvedPath, emitKind);
704            }
705            if (isPendingEmit) {
706                state.affectedFilesPendingEmitIndex!++;
707            }
708            else {
709                state.affectedFilesIndex!++;
710            }
711        }
712    }
713
714    /**
715     * Returns the result with affected file
716     */
717    function toAffectedFileResult<T>(state: BuilderProgramState, result: T, affected: SourceFile | Program): AffectedFileResult<T> {
718        doneWithAffectedFile(state, affected);
719        return { result, affected };
720    }
721
722    /**
723     * Returns the result with affected file
724     */
725    function toAffectedFileEmitResult(
726        state: BuilderProgramState,
727        result: EmitResult,
728        affected: SourceFile | Program,
729        emitKind: BuilderFileEmit,
730        isPendingEmit?: boolean,
731        isBuildInfoEmit?: boolean
732    ): AffectedFileResult<EmitResult> {
733        doneWithAffectedFile(state, affected, emitKind, isPendingEmit, isBuildInfoEmit);
734        return { result, affected };
735    }
736
737    /**
738     * Gets semantic diagnostics for the file which are
739     * bindAndCheckDiagnostics (from cache) and program diagnostics
740     */
741    function getSemanticDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] {
742        return concatenate(
743            getBinderAndCheckerDiagnosticsOfFile(state, sourceFile, cancellationToken),
744            Debug.checkDefined(state.program).getProgramDiagnostics(sourceFile)
745        );
746    }
747
748    /**
749     * Gets the binder and checker diagnostics either from cache if present, or otherwise from program and caches it
750     * Note that it is assumed that when asked about binder and checker diagnostics, the file has been taken out of affected files/changed file set
751     */
752    function getBinderAndCheckerDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] {
753        const path = sourceFile.resolvedPath;
754        if (state.semanticDiagnosticsPerFile) {
755            const cachedDiagnostics = state.semanticDiagnosticsPerFile.get(path);
756            // Report the bind and check diagnostics from the cache if we already have those diagnostics present
757            if (cachedDiagnostics) {
758                return filterSemanticDiagnostics(cachedDiagnostics, state.compilerOptions);
759            }
760        }
761
762        // Diagnostics werent cached, get them from program, and cache the result
763        const diagnostics = Debug.checkDefined(state.program).getBindAndCheckDiagnostics(sourceFile, cancellationToken);
764        if (state.semanticDiagnosticsPerFile) {
765            state.semanticDiagnosticsPerFile.set(path, diagnostics);
766        }
767        return filterSemanticDiagnostics(diagnostics, state.compilerOptions);
768    }
769
770    export type ProgramBuildInfoFileId = number & { __programBuildInfoFileIdBrand: any };
771    export type ProgramBuildInfoFileIdListId = number & { __programBuildInfoFileIdListIdBrand: any };
772    export type ProgramBuildInfoDiagnostic = ProgramBuildInfoFileId | [fileId: ProgramBuildInfoFileId, diagnostics: readonly ReusableDiagnostic[]];
773    export type ProgramBuilderInfoFilePendingEmit = [fileId: ProgramBuildInfoFileId, emitKind: BuilderFileEmit];
774    export type ProgramBuildInfoReferencedMap = [fileId: ProgramBuildInfoFileId, fileIdListId: ProgramBuildInfoFileIdListId][];
775    export type ProgramBuildInfoBuilderStateFileInfo = Omit<BuilderState.FileInfo, "signature"> & {
776        /**
777         * Signature is
778         * - undefined if FileInfo.version === FileInfo.signature
779         * - false if FileInfo has signature as undefined (not calculated)
780         * - string actual signature
781         */
782        signature: string | false | undefined;
783    };
784    /**
785     * [fileId, signature] if different from file's signature
786     * fileId if file wasnt emitted
787     */
788    export type ProgramBuildInfoEmitSignature = ProgramBuildInfoFileId | [fileId: ProgramBuildInfoFileId, signature: string];
789    /**
790     * ProgramBuildInfoFileInfo is string if FileInfo.version === FileInfo.signature && !FileInfo.affectsGlobalScope otherwise encoded FileInfo
791     */
792    export type ProgramBuildInfoFileInfo = string | ProgramBuildInfoBuilderStateFileInfo;
793    export interface ProgramMultiFileEmitBuildInfo {
794        fileNames: readonly string[];
795        fileInfos: readonly ProgramBuildInfoFileInfo[];
796        options: CompilerOptions | undefined;
797        fileIdsList?: readonly (readonly ProgramBuildInfoFileId[])[];
798        referencedMap?: ProgramBuildInfoReferencedMap;
799        exportedModulesMap?: ProgramBuildInfoReferencedMap;
800        semanticDiagnosticsPerFile?: ProgramBuildInfoDiagnostic[];
801        affectedFilesPendingEmit?: ProgramBuilderInfoFilePendingEmit[];
802        changeFileSet?: readonly ProgramBuildInfoFileId[];
803        emitSignatures?: readonly ProgramBuildInfoEmitSignature[];
804        // Because this is only output file in the program, we dont need fileId to deduplicate name
805        latestChangedDtsFile?: string;
806    }
807
808    export interface ProgramBundleEmitBuildInfo {
809        fileNames: readonly string[];
810        fileInfos: readonly string[];
811        options: CompilerOptions | undefined;
812        outSignature?: string;
813        latestChangedDtsFile?: string;
814    }
815
816    export type ProgramBuildInfo = ProgramMultiFileEmitBuildInfo | ProgramBundleEmitBuildInfo;
817
818    export function isProgramBundleEmitBuildInfo(info: ProgramBuildInfo): info is ProgramBundleEmitBuildInfo {
819        return !!outFile(info.options || {});
820    }
821
822    /**
823     * Gets the program information to be emitted in buildInfo so that we can use it to create new program
824     */
825    function getProgramBuildInfo(state: BuilderProgramState, getCanonicalFileName: GetCanonicalFileName): ProgramBuildInfo | undefined {
826        const outFilePath = outFile(state.compilerOptions);
827        if (outFilePath && !state.compilerOptions.composite) return;
828        const currentDirectory = Debug.checkDefined(state.program).getCurrentDirectory();
829        const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(state.compilerOptions)!, currentDirectory));
830        // Convert the file name to Path here if we set the fileName instead to optimize multiple d.ts file emits and having to compute Canonical path
831        const latestChangedDtsFile = state.latestChangedDtsFile ? relativeToBuildInfoEnsuringAbsolutePath(state.latestChangedDtsFile) : undefined;
832        if (outFilePath) {
833            const fileNames: string[] = [];
834            const fileInfos: string[] = [];
835            state.program!.getRootFileNames().forEach(f => {
836                const sourceFile = state.program!.getSourceFile(f);
837                if (!sourceFile) return;
838                fileNames.push(relativeToBuildInfo(sourceFile.resolvedPath));
839                fileInfos.push(sourceFile.version);
840            });
841            const result: ProgramBundleEmitBuildInfo = {
842                fileNames,
843                fileInfos,
844                options: convertToProgramBuildInfoCompilerOptions(state.compilerOptions, "affectsBundleEmitBuildInfo"),
845                outSignature: state.outSignature,
846                latestChangedDtsFile,
847            };
848            return result;
849        }
850
851        const fileNames: string[] = [];
852        const fileNameToFileId = new Map<string, ProgramBuildInfoFileId>();
853        let fileIdsList: (readonly ProgramBuildInfoFileId[])[] | undefined;
854        let fileNamesToFileIdListId: ESMap<string, ProgramBuildInfoFileIdListId> | undefined;
855        let emitSignatures: ProgramBuildInfoEmitSignature[] | undefined;
856        const fileInfos = arrayFrom(state.fileInfos.entries(), ([key, value]): ProgramBuildInfoFileInfo => {
857            // Ensure fileId
858            const fileId = toFileId(key);
859            Debug.assert(fileNames[fileId - 1] === relativeToBuildInfo(key));
860            const oldSignature = state.oldSignatures?.get(key);
861            const actualSignature = oldSignature !== undefined ? oldSignature || undefined : value.signature;
862            if (state.compilerOptions.composite) {
863                const file = state.program!.getSourceFileByPath(key)!;
864                if (!isJsonSourceFile(file) && sourceFileMayBeEmitted(file, state.program!)) {
865                    const emitSignature = state.emitSignatures?.get(key);
866                    if (emitSignature !== actualSignature) {
867                        (emitSignatures ||= []).push(emitSignature === undefined ? fileId : [fileId, emitSignature]);
868                    }
869                }
870            }
871            return value.version === actualSignature ?
872                value.affectsGlobalScope || value.impliedFormat ?
873                    // If file version is same as signature, dont serialize signature
874                    { version: value.version, signature: undefined, affectsGlobalScope: value.affectsGlobalScope, impliedFormat: value.impliedFormat } :
875                    // If file info only contains version and signature and both are same we can just write string
876                    value.version :
877                actualSignature !== undefined ? // If signature is not same as version, encode signature in the fileInfo
878                    oldSignature === undefined ?
879                        // If we havent computed signature, use fileInfo as is
880                        value :
881                        // Serialize fileInfo with new updated signature
882                        { version: value.version, signature: actualSignature, affectsGlobalScope: value.affectsGlobalScope, impliedFormat: value.impliedFormat } :
883                    // Signature of the FileInfo is undefined, serialize it as false
884                    { version: value.version, signature: false, affectsGlobalScope: value.affectsGlobalScope, impliedFormat: value.impliedFormat };
885        });
886
887        let referencedMap: ProgramBuildInfoReferencedMap | undefined;
888        if (state.referencedMap) {
889            referencedMap = arrayFrom(state.referencedMap.keys()).sort(compareStringsCaseSensitive).map(key => [
890                toFileId(key),
891                toFileIdListId(state.referencedMap!.getValues(key)!)
892            ]);
893        }
894
895        let exportedModulesMap: ProgramBuildInfoReferencedMap | undefined;
896        if (state.exportedModulesMap) {
897            exportedModulesMap = mapDefined(arrayFrom(state.exportedModulesMap.keys()).sort(compareStringsCaseSensitive), key => {
898                const oldValue = state.oldExportedModulesMap?.get(key);
899                // Not in temporary cache, use existing value
900                if (oldValue === undefined) return [toFileId(key), toFileIdListId(state.exportedModulesMap!.getValues(key)!)];
901                if (oldValue) return [toFileId(key), toFileIdListId(oldValue)];
902                return undefined;
903            });
904        }
905
906        let semanticDiagnosticsPerFile: ProgramBuildInfoDiagnostic[] | undefined;
907        if (state.semanticDiagnosticsPerFile) {
908            for (const key of arrayFrom(state.semanticDiagnosticsPerFile.keys()).sort(compareStringsCaseSensitive)) {
909                const value = state.semanticDiagnosticsPerFile.get(key)!;
910                (semanticDiagnosticsPerFile ||= []).push(
911                    value.length ?
912                        [
913                            toFileId(key),
914                            convertToReusableDiagnostics(value, relativeToBuildInfo)
915                        ] :
916                        toFileId(key)
917                );
918            }
919        }
920
921        let affectedFilesPendingEmit: ProgramBuilderInfoFilePendingEmit[] | undefined;
922        if (state.affectedFilesPendingEmit) {
923            const seenFiles = new Set<Path>();
924            for (const path of state.affectedFilesPendingEmit.slice(state.affectedFilesPendingEmitIndex).sort(compareStringsCaseSensitive)) {
925                if (tryAddToSet(seenFiles, path)) {
926                    (affectedFilesPendingEmit ||= []).push([toFileId(path), state.affectedFilesPendingEmitKind!.get(path)!]);
927                }
928            }
929        }
930
931        let changeFileSet: ProgramBuildInfoFileId[] | undefined;
932        if (state.changedFilesSet.size) {
933            for (const path of arrayFrom(state.changedFilesSet.keys()).sort(compareStringsCaseSensitive)) {
934                (changeFileSet ||= []).push(toFileId(path));
935            }
936        }
937
938        const result: ProgramMultiFileEmitBuildInfo = {
939            fileNames,
940            fileInfos,
941            options: convertToProgramBuildInfoCompilerOptions(state.compilerOptions, "affectsMultiFileEmitBuildInfo"),
942            fileIdsList,
943            referencedMap,
944            exportedModulesMap,
945            semanticDiagnosticsPerFile,
946            affectedFilesPendingEmit,
947            changeFileSet,
948            emitSignatures,
949            latestChangedDtsFile,
950        };
951        return result;
952
953        function relativeToBuildInfoEnsuringAbsolutePath(path: string) {
954            return relativeToBuildInfo(getNormalizedAbsolutePath(path, currentDirectory));
955        }
956
957        function relativeToBuildInfo(path: string) {
958            return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory, path, getCanonicalFileName));
959        }
960
961        function toFileId(path: Path): ProgramBuildInfoFileId {
962            let fileId = fileNameToFileId.get(path);
963            if (fileId === undefined) {
964                fileNames.push(relativeToBuildInfo(path));
965                fileNameToFileId.set(path, fileId = fileNames.length as ProgramBuildInfoFileId);
966            }
967            return fileId;
968        }
969
970        function toFileIdListId(set: ReadonlySet<Path>): ProgramBuildInfoFileIdListId {
971            const fileIds = arrayFrom(set.keys(), toFileId).sort(compareValues);
972            const key = fileIds.join();
973            let fileIdListId = fileNamesToFileIdListId?.get(key);
974            if (fileIdListId === undefined) {
975                (fileIdsList ||= []).push(fileIds);
976                (fileNamesToFileIdListId ||= new Map()).set(key, fileIdListId = fileIdsList.length as ProgramBuildInfoFileIdListId);
977            }
978            return fileIdListId;
979        }
980
981        /**
982         * @param optionKey key of CommandLineOption to use to determine if the option should be serialized in tsbuildinfo
983         */
984        function convertToProgramBuildInfoCompilerOptions(options: CompilerOptions, optionKey: "affectsBundleEmitBuildInfo" | "affectsMultiFileEmitBuildInfo") {
985            let result: CompilerOptions | undefined;
986            const { optionsNameMap } = getOptionsNameMap();
987            for (const name of getOwnKeys(options).sort(compareStringsCaseSensitive)) {
988                const optionInfo = optionsNameMap.get(name.toLowerCase());
989                if (optionInfo?.[optionKey]) {
990                    (result ||= {})[name] = convertToReusableCompilerOptionValue(
991                        optionInfo,
992                        options[name] as CompilerOptionsValue,
993                        relativeToBuildInfoEnsuringAbsolutePath
994                    );
995                }
996            }
997            return result;
998        }
999    }
1000
1001    function convertToReusableCompilerOptionValue(option: CommandLineOption | undefined, value: CompilerOptionsValue, relativeToBuildInfo: (path: string) => string) {
1002        if (option) {
1003            if (option.type === "list") {
1004                const values = value as readonly (string | number)[];
1005                if (option.element.isFilePath && values.length) {
1006                    return values.map(relativeToBuildInfo);
1007                }
1008            }
1009            else if (option.isFilePath) {
1010                return relativeToBuildInfo(value as string);
1011            }
1012        }
1013        return value;
1014    }
1015
1016    function convertToReusableDiagnostics(diagnostics: readonly Diagnostic[], relativeToBuildInfo: (path: string) => string): readonly ReusableDiagnostic[] {
1017        Debug.assert(!!diagnostics.length);
1018        return diagnostics.map(diagnostic => {
1019            const result: ReusableDiagnostic = convertToReusableDiagnosticRelatedInformation(diagnostic, relativeToBuildInfo);
1020            result.reportsUnnecessary = diagnostic.reportsUnnecessary;
1021            result.reportDeprecated = diagnostic.reportsDeprecated;
1022            result.source = diagnostic.source;
1023            result.skippedOn = diagnostic.skippedOn;
1024            const { relatedInformation } = diagnostic;
1025            result.relatedInformation = relatedInformation ?
1026                relatedInformation.length ?
1027                    relatedInformation.map(r => convertToReusableDiagnosticRelatedInformation(r, relativeToBuildInfo)) :
1028                    [] :
1029                undefined;
1030            return result;
1031        });
1032    }
1033
1034    function convertToReusableDiagnosticRelatedInformation(diagnostic: DiagnosticRelatedInformation, relativeToBuildInfo: (path: string) => string): ReusableDiagnosticRelatedInformation {
1035        const { file } = diagnostic;
1036        return {
1037            ...diagnostic,
1038            file: file ? relativeToBuildInfo(file.resolvedPath) : undefined
1039        };
1040    }
1041
1042    export enum BuilderProgramKind {
1043        SemanticDiagnosticsBuilderProgram,
1044        EmitAndSemanticDiagnosticsBuilderProgram
1045    }
1046
1047    export interface BuilderCreationParameters {
1048        newProgram: Program;
1049        host: BuilderProgramHost;
1050        oldProgram: BuilderProgram | undefined;
1051        configFileParsingDiagnostics: readonly Diagnostic[];
1052    }
1053
1054    export function getBuilderCreationParameters(newProgramOrRootNames: Program | readonly string[] | undefined, hostOrOptions: BuilderProgramHost | CompilerOptions | undefined, oldProgramOrHost?: BuilderProgram | CompilerHost, configFileParsingDiagnosticsOrOldProgram?: readonly Diagnostic[] | BuilderProgram, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[]): BuilderCreationParameters {
1055        let host: BuilderProgramHost;
1056        let newProgram: Program;
1057        let oldProgram: BuilderProgram;
1058        if (newProgramOrRootNames === undefined) {
1059            Debug.assert(hostOrOptions === undefined);
1060            host = oldProgramOrHost as CompilerHost;
1061            oldProgram = configFileParsingDiagnosticsOrOldProgram as BuilderProgram;
1062            Debug.assert(!!oldProgram);
1063            newProgram = oldProgram.getProgram();
1064        }
1065        else if (isArray(newProgramOrRootNames)) {
1066            oldProgram = configFileParsingDiagnosticsOrOldProgram as BuilderProgram;
1067            newProgram = createProgram({
1068                rootNames: newProgramOrRootNames,
1069                options: hostOrOptions as CompilerOptions,
1070                host: oldProgramOrHost as CompilerHost,
1071                oldProgram: oldProgram && oldProgram.getProgramOrUndefined(),
1072                configFileParsingDiagnostics,
1073                projectReferences
1074            });
1075            host = oldProgramOrHost as CompilerHost;
1076        }
1077        else {
1078            newProgram = newProgramOrRootNames;
1079            host = hostOrOptions as BuilderProgramHost;
1080            oldProgram = oldProgramOrHost as BuilderProgram;
1081            configFileParsingDiagnostics = configFileParsingDiagnosticsOrOldProgram as readonly Diagnostic[];
1082        }
1083        return { host, newProgram, oldProgram, configFileParsingDiagnostics: configFileParsingDiagnostics || emptyArray };
1084    }
1085
1086    function getTextHandlingSourceMapForSignature(text: string, data: WriteFileCallbackData | undefined) {
1087        return data?.sourceMapUrlPos !== undefined ? text.substring(0, data.sourceMapUrlPos) : text;
1088    }
1089
1090    export function computeSignatureWithDiagnostics(
1091        sourceFile: SourceFile,
1092        text: string,
1093        computeHash: BuilderState.ComputeHash | undefined,
1094        getCanonicalFileName: GetCanonicalFileName,
1095        data: WriteFileCallbackData | undefined
1096    ) {
1097        text = getTextHandlingSourceMapForSignature(text, data);
1098        let sourceFileDirectory: string | undefined;
1099        if (data?.diagnostics?.length) {
1100            text += data.diagnostics.map(diagnostic =>
1101                `${locationInfo(diagnostic)}${DiagnosticCategory[diagnostic.category]}${diagnostic.code}: ${flattenDiagnosticMessageText(diagnostic.messageText)}`
1102            ).join("\n");
1103        }
1104        return (computeHash ?? generateDjb2Hash)(text);
1105
1106        function flattenDiagnosticMessageText(diagnostic: string | DiagnosticMessageChain | undefined): string {
1107            return isString(diagnostic) ?
1108                diagnostic :
1109                diagnostic === undefined ?
1110                    "" :
1111                    !diagnostic.next ?
1112                        diagnostic.messageText :
1113                        diagnostic.messageText + diagnostic.next.map(flattenDiagnosticMessageText).join("\n");
1114        }
1115
1116        function locationInfo(diagnostic: DiagnosticWithLocation) {
1117            if (diagnostic.file.resolvedPath === sourceFile.resolvedPath) return `(${diagnostic.start},${diagnostic.length})`;
1118            if (sourceFileDirectory === undefined) sourceFileDirectory = getDirectoryPath(sourceFile.resolvedPath);
1119            return `${ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceFileDirectory, diagnostic.file.resolvedPath, getCanonicalFileName))}(${diagnostic.start},${diagnostic.length})`;
1120        }
1121    }
1122
1123    export function computeSignature(text: string, computeHash: BuilderState.ComputeHash | undefined, data?: WriteFileCallbackData) {
1124        return (computeHash ?? generateDjb2Hash)(getTextHandlingSourceMapForSignature(text, data));
1125    }
1126
1127    export function createBuilderProgram(kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): SemanticDiagnosticsBuilderProgram;
1128    export function createBuilderProgram(kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): EmitAndSemanticDiagnosticsBuilderProgram;
1129    export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, host, oldProgram, configFileParsingDiagnostics }: BuilderCreationParameters) {
1130        // Return same program if underlying program doesnt change
1131        let oldState = oldProgram && oldProgram.getState();
1132        if (oldState && newProgram === oldState.program && configFileParsingDiagnostics === newProgram.getConfigFileParsingDiagnostics()) {
1133            newProgram = undefined!; // TODO: GH#18217
1134            oldState = undefined;
1135            return oldProgram;
1136        }
1137
1138        /**
1139         * Create the canonical file name for identity
1140         */
1141        const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
1142        /**
1143         * Computing hash to for signature verification
1144         */
1145        const computeHash = maybeBind(host, host.createHash);
1146        const state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState, host.disableUseFileVersionAsSignature);
1147        newProgram.getProgramBuildInfo = () => getProgramBuildInfo(state, getCanonicalFileName);
1148
1149        // To ensure that we arent storing any references to old program or new program without state
1150        newProgram = undefined!; // TODO: GH#18217
1151        oldProgram = undefined;
1152        oldState = undefined;
1153
1154        const getState = () => state;
1155        const builderProgram = createRedirectedBuilderProgram(getState, configFileParsingDiagnostics);
1156        builderProgram.getState = getState;
1157        builderProgram.saveEmitState = () => backupBuilderProgramEmitState(state);
1158        builderProgram.restoreEmitState = (saved) => restoreBuilderProgramEmitState(state, saved);
1159        builderProgram.hasChangedEmitSignature = () => !!state.hasChangedEmitSignature;
1160        builderProgram.getAllDependencies = sourceFile => BuilderState.getAllDependencies(state, Debug.checkDefined(state.program), sourceFile);
1161        builderProgram.getSemanticDiagnostics = getSemanticDiagnostics;
1162        builderProgram.emit = emit;
1163        builderProgram.releaseProgram = () => releaseCache(state);
1164
1165        if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) {
1166            (builderProgram as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile;
1167        }
1168        else if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) {
1169            (builderProgram as EmitAndSemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile;
1170            (builderProgram as EmitAndSemanticDiagnosticsBuilderProgram).emitNextAffectedFile = emitNextAffectedFile;
1171            builderProgram.emitBuildInfo = emitBuildInfo;
1172        }
1173        else {
1174            notImplemented();
1175        }
1176
1177        return builderProgram;
1178
1179        function emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult {
1180            if (state.buildInfoEmitPending) {
1181                const result = Debug.checkDefined(state.program).emitBuildInfo(writeFile || maybeBind(host, host.writeFile), cancellationToken);
1182                state.buildInfoEmitPending = false;
1183                return result;
1184            }
1185            return emitSkippedWithNoDiagnostics;
1186        }
1187
1188        /**
1189         * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete
1190         * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host
1191         * in that order would be used to write the files
1192         */
1193        function emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult<EmitResult> {
1194            let affected = getNextAffectedFile(state, cancellationToken, computeHash, getCanonicalFileName, host);
1195            let emitKind = BuilderFileEmit.Full;
1196            let isPendingEmitFile = false;
1197            if (!affected) {
1198                if (!outFile(state.compilerOptions)) {
1199                    const pendingAffectedFile = getNextAffectedFilePendingEmit(state);
1200                    if (!pendingAffectedFile) {
1201                        if (!state.buildInfoEmitPending) {
1202                            return undefined;
1203                        }
1204
1205                        const affected = Debug.checkDefined(state.program);
1206                        return toAffectedFileEmitResult(
1207                            state,
1208                            // When whole program is affected, do emit only once (eg when --out or --outFile is specified)
1209                            // Otherwise just affected file
1210                            affected.emitBuildInfo(writeFile || maybeBind(host, host.writeFile), cancellationToken),
1211                            affected,
1212                            /*emitKind*/ BuilderFileEmit.Full,
1213                            /*isPendingEmitFile*/ false,
1214                            /*isBuildInfoEmit*/ true
1215                        );
1216                    }
1217                    ({ affectedFile: affected, emitKind } = pendingAffectedFile);
1218                    isPendingEmitFile = true;
1219                }
1220                else {
1221                    const program = Debug.checkDefined(state.program);
1222                    if (state.programEmitComplete) return undefined;
1223                    affected = program;
1224                }
1225            }
1226
1227            return toAffectedFileEmitResult(
1228                state,
1229                // When whole program is affected, do emit only once (eg when --out or --outFile is specified)
1230                // Otherwise just affected file
1231                Debug.checkDefined(state.program).emit(
1232                    affected === state.program ? undefined : affected as SourceFile,
1233                    getEmitDeclarations(state.compilerOptions) ?
1234                        getWriteFileCallback(writeFile, customTransformers) :
1235                        writeFile || maybeBind(host, host.writeFile),
1236                    cancellationToken,
1237                    emitOnlyDtsFiles || emitKind === BuilderFileEmit.DtsOnly,
1238                    customTransformers
1239                ),
1240                affected,
1241                emitKind,
1242                isPendingEmitFile,
1243            );
1244        }
1245
1246        function getWriteFileCallback(writeFile: WriteFileCallback | undefined, customTransformers: CustomTransformers | undefined): WriteFileCallback {
1247            return (fileName, text, writeByteOrderMark, onError, sourceFiles, data) => {
1248                if (isDeclarationFileName(fileName)) {
1249                    if (!outFile(state.compilerOptions)) {
1250                        Debug.assert(sourceFiles?.length === 1);
1251                        let emitSignature;
1252                        if (!customTransformers) {
1253                            const file = sourceFiles[0];
1254                            const info = state.fileInfos.get(file.resolvedPath)!;
1255                            if (info.signature === file.version) {
1256                                const signature = computeSignatureWithDiagnostics(
1257                                    file,
1258                                    text,
1259                                    computeHash,
1260                                    getCanonicalFileName,
1261                                    data,
1262                                );
1263                                // With d.ts diagnostics they are also part of the signature so emitSignature will be different from it since its just hash of d.ts
1264                                if (!data?.diagnostics?.length) emitSignature = signature;
1265                                if (signature !== file.version) { // Update it
1266                                    if (host.storeFilesChangingSignatureDuringEmit) (state.filesChangingSignature ??= new Set()).add(file.resolvedPath);
1267                                    if (state.exportedModulesMap) BuilderState.updateExportedModules(state, file, file.exportedModulesFromDeclarationEmit);
1268                                    if (state.affectedFiles) {
1269                                        // Keep old signature so we know what to undo if cancellation happens
1270                                        const existing = state.oldSignatures?.get(file.resolvedPath);
1271                                        if (existing === undefined) (state.oldSignatures ??= new Map()).set(file.resolvedPath, info.signature || false);
1272                                        info.signature = signature;
1273                                    }
1274                                    else {
1275                                        // These are directly commited
1276                                        info.signature = signature;
1277                                        state.oldExportedModulesMap?.clear();
1278                                    }
1279                                }
1280                            }
1281                        }
1282
1283                        // Store d.ts emit hash so later can be compared to check if d.ts has changed.
1284                        // Currently we do this only for composite projects since these are the only projects that can be referenced by other projects
1285                        // and would need their d.ts change time in --build mode
1286                        if (state.compilerOptions.composite) {
1287                            const filePath = sourceFiles[0].resolvedPath;
1288                            const oldSignature = state.emitSignatures?.get(filePath);
1289                            emitSignature ??= computeSignature(text, computeHash, data);
1290                            // Dont write dts files if they didn't change
1291                            if (emitSignature === oldSignature) return;
1292                            (state.emitSignatures ??= new Map()).set(filePath, emitSignature);
1293                            state.hasChangedEmitSignature = true;
1294                            state.latestChangedDtsFile = fileName;
1295                        }
1296                    }
1297                    else if (state.compilerOptions.composite) {
1298                        const newSignature = computeSignature(text, computeHash, data);
1299                        // Dont write dts files if they didn't change
1300                        if (newSignature === state.outSignature) return;
1301                        state.outSignature = newSignature;
1302                        state.hasChangedEmitSignature = true;
1303                        state.latestChangedDtsFile = fileName;
1304                    }
1305                }
1306                if (writeFile) writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data);
1307                else if (host.writeFile) host.writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data);
1308                else state.program!.writeFile(fileName, text, writeByteOrderMark, onError, sourceFiles, data);
1309            };
1310        }
1311
1312        /**
1313         * Emits the JavaScript and declaration files.
1314         * When targetSource file is specified, emits the files corresponding to that source file,
1315         * otherwise for the whole program.
1316         * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified,
1317         * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified,
1318         * it will only emit all the affected files instead of whole program
1319         *
1320         * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host
1321         * in that order would be used to write the files
1322         */
1323        function emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult {
1324            if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) {
1325                assertSourceFileOkWithoutNextAffectedCall(state, targetSourceFile);
1326            }
1327            const result = handleNoEmitOptions(builderProgram, targetSourceFile, writeFile, cancellationToken);
1328            if (result) return result;
1329
1330            // Emit only affected files if using builder for emit
1331            if (!targetSourceFile) {
1332                if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) {
1333                    // Emit and report any errors we ran into.
1334                    let sourceMaps: SourceMapEmitResult[] = [];
1335                    let emitSkipped = false;
1336                    let diagnostics: Diagnostic[] | undefined;
1337                    let emittedFiles: string[] = [];
1338
1339                    let affectedEmitResult: AffectedFileResult<EmitResult>;
1340                    while (affectedEmitResult = emitNextAffectedFile(writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers)) {
1341                        emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped;
1342                        diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics);
1343                        emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles);
1344                        sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps);
1345                    }
1346                    return {
1347                        emitSkipped,
1348                        diagnostics: diagnostics || emptyArray,
1349                        emittedFiles,
1350                        sourceMaps
1351                    };
1352                }
1353                // In non Emit builder, clear affected files pending emit
1354                else if (state.affectedFilesPendingEmitKind?.size) {
1355                    Debug.assert(kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram);
1356                    // State can clear affected files pending emit if
1357                    if (!emitOnlyDtsFiles // If we are doing complete emit, affected files pending emit can be cleared
1358                        // If every file pending emit is pending on only dts emit
1359                        || every(state.affectedFilesPendingEmit, (path, index) =>
1360                            index < state.affectedFilesPendingEmitIndex! ||
1361                            state.affectedFilesPendingEmitKind!.get(path) === BuilderFileEmit.DtsOnly)) {
1362                        clearAffectedFilesPendingEmit(state);
1363                    }
1364                }
1365            }
1366            return Debug.checkDefined(state.program).emit(
1367                targetSourceFile,
1368                getEmitDeclarations(state.compilerOptions) ?
1369                    getWriteFileCallback(writeFile, customTransformers) :
1370                    writeFile || maybeBind(host, host.writeFile),
1371                cancellationToken,
1372                emitOnlyDtsFiles,
1373                customTransformers
1374            );
1375        }
1376
1377        /**
1378         * Return the semantic diagnostics for the next affected file or undefined if iteration is complete
1379         * If provided ignoreSourceFile would be called before getting the diagnostics and would ignore the sourceFile if the returned value was true
1380         */
1381        function getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult<readonly Diagnostic[]> {
1382            while (true) {
1383                const affected = getNextAffectedFile(state, cancellationToken, computeHash, getCanonicalFileName, host);
1384                if (!affected) {
1385                    // Done
1386                    return undefined;
1387                }
1388                else if (affected === state.program) {
1389                    // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified)
1390                    return toAffectedFileResult(
1391                        state,
1392                        state.program.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken),
1393                        affected
1394                    );
1395                }
1396
1397                // Add file to affected file pending emit to handle for later emit time
1398                // Apart for emit builder do this for tsbuildinfo, do this for non emit builder when noEmit is set as tsbuildinfo is written and reused between emitters
1399                if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram || state.compilerOptions.noEmit || state.compilerOptions.noEmitOnError) {
1400                    addToAffectedFilesPendingEmit(state, (affected as SourceFile).resolvedPath, BuilderFileEmit.Full);
1401                }
1402
1403                // Get diagnostics for the affected file if its not ignored
1404                if (ignoreSourceFile && ignoreSourceFile(affected as SourceFile)) {
1405                    // Get next affected file
1406                    doneWithAffectedFile(state, affected);
1407                    continue;
1408                }
1409
1410                return toAffectedFileResult(
1411                    state,
1412                    getSemanticDiagnosticsOfFile(state, affected as SourceFile, cancellationToken),
1413                    affected
1414                );
1415            }
1416        }
1417
1418        /**
1419         * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program
1420         * The semantic diagnostics are cached and managed here
1421         * Note that it is assumed that when asked about semantic diagnostics through this API,
1422         * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics
1423         * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided,
1424         * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics
1425         */
1426        function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] {
1427            assertSourceFileOkWithoutNextAffectedCall(state, sourceFile);
1428            const compilerOptions = Debug.checkDefined(state.program).getCompilerOptions();
1429            if (outFile(compilerOptions)) {
1430                Debug.assert(!state.semanticDiagnosticsPerFile);
1431                // We dont need to cache the diagnostics just return them from program
1432                return Debug.checkDefined(state.program).getSemanticDiagnostics(sourceFile, cancellationToken);
1433            }
1434
1435            if (sourceFile) {
1436                return getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken);
1437            }
1438
1439            // When semantic builder asks for diagnostics of the whole program,
1440            // ensure that all the affected files are handled
1441            // eslint-disable-next-line no-empty
1442            while (getSemanticDiagnosticsOfNextAffectedFile(cancellationToken)) {
1443            }
1444
1445            let diagnostics: Diagnostic[] | undefined;
1446            for (const sourceFile of Debug.checkDefined(state.program).getSourceFiles()) {
1447                diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken));
1448            }
1449            return diagnostics || emptyArray;
1450        }
1451    }
1452
1453    function addToAffectedFilesPendingEmit(state: BuilderProgramState, affectedFilePendingEmit: Path, kind: BuilderFileEmit) {
1454        if (!state.affectedFilesPendingEmit) state.affectedFilesPendingEmit = [];
1455        if (!state.affectedFilesPendingEmitKind) state.affectedFilesPendingEmitKind = new Map();
1456
1457        const existingKind = state.affectedFilesPendingEmitKind.get(affectedFilePendingEmit);
1458        state.affectedFilesPendingEmit.push(affectedFilePendingEmit);
1459        state.affectedFilesPendingEmitKind.set(affectedFilePendingEmit, existingKind || kind);
1460
1461        // affectedFilesPendingEmitIndex === undefined
1462        // - means the emit state.affectedFilesPendingEmit was undefined before adding current affected files
1463        //   so start from 0 as array would be affectedFilesPendingEmit
1464        // else, continue to iterate from existing index, the current set is appended to existing files
1465        if (state.affectedFilesPendingEmitIndex === undefined) {
1466            state.affectedFilesPendingEmitIndex = 0;
1467        }
1468    }
1469
1470    export function toBuilderStateFileInfo(fileInfo: ProgramBuildInfoFileInfo): BuilderState.FileInfo {
1471        return isString(fileInfo) ?
1472            { version: fileInfo, signature: fileInfo, affectsGlobalScope: undefined, impliedFormat: undefined } :
1473            isString(fileInfo.signature) ?
1474                fileInfo as BuilderState.FileInfo :
1475                { version: fileInfo.version, signature: fileInfo.signature === false ? undefined : fileInfo.version, affectsGlobalScope: fileInfo.affectsGlobalScope, impliedFormat: fileInfo.impliedFormat };
1476    }
1477
1478    export function createBuilderProgramUsingProgramBuildInfo(program: ProgramBuildInfo, buildInfoPath: string, host: ReadBuildProgramHost): EmitAndSemanticDiagnosticsBuilderProgram {
1479        const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory()));
1480        const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
1481
1482        let state: ReusableBuilderProgramState;
1483        let filePaths: Path[] | undefined;
1484        let filePathsSetList: Set<Path>[] | undefined;
1485        const latestChangedDtsFile = program.latestChangedDtsFile ? toAbsolutePath(program.latestChangedDtsFile) : undefined;
1486        if (isProgramBundleEmitBuildInfo(program)) {
1487            state = {
1488                fileInfos: new Map(),
1489                compilerOptions: program.options ? convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath) : {},
1490                latestChangedDtsFile,
1491                outSignature: program.outSignature,
1492            };
1493        }
1494        else {
1495            filePaths = program.fileNames?.map(toPath);
1496            filePathsSetList = program.fileIdsList?.map(fileIds => new Set(fileIds.map(toFilePath)));
1497            const fileInfos = new Map<Path, BuilderState.FileInfo>();
1498            const emitSignatures = program.options?.composite && !outFile(program.options) ? new Map<Path, string>() : undefined;
1499            program.fileInfos.forEach((fileInfo, index) => {
1500                const path = toFilePath(index + 1 as ProgramBuildInfoFileId);
1501                const stateFileInfo = toBuilderStateFileInfo(fileInfo);
1502                fileInfos.set(path, stateFileInfo);
1503                if (emitSignatures && stateFileInfo.signature) emitSignatures.set(path, stateFileInfo.signature);
1504            });
1505            program.emitSignatures?.forEach(value => {
1506                if (isNumber(value)) emitSignatures!.delete(toFilePath(value));
1507                else emitSignatures!.set(toFilePath(value[0]), value[1]);
1508            });
1509            state = {
1510                fileInfos,
1511                compilerOptions: program.options ? convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath) : {},
1512                referencedMap: toManyToManyPathMap(program.referencedMap),
1513                exportedModulesMap: toManyToManyPathMap(program.exportedModulesMap),
1514                semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toFilePath(isNumber(value) ? value : value[0]), value => isNumber(value) ? emptyArray : value[1]),
1515                hasReusableDiagnostic: true,
1516                affectedFilesPendingEmit: map(program.affectedFilesPendingEmit, value => toFilePath(value[0])),
1517                affectedFilesPendingEmitKind: program.affectedFilesPendingEmit && arrayToMap(program.affectedFilesPendingEmit, value => toFilePath(value[0]), value => value[1]),
1518                affectedFilesPendingEmitIndex: program.affectedFilesPendingEmit && 0,
1519                changedFilesSet: new Set(map(program.changeFileSet, toFilePath)),
1520                latestChangedDtsFile,
1521                emitSignatures: emitSignatures?.size ? emitSignatures : undefined,
1522            };
1523        }
1524
1525        return {
1526            getState: () => state,
1527            saveEmitState: noop as BuilderProgram["saveEmitState"],
1528            restoreEmitState: noop,
1529            getProgram: notImplemented,
1530            getProgramOrUndefined: host.getLastCompiledProgram ? host.getLastCompiledProgram : returnUndefined,
1531            releaseProgram: noop,
1532            getCompilerOptions: () => state.compilerOptions,
1533            getSourceFile: notImplemented,
1534            getSourceFiles: notImplemented,
1535            getOptionsDiagnostics: notImplemented,
1536            getGlobalDiagnostics: notImplemented,
1537            getConfigFileParsingDiagnostics: notImplemented,
1538            getSyntacticDiagnostics: notImplemented,
1539            getDeclarationDiagnostics: notImplemented,
1540            getSemanticDiagnostics: notImplemented,
1541            emit: notImplemented,
1542            getAllDependencies: notImplemented,
1543            getCurrentDirectory: notImplemented,
1544            emitNextAffectedFile: notImplemented,
1545            getSemanticDiagnosticsOfNextAffectedFile: notImplemented,
1546            emitBuildInfo: notImplemented,
1547            close: noop,
1548            hasChangedEmitSignature: returnFalse,
1549        };
1550
1551        function toPath(path: string) {
1552            return ts.toPath(path, buildInfoDirectory, getCanonicalFileName);
1553        }
1554
1555        function toAbsolutePath(path: string) {
1556            return getNormalizedAbsolutePath(path, buildInfoDirectory);
1557        }
1558
1559        function toFilePath(fileId: ProgramBuildInfoFileId) {
1560            return filePaths![fileId - 1];
1561        }
1562
1563        function toFilePathsSet(fileIdsListId: ProgramBuildInfoFileIdListId) {
1564            return filePathsSetList![fileIdsListId - 1];
1565        }
1566
1567        function toManyToManyPathMap(referenceMap: ProgramBuildInfoReferencedMap | undefined): BuilderState.ManyToManyPathMap | undefined {
1568            if (!referenceMap) {
1569                return undefined;
1570            }
1571
1572            const map = BuilderState.createManyToManyPathMap();
1573            referenceMap.forEach(([fileId, fileIdListId]) =>
1574                map.set(toFilePath(fileId), toFilePathsSet(fileIdListId))
1575            );
1576            return map;
1577        }
1578    }
1579
1580    export function getBuildInfoFileVersionMap(
1581        program: ProgramBuildInfo,
1582        buildInfoPath: string,
1583        host: Pick<ReadBuildProgramHost, "useCaseSensitiveFileNames" | "getCurrentDirectory">
1584    ): ESMap<Path, string> {
1585        const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory()));
1586        const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
1587        const fileInfos = new Map<Path, string>();
1588        program.fileInfos.forEach((fileInfo, index) => {
1589            const path = toPath(program.fileNames[index], buildInfoDirectory, getCanonicalFileName);
1590            const version = isString(fileInfo) ? fileInfo : (fileInfo as ProgramBuildInfoBuilderStateFileInfo).version; // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
1591            fileInfos.set(path, version);
1592        });
1593        return fileInfos;
1594    }
1595
1596    export function createRedirectedBuilderProgram(getState: () => { program?: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: readonly Diagnostic[]): BuilderProgram {
1597        return {
1598            getState: notImplemented,
1599            saveEmitState: noop as BuilderProgram["saveEmitState"],
1600            restoreEmitState: noop,
1601            getProgram,
1602            getProgramOrUndefined: () => getState().program,
1603            releaseProgram: () => getState().program = undefined,
1604            getCompilerOptions: () => getState().compilerOptions,
1605            getSourceFile: fileName => getProgram().getSourceFile(fileName),
1606            getSourceFiles: () => getProgram().getSourceFiles(),
1607            getOptionsDiagnostics: cancellationToken => getProgram().getOptionsDiagnostics(cancellationToken),
1608            getGlobalDiagnostics: cancellationToken => getProgram().getGlobalDiagnostics(cancellationToken),
1609            getConfigFileParsingDiagnostics: () => configFileParsingDiagnostics,
1610            getSyntacticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSyntacticDiagnostics(sourceFile, cancellationToken),
1611            getDeclarationDiagnostics: (sourceFile, cancellationToken) => getProgram().getDeclarationDiagnostics(sourceFile, cancellationToken),
1612            getSemanticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSemanticDiagnostics(sourceFile, cancellationToken),
1613            emit: (sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers) => getProgram().emit(sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers),
1614            emitBuildInfo: (writeFile, cancellationToken) => getProgram().emitBuildInfo(writeFile, cancellationToken),
1615            getAllDependencies: notImplemented,
1616            getCurrentDirectory: () => getProgram().getCurrentDirectory(),
1617            close: noop,
1618        };
1619
1620        function getProgram() {
1621            return Debug.checkDefined(getState().program);
1622        }
1623    }
1624}
1625