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