• 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 ReusableBuilderState {
24        /**
25         * Cache of bind and check diagnostics for files with their Path being the key
26         */
27        semanticDiagnosticsPerFile?: ReadonlyESMap<Path, readonly ReusableDiagnostic[] | readonly Diagnostic[]> | undefined;
28        /**
29         * The map has key by source file's path that has been changed
30         */
31        changedFilesSet?: ReadonlySet<Path>;
32        /**
33         * Set of affected files being iterated
34         */
35        affectedFiles?: readonly SourceFile[] | undefined;
36        /**
37         * Current changed file for iterating over affected files
38         */
39        currentChangedFilePath?: Path | undefined;
40        /**
41         * Map of file signatures, with key being file path, calculated while getting current changed file's affected files
42         * These will be committed whenever the iteration through affected files of current changed file is complete
43         */
44        currentAffectedFilesSignatures?: ReadonlyESMap<Path, string> | undefined;
45        /**
46         * Newly computed visible to outside referencedSet
47         */
48        currentAffectedFilesExportedModulesMap?: Readonly<BuilderState.ComputingExportedModulesMap> | undefined;
49        /**
50         * True if the semantic diagnostics were copied from the old state
51         */
52        semanticDiagnosticsFromOldState?: Set<Path>;
53        /**
54         * program corresponding to this state
55         */
56        program?: Program | undefined;
57        /**
58         * compilerOptions for the program
59         */
60        compilerOptions: CompilerOptions;
61        /**
62         * Files pending to be emitted
63         */
64        affectedFilesPendingEmit?: readonly Path[] | undefined;
65        /**
66         * Files pending to be emitted kind.
67         */
68        affectedFilesPendingEmitKind?: ReadonlyESMap<Path, BuilderFileEmit> | undefined;
69        /**
70         * Current index to retrieve pending affected file
71         */
72        affectedFilesPendingEmitIndex?: number | undefined;
73        /*
74         * true if semantic diagnostics are ReusableDiagnostic instead of Diagnostic
75         */
76        hasReusableDiagnostic?: true;
77    }
78
79    export const enum BuilderFileEmit {
80        DtsOnly,
81        Full
82    }
83
84    /**
85     * State to store the changed files, affected files and cache semantic diagnostics
86     */
87    // TODO: GH#18217 Properties of this interface are frequently asserted to be defined.
88    export interface BuilderProgramState extends BuilderState {
89        /**
90         * Cache of bind and check diagnostics for files with their Path being the key
91         */
92        semanticDiagnosticsPerFile: ESMap<Path, readonly Diagnostic[]> | undefined;
93        /**
94         * The map has key by source file's path that has been changed
95         */
96        changedFilesSet: Set<Path>;
97        /**
98         * Set of affected files being iterated
99         */
100        affectedFiles: readonly SourceFile[] | undefined;
101        /**
102         * Current index to retrieve affected file from
103         */
104        affectedFilesIndex: number | undefined;
105        /**
106         * Current changed file for iterating over affected files
107         */
108        currentChangedFilePath: Path | undefined;
109        /**
110         * Map of file signatures, with key being file path, calculated while getting current changed file's affected files
111         * These will be committed whenever the iteration through affected files of current changed file is complete
112         */
113        currentAffectedFilesSignatures: ESMap<Path, string> | undefined;
114        /**
115         * Newly computed visible to outside referencedSet
116         */
117        currentAffectedFilesExportedModulesMap: BuilderState.ComputingExportedModulesMap | undefined;
118        /**
119         * Already seen affected files
120         */
121        seenAffectedFiles: Set<Path> | undefined;
122        /**
123         * whether this program has cleaned semantic diagnostics cache for lib files
124         */
125        cleanedDiagnosticsOfLibFiles?: boolean;
126        /**
127         * True if the semantic diagnostics were copied from the old state
128         */
129        semanticDiagnosticsFromOldState?: Set<Path>;
130        /**
131         * program corresponding to this state
132         */
133        program: Program | undefined;
134        /**
135         * compilerOptions for the program
136         */
137        compilerOptions: CompilerOptions;
138        /**
139         * Files pending to be emitted
140         */
141        affectedFilesPendingEmit: Path[] | undefined;
142        /**
143         * Files pending to be emitted kind.
144         */
145        affectedFilesPendingEmitKind: ESMap<Path, BuilderFileEmit> | undefined;
146        /**
147         * Current index to retrieve pending affected file
148         */
149        affectedFilesPendingEmitIndex: number | undefined;
150        /**
151         * true if build info is emitted
152         */
153        buildInfoEmitPending: boolean;
154        /**
155         * Already seen emitted files
156         */
157        seenEmittedFiles: ESMap<Path, BuilderFileEmit> | undefined;
158        /**
159         * true if program has been emitted
160         */
161        programEmitComplete?: true;
162    }
163
164    function hasSameKeys(map1: ReadonlyCollection<string> | undefined, map2: ReadonlyCollection<string> | undefined): boolean {
165        // Has same size and every key is present in both maps
166        return map1 === map2 || map1 !== undefined && map2 !== undefined && map1.size === map2.size && !forEachKey(map1, key => !map2.has(key));
167    }
168
169    /**
170     * Create the state so that we can iterate on changedFiles/affected files
171     */
172    function createBuilderProgramState(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly<ReusableBuilderProgramState>): BuilderProgramState {
173        const state = BuilderState.create(newProgram, getCanonicalFileName, oldState) as BuilderProgramState;
174        state.program = newProgram;
175        const compilerOptions = newProgram.getCompilerOptions();
176        state.compilerOptions = compilerOptions;
177        // With --out or --outFile, any change affects all semantic diagnostics so no need to cache them
178        if (!outFile(compilerOptions)) {
179            state.semanticDiagnosticsPerFile = new Map();
180        }
181        state.changedFilesSet = new Set();
182
183        const useOldState = BuilderState.canReuseOldState(state.referencedMap, oldState);
184        const oldCompilerOptions = useOldState ? oldState!.compilerOptions : undefined;
185        const canCopySemanticDiagnostics = useOldState && oldState!.semanticDiagnosticsPerFile && !!state.semanticDiagnosticsPerFile &&
186            !compilerOptionsAffectSemanticDiagnostics(compilerOptions, oldCompilerOptions!);
187        if (useOldState) {
188            // Verify the sanity of old state
189            if (!oldState!.currentChangedFilePath) {
190                const affectedSignatures = oldState!.currentAffectedFilesSignatures;
191                Debug.assert(!oldState!.affectedFiles && (!affectedSignatures || !affectedSignatures.size), "Cannot reuse if only few affected files of currentChangedFile were iterated");
192            }
193            const changedFilesSet = oldState!.changedFilesSet;
194            if (canCopySemanticDiagnostics) {
195                Debug.assert(!changedFilesSet || !forEachKey(changedFilesSet, path => oldState!.semanticDiagnosticsPerFile!.has(path)), "Semantic diagnostics shouldnt be available for changed files");
196            }
197
198            // Copy old state's changed files set
199            changedFilesSet?.forEach(value => state.changedFilesSet.add(value));
200            if (!outFile(compilerOptions) && oldState!.affectedFilesPendingEmit) {
201                state.affectedFilesPendingEmit = oldState!.affectedFilesPendingEmit.slice();
202                state.affectedFilesPendingEmitKind = oldState!.affectedFilesPendingEmitKind && new Map(oldState!.affectedFilesPendingEmitKind);
203                state.affectedFilesPendingEmitIndex = oldState!.affectedFilesPendingEmitIndex;
204                state.seenAffectedFiles = new Set();
205            }
206        }
207
208        // Update changed files and copy semantic diagnostics if we can
209        const referencedMap = state.referencedMap;
210        const oldReferencedMap = useOldState ? oldState!.referencedMap : undefined;
211        const copyDeclarationFileDiagnostics = canCopySemanticDiagnostics && !compilerOptions.skipLibCheck === !oldCompilerOptions!.skipLibCheck;
212        const copyLibFileDiagnostics = copyDeclarationFileDiagnostics && !compilerOptions.skipDefaultLibCheck === !oldCompilerOptions!.skipDefaultLibCheck;
213        state.fileInfos.forEach((info, sourceFilePath) => {
214            let oldInfo: Readonly<BuilderState.FileInfo> | undefined;
215            let newReferences: BuilderState.ReferencedSet | undefined;
216
217            // if not using old state, every file is changed
218            if (!useOldState ||
219                // File wasn't present in old state
220                !(oldInfo = oldState!.fileInfos.get(sourceFilePath)) ||
221                // versions dont match
222                oldInfo.version !== info.version ||
223                // Referenced files changed
224                !hasSameKeys(newReferences = referencedMap && referencedMap.get(sourceFilePath), oldReferencedMap && oldReferencedMap.get(sourceFilePath)) ||
225                // Referenced file was deleted in the new program
226                newReferences && forEachKey(newReferences, path => !state.fileInfos.has(path) && oldState!.fileInfos.has(path))) {
227                // Register file as changed file and do not copy semantic diagnostics, since all changed files need to be re-evaluated
228                state.changedFilesSet.add(sourceFilePath);
229            }
230            else if (canCopySemanticDiagnostics) {
231                const sourceFile = newProgram.getSourceFileByPath(sourceFilePath)!;
232
233                if (sourceFile.isDeclarationFile && !copyDeclarationFileDiagnostics) { return; }
234                if (sourceFile.hasNoDefaultLib && !copyLibFileDiagnostics) { return; }
235
236                // Unchanged file copy diagnostics
237                const diagnostics = oldState!.semanticDiagnosticsPerFile!.get(sourceFilePath);
238                if (diagnostics) {
239                    state.semanticDiagnosticsPerFile!.set(sourceFilePath, oldState!.hasReusableDiagnostic ? convertToDiagnostics(diagnostics as readonly ReusableDiagnostic[], newProgram, getCanonicalFileName) : diagnostics as readonly Diagnostic[]);
240                    if (!state.semanticDiagnosticsFromOldState) {
241                        state.semanticDiagnosticsFromOldState = new Set();
242                    }
243                    state.semanticDiagnosticsFromOldState.add(sourceFilePath);
244                }
245            }
246        });
247
248        // If the global file is removed, add all files as changed
249        if (useOldState && forEachEntry(oldState!.fileInfos, (info, sourceFilePath) => info.affectsGlobalScope && !state.fileInfos.has(sourceFilePath))) {
250            BuilderState.getAllFilesExcludingDefaultLibraryFile(state, newProgram, /*firstSourceFile*/ undefined)
251                .forEach(file => state.changedFilesSet.add(file.resolvedPath));
252        }
253        else if (oldCompilerOptions && !outFile(compilerOptions) && compilerOptionsAffectEmit(compilerOptions, oldCompilerOptions)) {
254            // Add all files to affectedFilesPendingEmit since emit changed
255            newProgram.getSourceFiles().forEach(f => addToAffectedFilesPendingEmit(state, f.resolvedPath, BuilderFileEmit.Full));
256            Debug.assert(!state.seenAffectedFiles || !state.seenAffectedFiles.size);
257            state.seenAffectedFiles = state.seenAffectedFiles || new Set();
258        }
259
260        state.buildInfoEmitPending = !!state.changedFilesSet.size;
261        return state;
262    }
263
264    function convertToDiagnostics(diagnostics: readonly ReusableDiagnostic[], newProgram: Program, getCanonicalFileName: GetCanonicalFileName): readonly Diagnostic[] {
265        if (!diagnostics.length) return emptyArray;
266        const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(newProgram.getCompilerOptions())!, newProgram.getCurrentDirectory()));
267        return diagnostics.map(diagnostic => {
268            const result: Diagnostic = convertToDiagnosticRelatedInformation(diagnostic, newProgram, toPath);
269            result.reportsUnnecessary = diagnostic.reportsUnnecessary;
270            result.reportsDeprecated = diagnostic.reportDeprecated;
271            result.source = diagnostic.source;
272            result.skippedOn = diagnostic.skippedOn;
273            const { relatedInformation } = diagnostic;
274            result.relatedInformation = relatedInformation ?
275                relatedInformation.length ?
276                    relatedInformation.map(r => convertToDiagnosticRelatedInformation(r, newProgram, toPath)) :
277                    [] :
278                undefined;
279            return result;
280        });
281
282        function toPath(path: string) {
283            return ts.toPath(path, buildInfoDirectory, getCanonicalFileName);
284        }
285    }
286
287    function convertToDiagnosticRelatedInformation(diagnostic: ReusableDiagnosticRelatedInformation, newProgram: Program, toPath: (path: string) => Path): DiagnosticRelatedInformation {
288        const { file } = diagnostic;
289        return {
290            ...diagnostic,
291            file: file ? newProgram.getSourceFileByPath(toPath(file)) : undefined
292        };
293    }
294
295    /**
296     * Releases program and other related not needed properties
297     */
298    function releaseCache(state: BuilderProgramState) {
299        BuilderState.releaseCache(state);
300        state.program = undefined;
301    }
302
303    /**
304     * Creates a clone of the state
305     */
306    function cloneBuilderProgramState(state: Readonly<BuilderProgramState>): BuilderProgramState {
307        const newState = BuilderState.clone(state) as BuilderProgramState;
308        newState.semanticDiagnosticsPerFile = state.semanticDiagnosticsPerFile && new Map(state.semanticDiagnosticsPerFile);
309        newState.changedFilesSet = new Set(state.changedFilesSet);
310        newState.affectedFiles = state.affectedFiles;
311        newState.affectedFilesIndex = state.affectedFilesIndex;
312        newState.currentChangedFilePath = state.currentChangedFilePath;
313        newState.currentAffectedFilesSignatures = state.currentAffectedFilesSignatures && new Map(state.currentAffectedFilesSignatures);
314        newState.currentAffectedFilesExportedModulesMap = state.currentAffectedFilesExportedModulesMap && new Map(state.currentAffectedFilesExportedModulesMap);
315        newState.seenAffectedFiles = state.seenAffectedFiles && new Set(state.seenAffectedFiles);
316        newState.cleanedDiagnosticsOfLibFiles = state.cleanedDiagnosticsOfLibFiles;
317        newState.semanticDiagnosticsFromOldState = state.semanticDiagnosticsFromOldState && new Set(state.semanticDiagnosticsFromOldState);
318        newState.program = state.program;
319        newState.compilerOptions = state.compilerOptions;
320        newState.affectedFilesPendingEmit = state.affectedFilesPendingEmit && state.affectedFilesPendingEmit.slice();
321        newState.affectedFilesPendingEmitKind = state.affectedFilesPendingEmitKind && new Map(state.affectedFilesPendingEmitKind);
322        newState.affectedFilesPendingEmitIndex = state.affectedFilesPendingEmitIndex;
323        newState.seenEmittedFiles = state.seenEmittedFiles && new Map(state.seenEmittedFiles);
324        newState.programEmitComplete = state.programEmitComplete;
325        return newState;
326    }
327
328    /**
329     * Verifies that source file is ok to be used in calls that arent handled by next
330     */
331    function assertSourceFileOkWithoutNextAffectedCall(state: BuilderProgramState, sourceFile: SourceFile | undefined) {
332        Debug.assert(!sourceFile || !state.affectedFiles || state.affectedFiles[state.affectedFilesIndex! - 1] !== sourceFile || !state.semanticDiagnosticsPerFile!.has(sourceFile.resolvedPath));
333    }
334
335    /**
336     * This function returns the next affected file to be processed.
337     * Note that until doneAffected is called it would keep reporting same result
338     * This is to allow the callers to be able to actually remove affected file only when the operation is complete
339     * eg. if during diagnostics check cancellation token ends up cancelling the request, the affected file should be retained
340     */
341    function getNextAffectedFile(state: BuilderProgramState, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash): SourceFile | Program | undefined {
342        while (true) {
343            const { affectedFiles } = state;
344            if (affectedFiles) {
345                const seenAffectedFiles = state.seenAffectedFiles!;
346                let affectedFilesIndex = state.affectedFilesIndex!; // TODO: GH#18217
347                while (affectedFilesIndex < affectedFiles.length) {
348                    const affectedFile = affectedFiles[affectedFilesIndex];
349                    if (!seenAffectedFiles.has(affectedFile.resolvedPath)) {
350                        // Set the next affected file as seen and remove the cached semantic diagnostics
351                        state.affectedFilesIndex = affectedFilesIndex;
352                        handleDtsMayChangeOfAffectedFile(state, affectedFile, cancellationToken, computeHash);
353                        return affectedFile;
354                    }
355                    affectedFilesIndex++;
356                }
357
358                // Remove the changed file from the change set
359                state.changedFilesSet.delete(state.currentChangedFilePath!);
360                state.currentChangedFilePath = undefined;
361                // Commit the changes in file signature
362                BuilderState.updateSignaturesFromCache(state, state.currentAffectedFilesSignatures!);
363                state.currentAffectedFilesSignatures!.clear();
364                BuilderState.updateExportedFilesMapFromCache(state, state.currentAffectedFilesExportedModulesMap);
365                state.affectedFiles = undefined;
366            }
367
368            // Get next changed file
369            const nextKey = state.changedFilesSet.keys().next();
370            if (nextKey.done) {
371                // Done
372                return undefined;
373            }
374
375            // With --out or --outFile all outputs go into single file
376            // so operations are performed directly on program, return program
377            const program = Debug.checkDefined(state.program);
378            const compilerOptions = program.getCompilerOptions();
379            if (outFile(compilerOptions)) {
380                Debug.assert(!state.semanticDiagnosticsPerFile);
381                return program;
382            }
383
384            // Get next batch of affected files
385            if (!state.currentAffectedFilesSignatures) state.currentAffectedFilesSignatures = new Map();
386            if (state.exportedModulesMap) {
387                if (!state.currentAffectedFilesExportedModulesMap) state.currentAffectedFilesExportedModulesMap = new Map();
388            }
389            state.affectedFiles = BuilderState.getFilesAffectedBy(state, program, nextKey.value, cancellationToken, computeHash, state.currentAffectedFilesSignatures, state.currentAffectedFilesExportedModulesMap);
390            state.currentChangedFilePath = nextKey.value;
391            state.affectedFilesIndex = 0;
392            if (!state.seenAffectedFiles) state.seenAffectedFiles = new Set();
393        }
394    }
395
396    /**
397     * Returns next file to be emitted from files that retrieved semantic diagnostics but did not emit yet
398     */
399    function getNextAffectedFilePendingEmit(state: BuilderProgramState) {
400        const { affectedFilesPendingEmit } = state;
401        if (affectedFilesPendingEmit) {
402            const seenEmittedFiles = (state.seenEmittedFiles || (state.seenEmittedFiles = new Map()));
403            for (let i = state.affectedFilesPendingEmitIndex!; i < affectedFilesPendingEmit.length; i++) {
404                const affectedFile = Debug.checkDefined(state.program).getSourceFileByPath(affectedFilesPendingEmit[i]);
405                if (affectedFile) {
406                    const seenKind = seenEmittedFiles.get(affectedFile.resolvedPath);
407                    const emitKind = Debug.checkDefined(Debug.checkDefined(state.affectedFilesPendingEmitKind).get(affectedFile.resolvedPath));
408                    if (seenKind === undefined || seenKind < emitKind) {
409                        // emit this file
410                        state.affectedFilesPendingEmitIndex = i;
411                        return { affectedFile, emitKind };
412                    }
413                }
414            }
415            state.affectedFilesPendingEmit = undefined;
416            state.affectedFilesPendingEmitKind = undefined;
417            state.affectedFilesPendingEmitIndex = undefined;
418        }
419        return undefined;
420    }
421
422    /**
423     *  Handles semantic diagnostics and dts emit for affectedFile and files, that are referencing modules that export entities from affected file
424     *  This is because even though js emit doesnt change, dts emit / type used can change resulting in need for dts emit and js change
425     */
426    function handleDtsMayChangeOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash) {
427        removeSemanticDiagnosticsOf(state, affectedFile.resolvedPath);
428
429        // If affected files is everything except default library, then nothing more to do
430        if (state.allFilesExcludingDefaultLibraryFile === state.affectedFiles) {
431            if (!state.cleanedDiagnosticsOfLibFiles) {
432                state.cleanedDiagnosticsOfLibFiles = true;
433                const program = Debug.checkDefined(state.program);
434                const options = program.getCompilerOptions();
435                forEach(program.getSourceFiles(), f =>
436                    program.isSourceFileDefaultLibrary(f) &&
437                    !skipTypeChecking(f, options, program) &&
438                    removeSemanticDiagnosticsOf(state, f.resolvedPath)
439                );
440            }
441            return;
442        }
443
444        if (!state.compilerOptions.assumeChangesOnlyAffectDirectDependencies) {
445            forEachReferencingModulesOfExportOfAffectedFile(state, affectedFile, (state, path) => handleDtsMayChangeOf(state, path, cancellationToken, computeHash));
446        }
447    }
448
449    /**
450     * Handle the dts may change, so they need to be added to pending emit if dts emit is enabled,
451     * Also we need to make sure signature is updated for these files
452     */
453    function handleDtsMayChangeOf(state: BuilderProgramState, path: Path, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash) {
454        removeSemanticDiagnosticsOf(state, path);
455
456        if (!state.changedFilesSet.has(path)) {
457            const program = Debug.checkDefined(state.program);
458            const sourceFile = program.getSourceFileByPath(path);
459            if (sourceFile) {
460                // Even though the js emit doesnt change and we are already handling dts emit and semantic diagnostics
461                // we need to update the signature to reflect correctness of the signature(which is output d.ts emit) of this file
462                // This ensures that we dont later during incremental builds considering wrong signature.
463                // Eg where this also is needed to ensure that .tsbuildinfo generated by incremental build should be same as if it was first fresh build
464                BuilderState.updateShapeSignature(
465                    state,
466                    program,
467                    sourceFile,
468                    Debug.checkDefined(state.currentAffectedFilesSignatures),
469                    cancellationToken,
470                    computeHash,
471                    state.currentAffectedFilesExportedModulesMap
472                );
473                // If not dts emit, nothing more to do
474                if (getEmitDeclarations(state.compilerOptions)) {
475                    addToAffectedFilesPendingEmit(state, path, BuilderFileEmit.DtsOnly);
476                }
477            }
478        }
479
480        return false;
481    }
482
483    /**
484     * Removes semantic diagnostics for path and
485     * returns true if there are no more semantic diagnostics from the old state
486     */
487    function removeSemanticDiagnosticsOf(state: BuilderProgramState, path: Path) {
488        if (!state.semanticDiagnosticsFromOldState) {
489            return true;
490        }
491        state.semanticDiagnosticsFromOldState.delete(path);
492        state.semanticDiagnosticsPerFile!.delete(path);
493        return !state.semanticDiagnosticsFromOldState.size;
494    }
495
496    function isChangedSignature(state: BuilderProgramState, path: Path) {
497        const newSignature = Debug.checkDefined(state.currentAffectedFilesSignatures).get(path);
498        const oldSignature = Debug.checkDefined(state.fileInfos.get(path)).signature;
499        return newSignature !== oldSignature;
500    }
501
502    /**
503     * Iterate on referencing modules that export entities from affected file
504     */
505    function forEachReferencingModulesOfExportOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, fn: (state: BuilderProgramState, filePath: Path) => boolean) {
506        // If there was change in signature (dts output) for the changed file,
507        // then only we need to handle pending file emit
508        if (!state.exportedModulesMap || !state.changedFilesSet.has(affectedFile.resolvedPath)) {
509            return;
510        }
511
512        if (!isChangedSignature(state, affectedFile.resolvedPath)) return;
513
514        // Since isolated modules dont change js files, files affected by change in signature is itself
515        // But we need to cleanup semantic diagnostics and queue dts emit for affected files
516        if (state.compilerOptions.isolatedModules) {
517            const seenFileNamesMap = new Map<Path, true>();
518            seenFileNamesMap.set(affectedFile.resolvedPath, true);
519            const queue = BuilderState.getReferencedByPaths(state, affectedFile.resolvedPath);
520            while (queue.length > 0) {
521                const currentPath = queue.pop()!;
522                if (!seenFileNamesMap.has(currentPath)) {
523                    seenFileNamesMap.set(currentPath, true);
524                    const result = fn(state, currentPath);
525                    if (result && isChangedSignature(state, currentPath)) {
526                        const currentSourceFile = Debug.checkDefined(state.program).getSourceFileByPath(currentPath)!;
527                        queue.push(...BuilderState.getReferencedByPaths(state, currentSourceFile.resolvedPath));
528                    }
529                }
530            }
531        }
532
533        Debug.assert(!!state.currentAffectedFilesExportedModulesMap);
534        const seenFileAndExportsOfFile = new Set<string>();
535        // Go through exported modules from cache first
536        // If exported modules has path, all files referencing file exported from are affected
537        if (forEachEntry(state.currentAffectedFilesExportedModulesMap, (exportedModules, exportedFromPath) =>
538            exportedModules &&
539            exportedModules.has(affectedFile.resolvedPath) &&
540            forEachFilesReferencingPath(state, exportedFromPath, seenFileAndExportsOfFile, fn)
541        )) {
542            return;
543        }
544
545        // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected
546        forEachEntry(state.exportedModulesMap, (exportedModules, exportedFromPath) =>
547            !state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it
548            exportedModules.has(affectedFile.resolvedPath) &&
549            forEachFilesReferencingPath(state, exportedFromPath, seenFileAndExportsOfFile, fn)
550        );
551    }
552
553    /**
554     * Iterate on files referencing referencedPath
555     */
556    function forEachFilesReferencingPath(state: BuilderProgramState, referencedPath: Path, seenFileAndExportsOfFile: Set<string>, fn: (state: BuilderProgramState, filePath: Path) => boolean) {
557        return forEachEntry(state.referencedMap!, (referencesInFile, filePath) =>
558            referencesInFile.has(referencedPath) && forEachFileAndExportsOfFile(state, filePath, seenFileAndExportsOfFile, fn)
559        );
560    }
561
562    /**
563     * fn on file and iterate on anything that exports this file
564     */
565    function forEachFileAndExportsOfFile(state: BuilderProgramState, filePath: Path, seenFileAndExportsOfFile: Set<string>, fn: (state: BuilderProgramState, filePath: Path) => boolean): boolean {
566        if (!tryAddToSet(seenFileAndExportsOfFile, filePath)) {
567            return false;
568        }
569
570        if (fn(state, filePath)) {
571            // If there are no more diagnostics from old cache, done
572            return true;
573        }
574
575        Debug.assert(!!state.currentAffectedFilesExportedModulesMap);
576        // Go through exported modules from cache first
577        // If exported modules has path, all files referencing file exported from are affected
578        if (forEachEntry(state.currentAffectedFilesExportedModulesMap, (exportedModules, exportedFromPath) =>
579            exportedModules &&
580            exportedModules.has(filePath) &&
581            forEachFileAndExportsOfFile(state, exportedFromPath, seenFileAndExportsOfFile, fn)
582        )) {
583            return true;
584        }
585
586        // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected
587        if (forEachEntry(state.exportedModulesMap!, (exportedModules, exportedFromPath) =>
588            !state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it
589            exportedModules.has(filePath) &&
590            forEachFileAndExportsOfFile(state, exportedFromPath, seenFileAndExportsOfFile, fn)
591        )) {
592            return true;
593        }
594
595        // Remove diagnostics of files that import this file (without going to exports of referencing files)
596        return !!forEachEntry(state.referencedMap!, (referencesInFile, referencingFilePath) =>
597            referencesInFile.has(filePath) &&
598            !seenFileAndExportsOfFile.has(referencingFilePath) && // Not already removed diagnostic file
599            fn(state, referencingFilePath) // Dont add to seen since this is not yet done with the export removal
600        );
601    }
602
603
604    /**
605     * This is called after completing operation on the next affected file.
606     * The operations here are postponed to ensure that cancellation during the iteration is handled correctly
607     */
608    function doneWithAffectedFile(
609        state: BuilderProgramState,
610        affected: SourceFile | Program,
611        emitKind?: BuilderFileEmit,
612        isPendingEmit?: boolean,
613        isBuildInfoEmit?: boolean
614    ) {
615        if (isBuildInfoEmit) {
616            state.buildInfoEmitPending = false;
617        }
618        else if (affected === state.program) {
619            state.changedFilesSet.clear();
620            state.programEmitComplete = true;
621        }
622        else {
623            state.seenAffectedFiles!.add((affected as SourceFile).resolvedPath);
624            if (emitKind !== undefined) {
625                (state.seenEmittedFiles || (state.seenEmittedFiles = new Map())).set((affected as SourceFile).resolvedPath, emitKind);
626            }
627            if (isPendingEmit) {
628                state.affectedFilesPendingEmitIndex!++;
629                state.buildInfoEmitPending = true;
630            }
631            else {
632                state.affectedFilesIndex!++;
633            }
634        }
635    }
636
637    /**
638     * Returns the result with affected file
639     */
640    function toAffectedFileResult<T>(state: BuilderProgramState, result: T, affected: SourceFile | Program): AffectedFileResult<T> {
641        doneWithAffectedFile(state, affected);
642        return { result, affected };
643    }
644
645    /**
646     * Returns the result with affected file
647     */
648    function toAffectedFileEmitResult(
649        state: BuilderProgramState,
650        result: EmitResult,
651        affected: SourceFile | Program,
652        emitKind: BuilderFileEmit,
653        isPendingEmit?: boolean,
654        isBuildInfoEmit?: boolean
655    ): AffectedFileResult<EmitResult> {
656        doneWithAffectedFile(state, affected, emitKind, isPendingEmit, isBuildInfoEmit);
657        return { result, affected };
658    }
659
660    /**
661     * Gets semantic diagnostics for the file which are
662     * bindAndCheckDiagnostics (from cache) and program diagnostics
663     */
664    function getSemanticDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] {
665        return concatenate(
666            getBinderAndCheckerDiagnosticsOfFile(state, sourceFile, cancellationToken),
667            Debug.checkDefined(state.program).getProgramDiagnostics(sourceFile)
668        );
669    }
670
671    /**
672     * Gets the binder and checker diagnostics either from cache if present, or otherwise from program and caches it
673     * 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
674     */
675    function getBinderAndCheckerDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] {
676        const path = sourceFile.resolvedPath;
677        if (state.semanticDiagnosticsPerFile) {
678            const cachedDiagnostics = state.semanticDiagnosticsPerFile.get(path);
679            // Report the bind and check diagnostics from the cache if we already have those diagnostics present
680            if (cachedDiagnostics) {
681                return filterSemanticDiagnotics(cachedDiagnostics, state.compilerOptions);
682            }
683        }
684
685        // Diagnostics werent cached, get them from program, and cache the result
686        const diagnostics = Debug.checkDefined(state.program).getBindAndCheckDiagnostics(sourceFile, cancellationToken);
687        if (state.semanticDiagnosticsPerFile) {
688            state.semanticDiagnosticsPerFile.set(path, diagnostics);
689        }
690        return filterSemanticDiagnotics(diagnostics, state.compilerOptions);
691    }
692
693    export type ProgramBuildInfoDiagnostic = string | [string, readonly ReusableDiagnostic[]];
694    export type ProgramBuilderInfoFilePendingEmit = [string, BuilderFileEmit];
695    export interface ProgramBuildInfo {
696        fileInfos: MapLike<BuilderState.FileInfo>;
697        options: CompilerOptions;
698        referencedMap?: MapLike<string[]>;
699        exportedModulesMap?: MapLike<string[]>;
700        semanticDiagnosticsPerFile?: ProgramBuildInfoDiagnostic[];
701        affectedFilesPendingEmit?: ProgramBuilderInfoFilePendingEmit[];
702    }
703
704    /**
705     * Gets the program information to be emitted in buildInfo so that we can use it to create new program
706     */
707    function getProgramBuildInfo(state: Readonly<ReusableBuilderProgramState>, getCanonicalFileName: GetCanonicalFileName): ProgramBuildInfo | undefined {
708        if (outFile(state.compilerOptions)) return undefined;
709        const currentDirectory = Debug.checkDefined(state.program).getCurrentDirectory();
710        const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(state.compilerOptions)!, currentDirectory));
711        const fileInfos: MapLike<BuilderState.FileInfo> = {};
712        state.fileInfos.forEach((value, key) => {
713            const signature = state.currentAffectedFilesSignatures && state.currentAffectedFilesSignatures.get(key);
714            fileInfos[relativeToBuildInfo(key)] = signature === undefined ? value : { version: value.version, signature, affectsGlobalScope: value.affectsGlobalScope };
715        });
716
717        const result: ProgramBuildInfo = {
718            fileInfos,
719            options: convertToReusableCompilerOptions(state.compilerOptions, relativeToBuildInfoEnsuringAbsolutePath)
720        };
721        if (state.referencedMap) {
722            const referencedMap: MapLike<string[]> = {};
723            for (const key of arrayFrom(state.referencedMap.keys()).sort(compareStringsCaseSensitive)) {
724                referencedMap[relativeToBuildInfo(key)] = arrayFrom(state.referencedMap.get(key)!.keys(), relativeToBuildInfo).sort(compareStringsCaseSensitive);
725            }
726            result.referencedMap = referencedMap;
727        }
728
729        if (state.exportedModulesMap) {
730            const exportedModulesMap: MapLike<string[]> = {};
731            for (const key of arrayFrom(state.exportedModulesMap.keys()).sort(compareStringsCaseSensitive)) {
732                const newValue = state.currentAffectedFilesExportedModulesMap && state.currentAffectedFilesExportedModulesMap.get(key);
733                // Not in temporary cache, use existing value
734                if (newValue === undefined) exportedModulesMap[relativeToBuildInfo(key)] = arrayFrom(state.exportedModulesMap.get(key)!.keys(), relativeToBuildInfo).sort(compareStringsCaseSensitive);
735                // Value in cache and has updated value map, use that
736                else if (newValue) exportedModulesMap[relativeToBuildInfo(key)] = arrayFrom(newValue.keys(), relativeToBuildInfo).sort(compareStringsCaseSensitive);
737            }
738            result.exportedModulesMap = exportedModulesMap;
739        }
740
741        if (state.semanticDiagnosticsPerFile) {
742            const semanticDiagnosticsPerFile: ProgramBuildInfoDiagnostic[] = [];
743            for (const key of arrayFrom(state.semanticDiagnosticsPerFile.keys()).sort(compareStringsCaseSensitive)) {
744                const value = state.semanticDiagnosticsPerFile.get(key)!;
745                semanticDiagnosticsPerFile.push(
746                    value.length ?
747                        [
748                            relativeToBuildInfo(key),
749                            state.hasReusableDiagnostic ?
750                                value as readonly ReusableDiagnostic[] :
751                                convertToReusableDiagnostics(value as readonly Diagnostic[], relativeToBuildInfo)
752                        ] :
753                        relativeToBuildInfo(key)
754                );
755            }
756            result.semanticDiagnosticsPerFile = semanticDiagnosticsPerFile;
757        }
758
759        if (state.affectedFilesPendingEmit) {
760            const affectedFilesPendingEmit: ProgramBuilderInfoFilePendingEmit[] = [];
761            const seenFiles = new Set<Path>();
762            for (const path of state.affectedFilesPendingEmit.slice(state.affectedFilesPendingEmitIndex).sort(compareStringsCaseSensitive)) {
763                if (tryAddToSet(seenFiles, path)) {
764                    affectedFilesPendingEmit.push([relativeToBuildInfo(path), state.affectedFilesPendingEmitKind!.get(path)!]);
765                }
766            }
767            result.affectedFilesPendingEmit = affectedFilesPendingEmit;
768        }
769
770        return result;
771
772        function relativeToBuildInfoEnsuringAbsolutePath(path: string) {
773            return relativeToBuildInfo(getNormalizedAbsolutePath(path, currentDirectory));
774        }
775
776        function relativeToBuildInfo(path: string) {
777            return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory, path, getCanonicalFileName));
778        }
779    }
780
781    function convertToReusableCompilerOptions(options: CompilerOptions, relativeToBuildInfo: (path: string) => string) {
782        const result: CompilerOptions = {};
783        const { optionsNameMap } = getOptionsNameMap();
784
785        for (const name in options) {
786            if (hasProperty(options, name)) {
787                result[name] = convertToReusableCompilerOptionValue(
788                    optionsNameMap.get(name.toLowerCase()),
789                    options[name] as CompilerOptionsValue,
790                    relativeToBuildInfo
791                );
792            }
793        }
794        if (result.configFilePath) {
795            result.configFilePath = relativeToBuildInfo(result.configFilePath);
796        }
797        return result;
798    }
799
800    function convertToReusableCompilerOptionValue(option: CommandLineOption | undefined, value: CompilerOptionsValue, relativeToBuildInfo: (path: string) => string) {
801        if (option) {
802            if (option.type === "list") {
803                const values = value as readonly (string | number)[];
804                if (option.element.isFilePath && values.length) {
805                    return values.map(relativeToBuildInfo);
806                }
807            }
808            else if (option.isFilePath) {
809                return relativeToBuildInfo(value as string);
810            }
811        }
812        return value;
813    }
814
815    function convertToReusableDiagnostics(diagnostics: readonly Diagnostic[], relativeToBuildInfo: (path: string) => string): readonly ReusableDiagnostic[] {
816        Debug.assert(!!diagnostics.length);
817        return diagnostics.map(diagnostic => {
818            const result: ReusableDiagnostic = convertToReusableDiagnosticRelatedInformation(diagnostic, relativeToBuildInfo);
819            result.reportsUnnecessary = diagnostic.reportsUnnecessary;
820            result.reportDeprecated = diagnostic.reportsDeprecated;
821            result.source = diagnostic.source;
822            result.skippedOn = diagnostic.skippedOn;
823            const { relatedInformation } = diagnostic;
824            result.relatedInformation = relatedInformation ?
825                relatedInformation.length ?
826                    relatedInformation.map(r => convertToReusableDiagnosticRelatedInformation(r, relativeToBuildInfo)) :
827                    [] :
828                undefined;
829            return result;
830        });
831    }
832
833    function convertToReusableDiagnosticRelatedInformation(diagnostic: DiagnosticRelatedInformation, relativeToBuildInfo: (path: string) => string): ReusableDiagnosticRelatedInformation {
834        const { file } = diagnostic;
835        return {
836            ...diagnostic,
837            file: file ? relativeToBuildInfo(file.resolvedPath) : undefined
838        };
839    }
840
841    export enum BuilderProgramKind {
842        SemanticDiagnosticsBuilderProgram,
843        EmitAndSemanticDiagnosticsBuilderProgram
844    }
845
846    export interface BuilderCreationParameters {
847        newProgram: Program;
848        host: BuilderProgramHost;
849        oldProgram: BuilderProgram | undefined;
850        configFileParsingDiagnostics: readonly Diagnostic[];
851    }
852
853    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 {
854        let host: BuilderProgramHost;
855        let newProgram: Program;
856        let oldProgram: BuilderProgram;
857        if (newProgramOrRootNames === undefined) {
858            Debug.assert(hostOrOptions === undefined);
859            host = oldProgramOrHost as CompilerHost;
860            oldProgram = configFileParsingDiagnosticsOrOldProgram as BuilderProgram;
861            Debug.assert(!!oldProgram);
862            newProgram = oldProgram.getProgram();
863        }
864        else if (isArray(newProgramOrRootNames)) {
865            oldProgram = configFileParsingDiagnosticsOrOldProgram as BuilderProgram;
866            newProgram = createProgram({
867                rootNames: newProgramOrRootNames,
868                options: hostOrOptions as CompilerOptions,
869                host: oldProgramOrHost as CompilerHost,
870                oldProgram: oldProgram && oldProgram.getProgramOrUndefined(),
871                configFileParsingDiagnostics,
872                projectReferences
873            });
874            host = oldProgramOrHost as CompilerHost;
875        }
876        else {
877            newProgram = newProgramOrRootNames;
878            host = hostOrOptions as BuilderProgramHost;
879            oldProgram = oldProgramOrHost as BuilderProgram;
880            configFileParsingDiagnostics = configFileParsingDiagnosticsOrOldProgram as readonly Diagnostic[];
881        }
882        return { host, newProgram, oldProgram, configFileParsingDiagnostics: configFileParsingDiagnostics || emptyArray };
883    }
884
885    export function createBuilderProgram(kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): SemanticDiagnosticsBuilderProgram;
886    export function createBuilderProgram(kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): EmitAndSemanticDiagnosticsBuilderProgram;
887    export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, host, oldProgram, configFileParsingDiagnostics }: BuilderCreationParameters) {
888        // Return same program if underlying program doesnt change
889        let oldState = oldProgram && oldProgram.getState();
890        if (oldState && newProgram === oldState.program && configFileParsingDiagnostics === newProgram.getConfigFileParsingDiagnostics()) {
891            newProgram = undefined!; // TODO: GH#18217
892            oldState = undefined;
893            return oldProgram;
894        }
895
896        /**
897         * Create the canonical file name for identity
898         */
899        const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
900        /**
901         * Computing hash to for signature verification
902         */
903        const computeHash = maybeBind(host, host.createHash);
904        let state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState);
905        let backupState: BuilderProgramState | undefined;
906        newProgram.getProgramBuildInfo = () => getProgramBuildInfo(state, getCanonicalFileName);
907
908        // To ensure that we arent storing any references to old program or new program without state
909        newProgram = undefined!; // TODO: GH#18217
910        oldProgram = undefined;
911        oldState = undefined;
912
913        const builderProgram = createRedirectedBuilderProgram(state, configFileParsingDiagnostics);
914        builderProgram.getState = () => state;
915        builderProgram.backupState = () => {
916            Debug.assert(backupState === undefined);
917            backupState = cloneBuilderProgramState(state);
918        };
919        builderProgram.restoreState = () => {
920            state = Debug.checkDefined(backupState);
921            backupState = undefined;
922        };
923        builderProgram.getAllDependencies = sourceFile => BuilderState.getAllDependencies(state, Debug.checkDefined(state.program), sourceFile);
924        builderProgram.getSemanticDiagnostics = getSemanticDiagnostics;
925        builderProgram.emit = emit;
926        builderProgram.releaseProgram = () => {
927            releaseCache(state);
928            backupState = undefined;
929        };
930
931        if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) {
932            (builderProgram as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile;
933        }
934        else if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) {
935            (builderProgram as EmitAndSemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile;
936            (builderProgram as EmitAndSemanticDiagnosticsBuilderProgram).emitNextAffectedFile = emitNextAffectedFile;
937            builderProgram.emitBuildInfo = emitBuildInfo;
938        }
939        else {
940            notImplemented();
941        }
942
943        return builderProgram;
944
945        function emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult {
946            if (state.buildInfoEmitPending) {
947                const result = Debug.checkDefined(state.program).emitBuildInfo(writeFile || maybeBind(host, host.writeFile), cancellationToken);
948                state.buildInfoEmitPending = false;
949                return result;
950            }
951            return emitSkippedWithNoDiagnostics;
952        }
953
954        /**
955         * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete
956         * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host
957         * in that order would be used to write the files
958         */
959        function emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult<EmitResult> {
960            let affected = getNextAffectedFile(state, cancellationToken, computeHash);
961            let emitKind = BuilderFileEmit.Full;
962            let isPendingEmitFile = false;
963            if (!affected) {
964                if (!outFile(state.compilerOptions)) {
965                    const pendingAffectedFile = getNextAffectedFilePendingEmit(state);
966                    if (!pendingAffectedFile) {
967                        if (!state.buildInfoEmitPending) {
968                            return undefined;
969                        }
970
971                        const affected = Debug.checkDefined(state.program);
972                        return toAffectedFileEmitResult(
973                            state,
974                            // When whole program is affected, do emit only once (eg when --out or --outFile is specified)
975                            // Otherwise just affected file
976                            affected.emitBuildInfo(writeFile || maybeBind(host, host.writeFile), cancellationToken),
977                            affected,
978                            /*emitKind*/ BuilderFileEmit.Full,
979                            /*isPendingEmitFile*/ false,
980                            /*isBuildInfoEmit*/ true
981                        );
982                    }
983                    ({ affectedFile: affected, emitKind } = pendingAffectedFile);
984                    isPendingEmitFile = true;
985                }
986                else {
987                    const program = Debug.checkDefined(state.program);
988                    if (state.programEmitComplete) return undefined;
989                    affected = program;
990                }
991            }
992
993            return toAffectedFileEmitResult(
994                state,
995                // When whole program is affected, do emit only once (eg when --out or --outFile is specified)
996                // Otherwise just affected file
997                Debug.checkDefined(state.program).emit(
998                    affected === state.program ? undefined : affected as SourceFile,
999                    writeFile || maybeBind(host, host.writeFile),
1000                    cancellationToken,
1001                    emitOnlyDtsFiles || emitKind === BuilderFileEmit.DtsOnly,
1002                    customTransformers
1003                ),
1004                affected,
1005                emitKind,
1006                isPendingEmitFile,
1007            );
1008        }
1009
1010        /**
1011         * Emits the JavaScript and declaration files.
1012         * When targetSource file is specified, emits the files corresponding to that source file,
1013         * otherwise for the whole program.
1014         * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified,
1015         * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified,
1016         * it will only emit all the affected files instead of whole program
1017         *
1018         * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host
1019         * in that order would be used to write the files
1020         */
1021        function emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult {
1022            let restorePendingEmitOnHandlingNoEmitSuccess = false;
1023            let savedAffectedFilesPendingEmit;
1024            let savedAffectedFilesPendingEmitKind;
1025            let savedAffectedFilesPendingEmitIndex;
1026            // Backup and restore affected pendings emit state for non emit Builder if noEmitOnError is enabled and emitBuildInfo could be written in case there are errors
1027            // This ensures pending files to emit is updated in tsbuildinfo
1028            // Note that when there are no errors, emit proceeds as if everything is emitted as it is callers reponsibility to write the files to disk if at all (because its builder that doesnt track files to emit)
1029            if (kind !== BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram &&
1030                !targetSourceFile &&
1031                !outFile(state.compilerOptions) &&
1032                !state.compilerOptions.noEmit &&
1033                state.compilerOptions.noEmitOnError) {
1034                restorePendingEmitOnHandlingNoEmitSuccess = true;
1035                savedAffectedFilesPendingEmit = state.affectedFilesPendingEmit && state.affectedFilesPendingEmit.slice();
1036                savedAffectedFilesPendingEmitKind = state.affectedFilesPendingEmitKind && new Map(state.affectedFilesPendingEmitKind);
1037                savedAffectedFilesPendingEmitIndex = state.affectedFilesPendingEmitIndex;
1038            }
1039
1040            if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) {
1041                assertSourceFileOkWithoutNextAffectedCall(state, targetSourceFile);
1042            }
1043            const result = handleNoEmitOptions(builderProgram, targetSourceFile, writeFile, cancellationToken);
1044            if (result) return result;
1045
1046            if (restorePendingEmitOnHandlingNoEmitSuccess) {
1047                state.affectedFilesPendingEmit = savedAffectedFilesPendingEmit;
1048                state.affectedFilesPendingEmitKind = savedAffectedFilesPendingEmitKind;
1049                state.affectedFilesPendingEmitIndex = savedAffectedFilesPendingEmitIndex;
1050            }
1051
1052            // Emit only affected files if using builder for emit
1053            if (!targetSourceFile && kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) {
1054                // Emit and report any errors we ran into.
1055                let sourceMaps: SourceMapEmitResult[] = [];
1056                let emitSkipped = false;
1057                let diagnostics: Diagnostic[] | undefined;
1058                let emittedFiles: string[] = [];
1059
1060                let affectedEmitResult: AffectedFileResult<EmitResult>;
1061                while (affectedEmitResult = emitNextAffectedFile(writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers)) {
1062                    emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped;
1063                    diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics);
1064                    emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles);
1065                    sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps);
1066                }
1067                return {
1068                    emitSkipped,
1069                    diagnostics: diagnostics || emptyArray,
1070                    emittedFiles,
1071                    sourceMaps
1072                };
1073            }
1074            return Debug.checkDefined(state.program).emit(targetSourceFile, writeFile || maybeBind(host, host.writeFile), cancellationToken, emitOnlyDtsFiles, customTransformers);
1075        }
1076
1077        /**
1078         * Return the semantic diagnostics for the next affected file or undefined if iteration is complete
1079         * If provided ignoreSourceFile would be called before getting the diagnostics and would ignore the sourceFile if the returned value was true
1080         */
1081        function getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult<readonly Diagnostic[]> {
1082            while (true) {
1083                const affected = getNextAffectedFile(state, cancellationToken, computeHash);
1084                if (!affected) {
1085                    // Done
1086                    return undefined;
1087                }
1088                else if (affected === state.program) {
1089                    // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified)
1090                    return toAffectedFileResult(
1091                        state,
1092                        state.program.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken),
1093                        affected
1094                    );
1095                }
1096
1097                // Add file to affected file pending emit to handle for later emit time
1098                // 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
1099                if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram || state.compilerOptions.noEmit || state.compilerOptions.noEmitOnError) {
1100                    addToAffectedFilesPendingEmit(state, (affected as SourceFile).resolvedPath, BuilderFileEmit.Full);
1101                }
1102
1103                // Get diagnostics for the affected file if its not ignored
1104                if (ignoreSourceFile && ignoreSourceFile(affected as SourceFile)) {
1105                    // Get next affected file
1106                    doneWithAffectedFile(state, affected);
1107                    continue;
1108                }
1109
1110                return toAffectedFileResult(
1111                    state,
1112                    getSemanticDiagnosticsOfFile(state, affected as SourceFile, cancellationToken),
1113                    affected
1114                );
1115            }
1116        }
1117
1118        /**
1119         * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program
1120         * The semantic diagnostics are cached and managed here
1121         * Note that it is assumed that when asked about semantic diagnostics through this API,
1122         * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics
1123         * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided,
1124         * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics
1125         */
1126        function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] {
1127            assertSourceFileOkWithoutNextAffectedCall(state, sourceFile);
1128            const compilerOptions = Debug.checkDefined(state.program).getCompilerOptions();
1129            if (outFile(compilerOptions)) {
1130                Debug.assert(!state.semanticDiagnosticsPerFile);
1131                // We dont need to cache the diagnostics just return them from program
1132                return Debug.checkDefined(state.program).getSemanticDiagnostics(sourceFile, cancellationToken);
1133            }
1134
1135            if (sourceFile) {
1136                return getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken);
1137            }
1138
1139            // When semantic builder asks for diagnostics of the whole program,
1140            // ensure that all the affected files are handled
1141            // eslint-disable-next-line no-empty
1142            while (getSemanticDiagnosticsOfNextAffectedFile(cancellationToken)) {
1143            }
1144
1145            let diagnostics: Diagnostic[] | undefined;
1146            for (const sourceFile of Debug.checkDefined(state.program).getSourceFiles()) {
1147                diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken));
1148            }
1149            return diagnostics || emptyArray;
1150        }
1151    }
1152
1153    function addToAffectedFilesPendingEmit(state: BuilderProgramState, affectedFilePendingEmit: Path, kind: BuilderFileEmit) {
1154        if (!state.affectedFilesPendingEmit) state.affectedFilesPendingEmit = [];
1155        if (!state.affectedFilesPendingEmitKind) state.affectedFilesPendingEmitKind = new Map();
1156
1157        const existingKind = state.affectedFilesPendingEmitKind.get(affectedFilePendingEmit);
1158        state.affectedFilesPendingEmit.push(affectedFilePendingEmit);
1159        state.affectedFilesPendingEmitKind.set(affectedFilePendingEmit, existingKind || kind);
1160
1161        // affectedFilesPendingEmitIndex === undefined
1162        // - means the emit state.affectedFilesPendingEmit was undefined before adding current affected files
1163        //   so start from 0 as array would be affectedFilesPendingEmit
1164        // else, continue to iterate from existing index, the current set is appended to existing files
1165        if (state.affectedFilesPendingEmitIndex === undefined) {
1166            state.affectedFilesPendingEmitIndex = 0;
1167        }
1168    }
1169
1170    function getMapOfReferencedSet(mapLike: MapLike<readonly string[]> | undefined, toPath: (path: string) => Path): ReadonlyESMap<Path, BuilderState.ReferencedSet> | undefined {
1171        if (!mapLike) return undefined;
1172        const map = new Map<Path, BuilderState.ReferencedSet>();
1173        // Copies keys/values from template. Note that for..in will not throw if
1174        // template is undefined, and instead will just exit the loop.
1175        for (const key in mapLike) {
1176            if (hasProperty(mapLike, key)) {
1177                map.set(toPath(key), new Set(mapLike[key].map(toPath)));
1178            }
1179        }
1180        return map;
1181    }
1182
1183    export function createBuildProgramUsingProgramBuildInfo(program: ProgramBuildInfo, buildInfoPath: string, host: ReadBuildProgramHost): EmitAndSemanticDiagnosticsBuilderProgram {
1184        const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory()));
1185        const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
1186
1187        const fileInfos = new Map<Path, BuilderState.FileInfo>();
1188        for (const key in program.fileInfos) {
1189            if (hasProperty(program.fileInfos, key)) {
1190                fileInfos.set(toPath(key), program.fileInfos[key]);
1191            }
1192        }
1193
1194        const state: ReusableBuilderProgramState = {
1195            fileInfos,
1196            compilerOptions: convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath),
1197            referencedMap: getMapOfReferencedSet(program.referencedMap, toPath),
1198            exportedModulesMap: getMapOfReferencedSet(program.exportedModulesMap, toPath),
1199            semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toPath(isString(value) ? value : value[0]), value => isString(value) ? emptyArray : value[1]),
1200            hasReusableDiagnostic: true,
1201            affectedFilesPendingEmit: map(program.affectedFilesPendingEmit, value => toPath(value[0])),
1202            affectedFilesPendingEmitKind: program.affectedFilesPendingEmit && arrayToMap(program.affectedFilesPendingEmit, value => toPath(value[0]), value => value[1]),
1203            affectedFilesPendingEmitIndex: program.affectedFilesPendingEmit && 0,
1204        };
1205        return {
1206            getState: () => state,
1207            backupState: noop,
1208            restoreState: noop,
1209            getProgram: notImplemented,
1210            getProgramOrUndefined: returnUndefined,
1211            releaseProgram: noop,
1212            getCompilerOptions: () => state.compilerOptions,
1213            getSourceFile: notImplemented,
1214            getSourceFiles: notImplemented,
1215            getOptionsDiagnostics: notImplemented,
1216            getGlobalDiagnostics: notImplemented,
1217            getConfigFileParsingDiagnostics: notImplemented,
1218            getSyntacticDiagnostics: notImplemented,
1219            getDeclarationDiagnostics: notImplemented,
1220            getSemanticDiagnostics: notImplemented,
1221            emit: notImplemented,
1222            getAllDependencies: notImplemented,
1223            getCurrentDirectory: notImplemented,
1224            emitNextAffectedFile: notImplemented,
1225            getSemanticDiagnosticsOfNextAffectedFile: notImplemented,
1226            emitBuildInfo: notImplemented,
1227            close: noop,
1228        };
1229
1230        function toPath(path: string) {
1231            return ts.toPath(path, buildInfoDirectory, getCanonicalFileName);
1232        }
1233
1234        function toAbsolutePath(path: string) {
1235            return getNormalizedAbsolutePath(path, buildInfoDirectory);
1236        }
1237    }
1238
1239    export function createRedirectedBuilderProgram(state: { program: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: readonly Diagnostic[]): BuilderProgram {
1240        return {
1241            getState: notImplemented,
1242            backupState: noop,
1243            restoreState: noop,
1244            getProgram,
1245            getProgramOrUndefined: () => state.program,
1246            releaseProgram: () => state.program = undefined,
1247            getCompilerOptions: () => state.compilerOptions,
1248            getSourceFile: fileName => getProgram().getSourceFile(fileName),
1249            getSourceFiles: () => getProgram().getSourceFiles(),
1250            getOptionsDiagnostics: cancellationToken => getProgram().getOptionsDiagnostics(cancellationToken),
1251            getGlobalDiagnostics: cancellationToken => getProgram().getGlobalDiagnostics(cancellationToken),
1252            getConfigFileParsingDiagnostics: () => configFileParsingDiagnostics,
1253            getSyntacticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSyntacticDiagnostics(sourceFile, cancellationToken),
1254            getDeclarationDiagnostics: (sourceFile, cancellationToken) => getProgram().getDeclarationDiagnostics(sourceFile, cancellationToken),
1255            getSemanticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSemanticDiagnostics(sourceFile, cancellationToken),
1256            emit: (sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers) => getProgram().emit(sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers),
1257            emitBuildInfo: (writeFile, cancellationToken) => getProgram().emitBuildInfo(writeFile, cancellationToken),
1258            getAllDependencies: notImplemented,
1259            getCurrentDirectory: () => getProgram().getCurrentDirectory(),
1260            close: noop,
1261        };
1262
1263        function getProgram() {
1264            return Debug.checkDefined(state.program);
1265        }
1266    }
1267}
1268