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