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