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