• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import {
2    arrayFrom,
3    CancellationToken,
4    computeSignatureWithDiagnostics,
5    CustomTransformers,
6    Debug,
7    EmitOutput,
8    emptyArray,
9    ESMap,
10    ExportedModulesFromDeclarationEmit,
11    GetCanonicalFileName,
12    getDirectoryPath,
13    getSourceFileOfNode,
14    isDeclarationFileName,
15    isExternalOrCommonJsModule,
16    isGlobalScopeAugmentation,
17    isJsonSourceFile,
18    isModuleWithStringLiteralName,
19    isStringLiteral,
20    Iterator,
21    Map,
22    mapDefined,
23    mapDefinedIterator,
24    ModuleDeclaration,
25    ModuleKind,
26    outFile,
27    OutputFile,
28    Path,
29    Program,
30    ReadonlySet,
31    Set,
32    some,
33    SourceFile,
34    StringLiteralLike,
35    Symbol,
36    toPath,
37    TypeChecker,
38} from "./_namespaces/ts";
39
40/** @internal */
41export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean,
42    cancellationToken?: CancellationToken, customTransformers?: CustomTransformers, forceDtsEmit?: boolean): EmitOutput {
43    const outputFiles: OutputFile[] = [];
44    const { emitSkipped, diagnostics } = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers, forceDtsEmit);
45    return { outputFiles, emitSkipped, diagnostics };
46
47    function writeFile(fileName: string, text: string, writeByteOrderMark: boolean) {
48        outputFiles.push({ name: fileName, writeByteOrderMark, text });
49    }
50}
51/** @internal */
52export interface BuilderState {
53    /**
54     * Information of the file eg. its version, signature etc
55     */
56    fileInfos: ESMap<Path, BuilderState.FileInfo>;
57    /**
58     * Contains the map of ReferencedSet=Referenced files of the file if module emit is enabled
59     * Otherwise undefined
60     * Thus non undefined value indicates, module emit
61     */
62    readonly referencedMap?: BuilderState.ReadonlyManyToManyPathMap | undefined;
63    /**
64     * Contains the map of exported modules ReferencedSet=exported module files from the file if module emit is enabled
65     * Otherwise undefined
66     *
67     * This is equivalent to referencedMap, but for the emitted .d.ts file.
68     */
69    readonly exportedModulesMap?: BuilderState.ManyToManyPathMap | undefined;
70
71    /**
72     * true if file version is used as signature
73     * This helps in delaying the calculation of the d.ts hash as version for the file till reasonable time
74     */
75    useFileVersionAsSignature?: boolean;
76    /**
77     * Map of files that have already called update signature.
78     * That means hence forth these files are assumed to have
79     * no change in their signature for this version of the program
80     */
81    hasCalledUpdateShapeSignature?: Set<Path>;
82    /**
83     * Stores signatures before before the update till affected file is commited
84     */
85    oldSignatures?: ESMap<Path, string | false>;
86    /**
87     * Stores exportedModulesMap before the update till affected file is commited
88     */
89    oldExportedModulesMap?: ESMap<Path, ReadonlySet<Path> | false>;
90    /**
91     * Cache of all files excluding default library file for the current program
92     */
93    allFilesExcludingDefaultLibraryFile?: readonly SourceFile[];
94    /**
95     * Cache of all the file names
96     */
97    allFileNames?: readonly string[];
98}
99/** @internal */
100export namespace BuilderState {
101    /**
102     * Information about the source file: Its version and optional signature from last emit
103     */
104    export interface FileInfo {
105        readonly version: string;
106        signature: string | undefined;
107        affectsGlobalScope: true | undefined;
108        impliedFormat: SourceFile["impliedNodeFormat"];
109    }
110
111    export interface ReadonlyManyToManyPathMap {
112        getKeys(v: Path): ReadonlySet<Path> | undefined;
113        getValues(k: Path): ReadonlySet<Path> | undefined;
114        keys(): Iterator<Path>;
115    }
116
117    export interface ManyToManyPathMap extends ReadonlyManyToManyPathMap {
118        deleteKey(k: Path): boolean;
119        set(k: Path, v: ReadonlySet<Path>): void;
120    }
121
122    export function createManyToManyPathMap(): ManyToManyPathMap {
123        function create(forward: ESMap<Path, ReadonlySet<Path>>, reverse: ESMap<Path, Set<Path>>, deleted: Set<Path> | undefined): ManyToManyPathMap {
124            const map: ManyToManyPathMap = {
125                getKeys: v => reverse.get(v),
126                getValues: k => forward.get(k),
127                keys: () => forward.keys(),
128
129                deleteKey: k => {
130                    (deleted ||= new Set<Path>()).add(k);
131
132                    const set = forward.get(k);
133                    if (!set) {
134                        return false;
135                    }
136
137                    set.forEach(v => deleteFromMultimap(reverse, v, k));
138                    forward.delete(k);
139                    return true;
140                },
141                set: (k, vSet) => {
142                    deleted?.delete(k);
143
144                    const existingVSet = forward.get(k);
145                    forward.set(k, vSet);
146
147                    existingVSet?.forEach(v => {
148                        if (!vSet.has(v)) {
149                            deleteFromMultimap(reverse, v, k);
150                        }
151                    });
152
153                    vSet.forEach(v => {
154                        if (!existingVSet?.has(v)) {
155                            addToMultimap(reverse, v, k);
156                        }
157                    });
158
159                    return map;
160                },
161            };
162
163            return map;
164        }
165
166        return create(new Map<Path, Set<Path>>(), new Map<Path, Set<Path>>(), /*deleted*/ undefined);
167    }
168
169    function addToMultimap<K, V>(map: ESMap<K, Set<V>>, k: K, v: V): void {
170        let set = map.get(k);
171        if (!set) {
172            set = new Set<V>();
173            map.set(k, set);
174        }
175        set.add(v);
176    }
177
178    function deleteFromMultimap<K, V>(map: ESMap<K, Set<V>>, k: K, v: V): boolean {
179        const set = map.get(k);
180
181        if (set?.delete(v)) {
182            if (!set.size) {
183                map.delete(k);
184            }
185            return true;
186        }
187
188        return false;
189    }
190
191    /**
192     * Compute the hash to store the shape of the file
193     */
194    export type ComputeHash = ((data: string) => string) | undefined;
195
196    function getReferencedFilesFromImportedModuleSymbol(symbol: Symbol): Path[] {
197        return mapDefined(symbol.declarations, declaration => getSourceFileOfNode(declaration)?.resolvedPath);
198    }
199
200    /**
201     * Get the module source file and all augmenting files from the import name node from file
202     */
203    function getReferencedFilesFromImportLiteral(checker: TypeChecker, importName: StringLiteralLike): Path[] | undefined {
204        const symbol = checker.getSymbolAtLocation(importName);
205        return symbol && getReferencedFilesFromImportedModuleSymbol(symbol);
206    }
207
208    /**
209     * Gets the path to reference file from file name, it could be resolvedPath if present otherwise path
210     */
211    function getReferencedFileFromFileName(program: Program, fileName: string, sourceFileDirectory: Path, getCanonicalFileName: GetCanonicalFileName): Path {
212        return toPath(program.getProjectReferenceRedirect(fileName) || fileName, sourceFileDirectory, getCanonicalFileName);
213    }
214
215    /**
216     * Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true
217     */
218    function getReferencedFiles(program: Program, sourceFile: SourceFile, getCanonicalFileName: GetCanonicalFileName): Set<Path> | undefined {
219        let referencedFiles: Set<Path> | undefined;
220
221        // We need to use a set here since the code can contain the same import twice,
222        // but that will only be one dependency.
223        // To avoid invernal conversion, the key of the referencedFiles map must be of type Path
224        if (sourceFile.imports && sourceFile.imports.length > 0) {
225            const checker: TypeChecker = program.getTypeChecker();
226            for (const importName of sourceFile.imports) {
227                const declarationSourceFilePaths = getReferencedFilesFromImportLiteral(checker, importName);
228                declarationSourceFilePaths?.forEach(addReferencedFile);
229            }
230        }
231
232        const sourceFileDirectory = getDirectoryPath(sourceFile.resolvedPath);
233        // Handle triple slash references
234        if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) {
235            for (const referencedFile of sourceFile.referencedFiles) {
236                const referencedPath = getReferencedFileFromFileName(program, referencedFile.fileName, sourceFileDirectory, getCanonicalFileName);
237                addReferencedFile(referencedPath);
238            }
239        }
240
241        // Handle type reference directives
242        if (sourceFile.resolvedTypeReferenceDirectiveNames) {
243            sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => {
244                if (!resolvedTypeReferenceDirective) {
245                    return;
246                }
247
248                const fileName = resolvedTypeReferenceDirective.resolvedFileName!; // TODO: GH#18217
249                const typeFilePath = getReferencedFileFromFileName(program, fileName, sourceFileDirectory, getCanonicalFileName);
250                addReferencedFile(typeFilePath);
251            });
252        }
253
254        // Add module augmentation as references
255        if (sourceFile.moduleAugmentations.length) {
256            const checker = program.getTypeChecker();
257            for (const moduleName of sourceFile.moduleAugmentations) {
258                if (!isStringLiteral(moduleName)) continue;
259                const symbol = checker.getSymbolAtLocation(moduleName);
260                if (!symbol) continue;
261
262                // Add any file other than our own as reference
263                addReferenceFromAmbientModule(symbol);
264            }
265        }
266
267        // From ambient modules
268        for (const ambientModule of program.getTypeChecker().getAmbientModules()) {
269            if (ambientModule.declarations && ambientModule.declarations.length > 1) {
270                addReferenceFromAmbientModule(ambientModule);
271            }
272        }
273
274        return referencedFiles;
275
276        function addReferenceFromAmbientModule(symbol: Symbol) {
277            if (!symbol.declarations) {
278                return;
279            }
280            // Add any file other than our own as reference
281            for (const declaration of symbol.declarations) {
282                const declarationSourceFile = getSourceFileOfNode(declaration);
283                if (declarationSourceFile &&
284                    declarationSourceFile !== sourceFile) {
285                    addReferencedFile(declarationSourceFile.resolvedPath);
286                }
287            }
288        }
289
290        function addReferencedFile(referencedPath: Path) {
291            (referencedFiles || (referencedFiles = new Set())).add(referencedPath);
292        }
293    }
294
295    /**
296     * Returns true if oldState is reusable, that is the emitKind = module/non module has not changed
297     */
298    export function canReuseOldState(newReferencedMap: ReadonlyManyToManyPathMap | undefined, oldState: BuilderState | undefined) {
299        return oldState && !oldState.referencedMap === !newReferencedMap;
300    }
301
302    /**
303     * Creates the state of file references and signature for the new program from oldState if it is safe
304     */
305    export function create(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly<BuilderState>, disableUseFileVersionAsSignature?: boolean): BuilderState {
306        const fileInfos = new Map<Path, FileInfo>();
307        const referencedMap = newProgram.getCompilerOptions().module !== ModuleKind.None ? createManyToManyPathMap() : undefined;
308        const exportedModulesMap = referencedMap ? createManyToManyPathMap() : undefined;
309        const useOldState = canReuseOldState(referencedMap, oldState);
310
311        // Ensure source files have parent pointers set
312        newProgram.getTypeChecker();
313
314        // Create the reference map, and set the file infos
315        for (const sourceFile of newProgram.getSourceFiles()) {
316            const version = Debug.checkDefined(sourceFile.version, "Program intended to be used with Builder should have source files with versions set");
317            const oldUncommittedSignature = useOldState ? oldState!.oldSignatures?.get(sourceFile.resolvedPath) : undefined;
318            const signature = oldUncommittedSignature === undefined ?
319                useOldState ? oldState!.fileInfos.get(sourceFile.resolvedPath)?.signature : undefined :
320                oldUncommittedSignature || undefined;
321            if (referencedMap) {
322                const newReferences = getReferencedFiles(newProgram, sourceFile, getCanonicalFileName);
323                if (newReferences) {
324                    referencedMap.set(sourceFile.resolvedPath, newReferences);
325                }
326                // Copy old visible to outside files map
327                if (useOldState) {
328                    const oldUncommittedExportedModules = oldState!.oldExportedModulesMap?.get(sourceFile.resolvedPath);
329                    const exportedModules = oldUncommittedExportedModules === undefined ?
330                        oldState!.exportedModulesMap!.getValues(sourceFile.resolvedPath) :
331                        oldUncommittedExportedModules || undefined;
332                    if (exportedModules) {
333                        exportedModulesMap!.set(sourceFile.resolvedPath, exportedModules);
334                    }
335                }
336            }
337            fileInfos.set(sourceFile.resolvedPath, {
338                version,
339                signature,
340                affectsGlobalScope: isFileAffectingGlobalScope(sourceFile) || undefined,
341                impliedFormat: sourceFile.impliedNodeFormat
342            });
343        }
344
345        return {
346            fileInfos,
347            referencedMap,
348            exportedModulesMap,
349            useFileVersionAsSignature: !disableUseFileVersionAsSignature && !useOldState
350        };
351    }
352
353    /**
354     * Releases needed properties
355     */
356    export function releaseCache(state: BuilderState) {
357        state.allFilesExcludingDefaultLibraryFile = undefined;
358        state.allFileNames = undefined;
359    }
360
361    /**
362     * Gets the files affected by the path from the program
363     */
364    export function getFilesAffectedBy(
365        state: BuilderState,
366        programOfThisState: Program,
367        path: Path,
368        cancellationToken: CancellationToken | undefined,
369        computeHash: ComputeHash,
370        getCanonicalFileName: GetCanonicalFileName,
371    ): readonly SourceFile[] {
372        const result = getFilesAffectedByWithOldState(
373            state,
374            programOfThisState,
375            path,
376            cancellationToken,
377            computeHash,
378            getCanonicalFileName,
379        );
380        state.oldSignatures?.clear();
381        state.oldExportedModulesMap?.clear();
382        return result;
383    }
384
385    export function getFilesAffectedByWithOldState(
386        state: BuilderState,
387        programOfThisState: Program,
388        path: Path,
389        cancellationToken: CancellationToken | undefined,
390        computeHash: ComputeHash,
391        getCanonicalFileName: GetCanonicalFileName,
392    ): readonly SourceFile[] {
393        const sourceFile = programOfThisState.getSourceFileByPath(path);
394        if (!sourceFile) {
395            return emptyArray;
396        }
397
398        if (!updateShapeSignature(state, programOfThisState, sourceFile, cancellationToken, computeHash, getCanonicalFileName)) {
399            return [sourceFile];
400        }
401
402        return (state.referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit)(state, programOfThisState, sourceFile, cancellationToken, computeHash, getCanonicalFileName);
403    }
404
405    export function updateSignatureOfFile(state: BuilderState, signature: string | undefined, path: Path) {
406        state.fileInfos.get(path)!.signature = signature;
407        (state.hasCalledUpdateShapeSignature ||= new Set()).add(path);
408    }
409
410    /**
411     * Returns if the shape of the signature has changed since last emit
412     */
413    export function updateShapeSignature(
414        state: BuilderState,
415        programOfThisState: Program,
416        sourceFile: SourceFile,
417        cancellationToken: CancellationToken | undefined,
418        computeHash: ComputeHash,
419        getCanonicalFileName: GetCanonicalFileName,
420        useFileVersionAsSignature = state.useFileVersionAsSignature
421    ) {
422        // If we have cached the result for this file, that means hence forth we should assume file shape is uptodate
423        if (state.hasCalledUpdateShapeSignature?.has(sourceFile.resolvedPath)) return false;
424
425        const info = state.fileInfos.get(sourceFile.resolvedPath)!;
426        const prevSignature = info.signature;
427        let latestSignature: string | undefined;
428        if (!sourceFile.isDeclarationFile && !useFileVersionAsSignature) {
429            programOfThisState.emit(
430                sourceFile,
431                (fileName, text, _writeByteOrderMark, _onError, sourceFiles, data) => {
432                    Debug.assert(isDeclarationFileName(fileName), `File extension for signature expected to be dts: Got:: ${fileName}`);
433                    latestSignature = computeSignatureWithDiagnostics(
434                        sourceFile,
435                        text,
436                        computeHash,
437                        getCanonicalFileName,
438                        data,
439                    );
440                    if (latestSignature !== prevSignature) {
441                        updateExportedModules(state, sourceFile, sourceFiles![0].exportedModulesFromDeclarationEmit);
442                    }
443                },
444                cancellationToken,
445                /*emitOnlyDtsFiles*/ true,
446                /*customTransformers*/ undefined,
447                /*forceDtsEmit*/ true
448            );
449        }
450        // Default is to use file version as signature
451        if (latestSignature === undefined) {
452            latestSignature = sourceFile.version;
453            if (state.exportedModulesMap && latestSignature !== prevSignature) {
454                (state.oldExportedModulesMap ||= new Map()).set(sourceFile.resolvedPath, state.exportedModulesMap.getValues(sourceFile.resolvedPath) || false);
455                // All the references in this file are exported
456                const references = state.referencedMap ? state.referencedMap.getValues(sourceFile.resolvedPath) : undefined;
457                if (references) {
458                    state.exportedModulesMap.set(sourceFile.resolvedPath, references);
459                }
460                else {
461                    state.exportedModulesMap.deleteKey(sourceFile.resolvedPath);
462                }
463            }
464        }
465        (state.oldSignatures ||= new Map()).set(sourceFile.resolvedPath, prevSignature || false);
466        (state.hasCalledUpdateShapeSignature ||= new Set()).add(sourceFile.resolvedPath);
467        info.signature = latestSignature;
468        return latestSignature !== prevSignature;
469    }
470
471    /**
472     * Coverts the declaration emit result into exported modules map
473     */
474    export function updateExportedModules(state: BuilderState, sourceFile: SourceFile, exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined) {
475        if (!state.exportedModulesMap) return;
476        (state.oldExportedModulesMap ||= new Map()).set(sourceFile.resolvedPath, state.exportedModulesMap.getValues(sourceFile.resolvedPath) || false);
477        if (!exportedModulesFromDeclarationEmit) {
478            state.exportedModulesMap.deleteKey(sourceFile.resolvedPath);
479            return;
480        }
481
482        let exportedModules: Set<Path> | undefined;
483        exportedModulesFromDeclarationEmit.forEach(symbol => addExportedModule(getReferencedFilesFromImportedModuleSymbol(symbol)));
484        if (exportedModules) {
485            state.exportedModulesMap.set(sourceFile.resolvedPath, exportedModules);
486        }
487        else {
488            state.exportedModulesMap.deleteKey(sourceFile.resolvedPath);
489        }
490
491        function addExportedModule(exportedModulePaths: Path[] | undefined) {
492            if (exportedModulePaths?.length) {
493                if (!exportedModules) {
494                    exportedModules = new Set();
495                }
496                exportedModulePaths.forEach(path => exportedModules!.add(path));
497            }
498        }
499    }
500
501    /**
502     * Get all the dependencies of the sourceFile
503     */
504    export function getAllDependencies(state: BuilderState, programOfThisState: Program, sourceFile: SourceFile): readonly string[] {
505        const compilerOptions = programOfThisState.getCompilerOptions();
506        // With --out or --outFile all outputs go into single file, all files depend on each other
507        if (outFile(compilerOptions)) {
508            return getAllFileNames(state, programOfThisState);
509        }
510
511        // If this is non module emit, or its a global file, it depends on all the source files
512        if (!state.referencedMap || isFileAffectingGlobalScope(sourceFile)) {
513            return getAllFileNames(state, programOfThisState);
514        }
515
516        // Get the references, traversing deep from the referenceMap
517        const seenMap = new Set<Path>();
518        const queue = [sourceFile.resolvedPath];
519        while (queue.length) {
520            const path = queue.pop()!;
521            if (!seenMap.has(path)) {
522                seenMap.add(path);
523                const references = state.referencedMap.getValues(path);
524                if (references) {
525                    const iterator = references.keys();
526                    for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) {
527                        queue.push(iterResult.value);
528                    }
529                }
530            }
531        }
532
533        return arrayFrom(mapDefinedIterator(seenMap.keys(), path => programOfThisState.getSourceFileByPath(path)?.fileName ?? path));
534    }
535
536    /**
537     * Gets the names of all files from the program
538     */
539    function getAllFileNames(state: BuilderState, programOfThisState: Program): readonly string[] {
540        if (!state.allFileNames) {
541            const sourceFiles = programOfThisState.getSourceFiles();
542            state.allFileNames = sourceFiles === emptyArray ? emptyArray : sourceFiles.map(file => file.fileName);
543        }
544        return state.allFileNames;
545    }
546
547    /**
548     * Gets the files referenced by the the file path
549     */
550    export function getReferencedByPaths(state: Readonly<BuilderState>, referencedFilePath: Path) {
551        const keys = state.referencedMap!.getKeys(referencedFilePath);
552        return keys ? arrayFrom(keys.keys()) : [];
553    }
554
555    /**
556     * For script files that contains only ambient external modules, although they are not actually external module files,
557     * they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore,
558     * there are no point to rebuild all script files if these special files have changed. However, if any statement
559     * in the file is not ambient external module, we treat it as a regular script file.
560     */
561    function containsOnlyAmbientModules(sourceFile: SourceFile) {
562        for (const statement of sourceFile.statements) {
563            if (!isModuleWithStringLiteralName(statement)) {
564                return false;
565            }
566        }
567        return true;
568    }
569
570    /**
571     * Return true if file contains anything that augments to global scope we need to build them as if
572     * they are global files as well as module
573     */
574    function containsGlobalScopeAugmentation(sourceFile: SourceFile) {
575        return some(sourceFile.moduleAugmentations, augmentation => isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration));
576    }
577
578    /**
579     * Return true if the file will invalidate all files because it affectes global scope
580     */
581    function isFileAffectingGlobalScope(sourceFile: SourceFile) {
582        return containsGlobalScopeAugmentation(sourceFile) ||
583            !isExternalOrCommonJsModule(sourceFile) && !isJsonSourceFile(sourceFile) && !containsOnlyAmbientModules(sourceFile);
584    }
585
586    /**
587     * Gets all files of the program excluding the default library file
588     */
589    export function getAllFilesExcludingDefaultLibraryFile(state: BuilderState, programOfThisState: Program, firstSourceFile: SourceFile | undefined): readonly SourceFile[] {
590        // Use cached result
591        if (state.allFilesExcludingDefaultLibraryFile) {
592            return state.allFilesExcludingDefaultLibraryFile;
593        }
594
595        let result: SourceFile[] | undefined;
596        if (firstSourceFile) addSourceFile(firstSourceFile);
597        for (const sourceFile of programOfThisState.getSourceFiles()) {
598            if (sourceFile !== firstSourceFile) {
599                addSourceFile(sourceFile);
600            }
601        }
602        state.allFilesExcludingDefaultLibraryFile = result || emptyArray;
603        return state.allFilesExcludingDefaultLibraryFile;
604
605        function addSourceFile(sourceFile: SourceFile) {
606            if (!programOfThisState.isSourceFileDefaultLibrary(sourceFile)) {
607                (result || (result = [])).push(sourceFile);
608            }
609        }
610    }
611
612    /**
613     * When program emits non modular code, gets the files affected by the sourceFile whose shape has changed
614     */
615    function getFilesAffectedByUpdatedShapeWhenNonModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile) {
616        const compilerOptions = programOfThisState.getCompilerOptions();
617        // If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project,
618        // so returning the file itself is good enough.
619        if (compilerOptions && outFile(compilerOptions)) {
620            return [sourceFileWithUpdatedShape];
621        }
622        return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape);
623    }
624
625    /**
626     * When program emits modular code, gets the files affected by the sourceFile whose shape has changed
627     */
628    function getFilesAffectedByUpdatedShapeWhenModuleEmit(
629        state: BuilderState,
630        programOfThisState: Program,
631        sourceFileWithUpdatedShape: SourceFile,
632        cancellationToken: CancellationToken | undefined,
633        computeHash: ComputeHash,
634        getCanonicalFileName: GetCanonicalFileName,
635    ) {
636        if (isFileAffectingGlobalScope(sourceFileWithUpdatedShape)) {
637            return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape);
638        }
639
640        const compilerOptions = programOfThisState.getCompilerOptions();
641        if (compilerOptions && (compilerOptions.isolatedModules || outFile(compilerOptions))) {
642            return [sourceFileWithUpdatedShape];
643        }
644
645        // Now we need to if each file in the referencedBy list has a shape change as well.
646        // Because if so, its own referencedBy files need to be saved as well to make the
647        // emitting result consistent with files on disk.
648        const seenFileNamesMap = new Map<Path, SourceFile>();
649
650        // Start with the paths this file was referenced by
651        seenFileNamesMap.set(sourceFileWithUpdatedShape.resolvedPath, sourceFileWithUpdatedShape);
652        const queue = getReferencedByPaths(state, sourceFileWithUpdatedShape.resolvedPath);
653        while (queue.length > 0) {
654            const currentPath = queue.pop()!;
655            if (!seenFileNamesMap.has(currentPath)) {
656                const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath)!;
657                seenFileNamesMap.set(currentPath, currentSourceFile);
658                if (currentSourceFile && updateShapeSignature(state, programOfThisState, currentSourceFile, cancellationToken, computeHash, getCanonicalFileName)) {
659                    queue.push(...getReferencedByPaths(state, currentSourceFile.resolvedPath));
660                }
661            }
662        }
663
664        // Return array of values that needs emit
665        return arrayFrom(mapDefinedIterator(seenFileNamesMap.values(), value => value));
666    }
667}
668