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