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 ReusableBuilderState { 24 /** 25 * Cache of bind and check diagnostics for files with their Path being the key 26 */ 27 semanticDiagnosticsPerFile?: ReadonlyESMap<Path, readonly ReusableDiagnostic[] | readonly Diagnostic[]> | undefined; 28 /** 29 * The map has key by source file's path that has been changed 30 */ 31 changedFilesSet?: ReadonlySet<Path>; 32 /** 33 * Set of affected files being iterated 34 */ 35 affectedFiles?: readonly SourceFile[] | undefined; 36 /** 37 * Current changed file for iterating over affected files 38 */ 39 currentChangedFilePath?: Path | undefined; 40 /** 41 * Map of file signatures, with key being file path, calculated while getting current changed file's affected files 42 * These will be committed whenever the iteration through affected files of current changed file is complete 43 */ 44 currentAffectedFilesSignatures?: ReadonlyESMap<Path, string> | undefined; 45 /** 46 * Newly computed visible to outside referencedSet 47 */ 48 currentAffectedFilesExportedModulesMap?: Readonly<BuilderState.ComputingExportedModulesMap> | undefined; 49 /** 50 * True if the semantic diagnostics were copied from the old state 51 */ 52 semanticDiagnosticsFromOldState?: Set<Path>; 53 /** 54 * program corresponding to this state 55 */ 56 program?: Program | undefined; 57 /** 58 * compilerOptions for the program 59 */ 60 compilerOptions: CompilerOptions; 61 /** 62 * Files pending to be emitted 63 */ 64 affectedFilesPendingEmit?: readonly Path[] | undefined; 65 /** 66 * Files pending to be emitted kind. 67 */ 68 affectedFilesPendingEmitKind?: ReadonlyESMap<Path, BuilderFileEmit> | undefined; 69 /** 70 * Current index to retrieve pending affected file 71 */ 72 affectedFilesPendingEmitIndex?: number | undefined; 73 /* 74 * true if semantic diagnostics are ReusableDiagnostic instead of Diagnostic 75 */ 76 hasReusableDiagnostic?: true; 77 } 78 79 export const enum BuilderFileEmit { 80 DtsOnly, 81 Full 82 } 83 84 /** 85 * State to store the changed files, affected files and cache semantic diagnostics 86 */ 87 // TODO: GH#18217 Properties of this interface are frequently asserted to be defined. 88 export interface BuilderProgramState extends BuilderState { 89 /** 90 * Cache of bind and check diagnostics for files with their Path being the key 91 */ 92 semanticDiagnosticsPerFile: ESMap<Path, readonly Diagnostic[]> | undefined; 93 /** 94 * The map has key by source file's path that has been changed 95 */ 96 changedFilesSet: Set<Path>; 97 /** 98 * Set of affected files being iterated 99 */ 100 affectedFiles: readonly SourceFile[] | undefined; 101 /** 102 * Current index to retrieve affected file from 103 */ 104 affectedFilesIndex: number | undefined; 105 /** 106 * Current changed file for iterating over affected files 107 */ 108 currentChangedFilePath: Path | undefined; 109 /** 110 * Map of file signatures, with key being file path, calculated while getting current changed file's affected files 111 * These will be committed whenever the iteration through affected files of current changed file is complete 112 */ 113 currentAffectedFilesSignatures: ESMap<Path, string> | undefined; 114 /** 115 * Newly computed visible to outside referencedSet 116 */ 117 currentAffectedFilesExportedModulesMap: BuilderState.ComputingExportedModulesMap | undefined; 118 /** 119 * Already seen affected files 120 */ 121 seenAffectedFiles: Set<Path> | undefined; 122 /** 123 * whether this program has cleaned semantic diagnostics cache for lib files 124 */ 125 cleanedDiagnosticsOfLibFiles?: boolean; 126 /** 127 * True if the semantic diagnostics were copied from the old state 128 */ 129 semanticDiagnosticsFromOldState?: Set<Path>; 130 /** 131 * program corresponding to this state 132 */ 133 program: Program | undefined; 134 /** 135 * compilerOptions for the program 136 */ 137 compilerOptions: CompilerOptions; 138 /** 139 * Files pending to be emitted 140 */ 141 affectedFilesPendingEmit: Path[] | undefined; 142 /** 143 * Files pending to be emitted kind. 144 */ 145 affectedFilesPendingEmitKind: ESMap<Path, BuilderFileEmit> | undefined; 146 /** 147 * Current index to retrieve pending affected file 148 */ 149 affectedFilesPendingEmitIndex: number | undefined; 150 /** 151 * true if build info is emitted 152 */ 153 buildInfoEmitPending: boolean; 154 /** 155 * Already seen emitted files 156 */ 157 seenEmittedFiles: ESMap<Path, BuilderFileEmit> | undefined; 158 /** 159 * true if program has been emitted 160 */ 161 programEmitComplete?: true; 162 } 163 164 function hasSameKeys(map1: ReadonlyCollection<string> | undefined, map2: ReadonlyCollection<string> | undefined): boolean { 165 // Has same size and every key is present in both maps 166 return map1 === map2 || map1 !== undefined && map2 !== undefined && map1.size === map2.size && !forEachKey(map1, key => !map2.has(key)); 167 } 168 169 /** 170 * Create the state so that we can iterate on changedFiles/affected files 171 */ 172 function createBuilderProgramState(newProgram: Program, getCanonicalFileName: GetCanonicalFileName, oldState?: Readonly<ReusableBuilderProgramState>): BuilderProgramState { 173 const state = BuilderState.create(newProgram, getCanonicalFileName, oldState) as BuilderProgramState; 174 state.program = newProgram; 175 const compilerOptions = newProgram.getCompilerOptions(); 176 state.compilerOptions = compilerOptions; 177 // With --out or --outFile, any change affects all semantic diagnostics so no need to cache them 178 if (!outFile(compilerOptions)) { 179 state.semanticDiagnosticsPerFile = new Map(); 180 } 181 state.changedFilesSet = new Set(); 182 183 const useOldState = BuilderState.canReuseOldState(state.referencedMap, oldState); 184 const oldCompilerOptions = useOldState ? oldState!.compilerOptions : undefined; 185 const canCopySemanticDiagnostics = useOldState && oldState!.semanticDiagnosticsPerFile && !!state.semanticDiagnosticsPerFile && 186 !compilerOptionsAffectSemanticDiagnostics(compilerOptions, oldCompilerOptions!); 187 if (useOldState) { 188 // Verify the sanity of old state 189 if (!oldState!.currentChangedFilePath) { 190 const affectedSignatures = oldState!.currentAffectedFilesSignatures; 191 Debug.assert(!oldState!.affectedFiles && (!affectedSignatures || !affectedSignatures.size), "Cannot reuse if only few affected files of currentChangedFile were iterated"); 192 } 193 const changedFilesSet = oldState!.changedFilesSet; 194 if (canCopySemanticDiagnostics) { 195 Debug.assert(!changedFilesSet || !forEachKey(changedFilesSet, path => oldState!.semanticDiagnosticsPerFile!.has(path)), "Semantic diagnostics shouldnt be available for changed files"); 196 } 197 198 // Copy old state's changed files set 199 changedFilesSet?.forEach(value => state.changedFilesSet.add(value)); 200 if (!outFile(compilerOptions) && oldState!.affectedFilesPendingEmit) { 201 state.affectedFilesPendingEmit = oldState!.affectedFilesPendingEmit.slice(); 202 state.affectedFilesPendingEmitKind = oldState!.affectedFilesPendingEmitKind && new Map(oldState!.affectedFilesPendingEmitKind); 203 state.affectedFilesPendingEmitIndex = oldState!.affectedFilesPendingEmitIndex; 204 state.seenAffectedFiles = new Set(); 205 } 206 } 207 208 // Update changed files and copy semantic diagnostics if we can 209 const referencedMap = state.referencedMap; 210 const oldReferencedMap = useOldState ? oldState!.referencedMap : undefined; 211 const copyDeclarationFileDiagnostics = canCopySemanticDiagnostics && !compilerOptions.skipLibCheck === !oldCompilerOptions!.skipLibCheck; 212 const copyLibFileDiagnostics = copyDeclarationFileDiagnostics && !compilerOptions.skipDefaultLibCheck === !oldCompilerOptions!.skipDefaultLibCheck; 213 state.fileInfos.forEach((info, sourceFilePath) => { 214 let oldInfo: Readonly<BuilderState.FileInfo> | undefined; 215 let newReferences: BuilderState.ReferencedSet | undefined; 216 217 // if not using old state, every file is changed 218 if (!useOldState || 219 // File wasn't present in old state 220 !(oldInfo = oldState!.fileInfos.get(sourceFilePath)) || 221 // versions dont match 222 oldInfo.version !== info.version || 223 // Referenced files changed 224 !hasSameKeys(newReferences = referencedMap && referencedMap.get(sourceFilePath), oldReferencedMap && oldReferencedMap.get(sourceFilePath)) || 225 // Referenced file was deleted in the new program 226 newReferences && forEachKey(newReferences, path => !state.fileInfos.has(path) && oldState!.fileInfos.has(path))) { 227 // Register file as changed file and do not copy semantic diagnostics, since all changed files need to be re-evaluated 228 state.changedFilesSet.add(sourceFilePath); 229 } 230 else if (canCopySemanticDiagnostics) { 231 const sourceFile = newProgram.getSourceFileByPath(sourceFilePath)!; 232 233 if (sourceFile.isDeclarationFile && !copyDeclarationFileDiagnostics) { return; } 234 if (sourceFile.hasNoDefaultLib && !copyLibFileDiagnostics) { return; } 235 236 // Unchanged file copy diagnostics 237 const diagnostics = oldState!.semanticDiagnosticsPerFile!.get(sourceFilePath); 238 if (diagnostics) { 239 state.semanticDiagnosticsPerFile!.set(sourceFilePath, oldState!.hasReusableDiagnostic ? convertToDiagnostics(diagnostics as readonly ReusableDiagnostic[], newProgram, getCanonicalFileName) : diagnostics as readonly Diagnostic[]); 240 if (!state.semanticDiagnosticsFromOldState) { 241 state.semanticDiagnosticsFromOldState = new Set(); 242 } 243 state.semanticDiagnosticsFromOldState.add(sourceFilePath); 244 } 245 } 246 }); 247 248 // If the global file is removed, add all files as changed 249 if (useOldState && forEachEntry(oldState!.fileInfos, (info, sourceFilePath) => info.affectsGlobalScope && !state.fileInfos.has(sourceFilePath))) { 250 BuilderState.getAllFilesExcludingDefaultLibraryFile(state, newProgram, /*firstSourceFile*/ undefined) 251 .forEach(file => state.changedFilesSet.add(file.resolvedPath)); 252 } 253 else if (oldCompilerOptions && !outFile(compilerOptions) && compilerOptionsAffectEmit(compilerOptions, oldCompilerOptions)) { 254 // Add all files to affectedFilesPendingEmit since emit changed 255 newProgram.getSourceFiles().forEach(f => addToAffectedFilesPendingEmit(state, f.resolvedPath, BuilderFileEmit.Full)); 256 Debug.assert(!state.seenAffectedFiles || !state.seenAffectedFiles.size); 257 state.seenAffectedFiles = state.seenAffectedFiles || new Set(); 258 } 259 260 state.buildInfoEmitPending = !!state.changedFilesSet.size; 261 return state; 262 } 263 264 function convertToDiagnostics(diagnostics: readonly ReusableDiagnostic[], newProgram: Program, getCanonicalFileName: GetCanonicalFileName): readonly Diagnostic[] { 265 if (!diagnostics.length) return emptyArray; 266 const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(newProgram.getCompilerOptions())!, newProgram.getCurrentDirectory())); 267 return diagnostics.map(diagnostic => { 268 const result: Diagnostic = convertToDiagnosticRelatedInformation(diagnostic, newProgram, toPath); 269 result.reportsUnnecessary = diagnostic.reportsUnnecessary; 270 result.reportsDeprecated = diagnostic.reportDeprecated; 271 result.source = diagnostic.source; 272 result.skippedOn = diagnostic.skippedOn; 273 const { relatedInformation } = diagnostic; 274 result.relatedInformation = relatedInformation ? 275 relatedInformation.length ? 276 relatedInformation.map(r => convertToDiagnosticRelatedInformation(r, newProgram, toPath)) : 277 [] : 278 undefined; 279 return result; 280 }); 281 282 function toPath(path: string) { 283 return ts.toPath(path, buildInfoDirectory, getCanonicalFileName); 284 } 285 } 286 287 function convertToDiagnosticRelatedInformation(diagnostic: ReusableDiagnosticRelatedInformation, newProgram: Program, toPath: (path: string) => Path): DiagnosticRelatedInformation { 288 const { file } = diagnostic; 289 return { 290 ...diagnostic, 291 file: file ? newProgram.getSourceFileByPath(toPath(file)) : undefined 292 }; 293 } 294 295 /** 296 * Releases program and other related not needed properties 297 */ 298 function releaseCache(state: BuilderProgramState) { 299 BuilderState.releaseCache(state); 300 state.program = undefined; 301 } 302 303 /** 304 * Creates a clone of the state 305 */ 306 function cloneBuilderProgramState(state: Readonly<BuilderProgramState>): BuilderProgramState { 307 const newState = BuilderState.clone(state) as BuilderProgramState; 308 newState.semanticDiagnosticsPerFile = state.semanticDiagnosticsPerFile && new Map(state.semanticDiagnosticsPerFile); 309 newState.changedFilesSet = new Set(state.changedFilesSet); 310 newState.affectedFiles = state.affectedFiles; 311 newState.affectedFilesIndex = state.affectedFilesIndex; 312 newState.currentChangedFilePath = state.currentChangedFilePath; 313 newState.currentAffectedFilesSignatures = state.currentAffectedFilesSignatures && new Map(state.currentAffectedFilesSignatures); 314 newState.currentAffectedFilesExportedModulesMap = state.currentAffectedFilesExportedModulesMap && new Map(state.currentAffectedFilesExportedModulesMap); 315 newState.seenAffectedFiles = state.seenAffectedFiles && new Set(state.seenAffectedFiles); 316 newState.cleanedDiagnosticsOfLibFiles = state.cleanedDiagnosticsOfLibFiles; 317 newState.semanticDiagnosticsFromOldState = state.semanticDiagnosticsFromOldState && new Set(state.semanticDiagnosticsFromOldState); 318 newState.program = state.program; 319 newState.compilerOptions = state.compilerOptions; 320 newState.affectedFilesPendingEmit = state.affectedFilesPendingEmit && state.affectedFilesPendingEmit.slice(); 321 newState.affectedFilesPendingEmitKind = state.affectedFilesPendingEmitKind && new Map(state.affectedFilesPendingEmitKind); 322 newState.affectedFilesPendingEmitIndex = state.affectedFilesPendingEmitIndex; 323 newState.seenEmittedFiles = state.seenEmittedFiles && new Map(state.seenEmittedFiles); 324 newState.programEmitComplete = state.programEmitComplete; 325 return newState; 326 } 327 328 /** 329 * Verifies that source file is ok to be used in calls that arent handled by next 330 */ 331 function assertSourceFileOkWithoutNextAffectedCall(state: BuilderProgramState, sourceFile: SourceFile | undefined) { 332 Debug.assert(!sourceFile || !state.affectedFiles || state.affectedFiles[state.affectedFilesIndex! - 1] !== sourceFile || !state.semanticDiagnosticsPerFile!.has(sourceFile.resolvedPath)); 333 } 334 335 /** 336 * This function returns the next affected file to be processed. 337 * Note that until doneAffected is called it would keep reporting same result 338 * This is to allow the callers to be able to actually remove affected file only when the operation is complete 339 * eg. if during diagnostics check cancellation token ends up cancelling the request, the affected file should be retained 340 */ 341 function getNextAffectedFile(state: BuilderProgramState, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash): SourceFile | Program | undefined { 342 while (true) { 343 const { affectedFiles } = state; 344 if (affectedFiles) { 345 const seenAffectedFiles = state.seenAffectedFiles!; 346 let affectedFilesIndex = state.affectedFilesIndex!; // TODO: GH#18217 347 while (affectedFilesIndex < affectedFiles.length) { 348 const affectedFile = affectedFiles[affectedFilesIndex]; 349 if (!seenAffectedFiles.has(affectedFile.resolvedPath)) { 350 // Set the next affected file as seen and remove the cached semantic diagnostics 351 state.affectedFilesIndex = affectedFilesIndex; 352 handleDtsMayChangeOfAffectedFile(state, affectedFile, cancellationToken, computeHash); 353 return affectedFile; 354 } 355 affectedFilesIndex++; 356 } 357 358 // Remove the changed file from the change set 359 state.changedFilesSet.delete(state.currentChangedFilePath!); 360 state.currentChangedFilePath = undefined; 361 // Commit the changes in file signature 362 BuilderState.updateSignaturesFromCache(state, state.currentAffectedFilesSignatures!); 363 state.currentAffectedFilesSignatures!.clear(); 364 BuilderState.updateExportedFilesMapFromCache(state, state.currentAffectedFilesExportedModulesMap); 365 state.affectedFiles = undefined; 366 } 367 368 // Get next changed file 369 const nextKey = state.changedFilesSet.keys().next(); 370 if (nextKey.done) { 371 // Done 372 return undefined; 373 } 374 375 // With --out or --outFile all outputs go into single file 376 // so operations are performed directly on program, return program 377 const program = Debug.checkDefined(state.program); 378 const compilerOptions = program.getCompilerOptions(); 379 if (outFile(compilerOptions)) { 380 Debug.assert(!state.semanticDiagnosticsPerFile); 381 return program; 382 } 383 384 // Get next batch of affected files 385 if (!state.currentAffectedFilesSignatures) state.currentAffectedFilesSignatures = new Map(); 386 if (state.exportedModulesMap) { 387 if (!state.currentAffectedFilesExportedModulesMap) state.currentAffectedFilesExportedModulesMap = new Map(); 388 } 389 state.affectedFiles = BuilderState.getFilesAffectedBy(state, program, nextKey.value, cancellationToken, computeHash, state.currentAffectedFilesSignatures, state.currentAffectedFilesExportedModulesMap); 390 state.currentChangedFilePath = nextKey.value; 391 state.affectedFilesIndex = 0; 392 if (!state.seenAffectedFiles) state.seenAffectedFiles = new Set(); 393 } 394 } 395 396 /** 397 * Returns next file to be emitted from files that retrieved semantic diagnostics but did not emit yet 398 */ 399 function getNextAffectedFilePendingEmit(state: BuilderProgramState) { 400 const { affectedFilesPendingEmit } = state; 401 if (affectedFilesPendingEmit) { 402 const seenEmittedFiles = (state.seenEmittedFiles || (state.seenEmittedFiles = new Map())); 403 for (let i = state.affectedFilesPendingEmitIndex!; i < affectedFilesPendingEmit.length; i++) { 404 const affectedFile = Debug.checkDefined(state.program).getSourceFileByPath(affectedFilesPendingEmit[i]); 405 if (affectedFile) { 406 const seenKind = seenEmittedFiles.get(affectedFile.resolvedPath); 407 const emitKind = Debug.checkDefined(Debug.checkDefined(state.affectedFilesPendingEmitKind).get(affectedFile.resolvedPath)); 408 if (seenKind === undefined || seenKind < emitKind) { 409 // emit this file 410 state.affectedFilesPendingEmitIndex = i; 411 return { affectedFile, emitKind }; 412 } 413 } 414 } 415 state.affectedFilesPendingEmit = undefined; 416 state.affectedFilesPendingEmitKind = undefined; 417 state.affectedFilesPendingEmitIndex = undefined; 418 } 419 return undefined; 420 } 421 422 /** 423 * Handles semantic diagnostics and dts emit for affectedFile and files, that are referencing modules that export entities from affected file 424 * This is because even though js emit doesnt change, dts emit / type used can change resulting in need for dts emit and js change 425 */ 426 function handleDtsMayChangeOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash) { 427 removeSemanticDiagnosticsOf(state, affectedFile.resolvedPath); 428 429 // If affected files is everything except default library, then nothing more to do 430 if (state.allFilesExcludingDefaultLibraryFile === state.affectedFiles) { 431 if (!state.cleanedDiagnosticsOfLibFiles) { 432 state.cleanedDiagnosticsOfLibFiles = true; 433 const program = Debug.checkDefined(state.program); 434 const options = program.getCompilerOptions(); 435 forEach(program.getSourceFiles(), f => 436 program.isSourceFileDefaultLibrary(f) && 437 !skipTypeChecking(f, options, program) && 438 removeSemanticDiagnosticsOf(state, f.resolvedPath) 439 ); 440 } 441 return; 442 } 443 444 if (!state.compilerOptions.assumeChangesOnlyAffectDirectDependencies) { 445 forEachReferencingModulesOfExportOfAffectedFile(state, affectedFile, (state, path) => handleDtsMayChangeOf(state, path, cancellationToken, computeHash)); 446 } 447 } 448 449 /** 450 * Handle the dts may change, so they need to be added to pending emit if dts emit is enabled, 451 * Also we need to make sure signature is updated for these files 452 */ 453 function handleDtsMayChangeOf(state: BuilderProgramState, path: Path, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash) { 454 removeSemanticDiagnosticsOf(state, path); 455 456 if (!state.changedFilesSet.has(path)) { 457 const program = Debug.checkDefined(state.program); 458 const sourceFile = program.getSourceFileByPath(path); 459 if (sourceFile) { 460 // Even though the js emit doesnt change and we are already handling dts emit and semantic diagnostics 461 // we need to update the signature to reflect correctness of the signature(which is output d.ts emit) of this file 462 // This ensures that we dont later during incremental builds considering wrong signature. 463 // Eg where this also is needed to ensure that .tsbuildinfo generated by incremental build should be same as if it was first fresh build 464 BuilderState.updateShapeSignature( 465 state, 466 program, 467 sourceFile, 468 Debug.checkDefined(state.currentAffectedFilesSignatures), 469 cancellationToken, 470 computeHash, 471 state.currentAffectedFilesExportedModulesMap 472 ); 473 // If not dts emit, nothing more to do 474 if (getEmitDeclarations(state.compilerOptions)) { 475 addToAffectedFilesPendingEmit(state, path, BuilderFileEmit.DtsOnly); 476 } 477 } 478 } 479 480 return false; 481 } 482 483 /** 484 * Removes semantic diagnostics for path and 485 * returns true if there are no more semantic diagnostics from the old state 486 */ 487 function removeSemanticDiagnosticsOf(state: BuilderProgramState, path: Path) { 488 if (!state.semanticDiagnosticsFromOldState) { 489 return true; 490 } 491 state.semanticDiagnosticsFromOldState.delete(path); 492 state.semanticDiagnosticsPerFile!.delete(path); 493 return !state.semanticDiagnosticsFromOldState.size; 494 } 495 496 function isChangedSignature(state: BuilderProgramState, path: Path) { 497 const newSignature = Debug.checkDefined(state.currentAffectedFilesSignatures).get(path); 498 const oldSignature = Debug.checkDefined(state.fileInfos.get(path)).signature; 499 return newSignature !== oldSignature; 500 } 501 502 /** 503 * Iterate on referencing modules that export entities from affected file 504 */ 505 function forEachReferencingModulesOfExportOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, fn: (state: BuilderProgramState, filePath: Path) => boolean) { 506 // If there was change in signature (dts output) for the changed file, 507 // then only we need to handle pending file emit 508 if (!state.exportedModulesMap || !state.changedFilesSet.has(affectedFile.resolvedPath)) { 509 return; 510 } 511 512 if (!isChangedSignature(state, affectedFile.resolvedPath)) return; 513 514 // Since isolated modules dont change js files, files affected by change in signature is itself 515 // But we need to cleanup semantic diagnostics and queue dts emit for affected files 516 if (state.compilerOptions.isolatedModules) { 517 const seenFileNamesMap = new Map<Path, true>(); 518 seenFileNamesMap.set(affectedFile.resolvedPath, true); 519 const queue = BuilderState.getReferencedByPaths(state, affectedFile.resolvedPath); 520 while (queue.length > 0) { 521 const currentPath = queue.pop()!; 522 if (!seenFileNamesMap.has(currentPath)) { 523 seenFileNamesMap.set(currentPath, true); 524 const result = fn(state, currentPath); 525 if (result && isChangedSignature(state, currentPath)) { 526 const currentSourceFile = Debug.checkDefined(state.program).getSourceFileByPath(currentPath)!; 527 queue.push(...BuilderState.getReferencedByPaths(state, currentSourceFile.resolvedPath)); 528 } 529 } 530 } 531 } 532 533 Debug.assert(!!state.currentAffectedFilesExportedModulesMap); 534 const seenFileAndExportsOfFile = new Set<string>(); 535 // Go through exported modules from cache first 536 // If exported modules has path, all files referencing file exported from are affected 537 if (forEachEntry(state.currentAffectedFilesExportedModulesMap, (exportedModules, exportedFromPath) => 538 exportedModules && 539 exportedModules.has(affectedFile.resolvedPath) && 540 forEachFilesReferencingPath(state, exportedFromPath, seenFileAndExportsOfFile, fn) 541 )) { 542 return; 543 } 544 545 // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected 546 forEachEntry(state.exportedModulesMap, (exportedModules, exportedFromPath) => 547 !state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it 548 exportedModules.has(affectedFile.resolvedPath) && 549 forEachFilesReferencingPath(state, exportedFromPath, seenFileAndExportsOfFile, fn) 550 ); 551 } 552 553 /** 554 * Iterate on files referencing referencedPath 555 */ 556 function forEachFilesReferencingPath(state: BuilderProgramState, referencedPath: Path, seenFileAndExportsOfFile: Set<string>, fn: (state: BuilderProgramState, filePath: Path) => boolean) { 557 return forEachEntry(state.referencedMap!, (referencesInFile, filePath) => 558 referencesInFile.has(referencedPath) && forEachFileAndExportsOfFile(state, filePath, seenFileAndExportsOfFile, fn) 559 ); 560 } 561 562 /** 563 * fn on file and iterate on anything that exports this file 564 */ 565 function forEachFileAndExportsOfFile(state: BuilderProgramState, filePath: Path, seenFileAndExportsOfFile: Set<string>, fn: (state: BuilderProgramState, filePath: Path) => boolean): boolean { 566 if (!tryAddToSet(seenFileAndExportsOfFile, filePath)) { 567 return false; 568 } 569 570 if (fn(state, filePath)) { 571 // If there are no more diagnostics from old cache, done 572 return true; 573 } 574 575 Debug.assert(!!state.currentAffectedFilesExportedModulesMap); 576 // Go through exported modules from cache first 577 // If exported modules has path, all files referencing file exported from are affected 578 if (forEachEntry(state.currentAffectedFilesExportedModulesMap, (exportedModules, exportedFromPath) => 579 exportedModules && 580 exportedModules.has(filePath) && 581 forEachFileAndExportsOfFile(state, exportedFromPath, seenFileAndExportsOfFile, fn) 582 )) { 583 return true; 584 } 585 586 // If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected 587 if (forEachEntry(state.exportedModulesMap!, (exportedModules, exportedFromPath) => 588 !state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it 589 exportedModules.has(filePath) && 590 forEachFileAndExportsOfFile(state, exportedFromPath, seenFileAndExportsOfFile, fn) 591 )) { 592 return true; 593 } 594 595 // Remove diagnostics of files that import this file (without going to exports of referencing files) 596 return !!forEachEntry(state.referencedMap!, (referencesInFile, referencingFilePath) => 597 referencesInFile.has(filePath) && 598 !seenFileAndExportsOfFile.has(referencingFilePath) && // Not already removed diagnostic file 599 fn(state, referencingFilePath) // Dont add to seen since this is not yet done with the export removal 600 ); 601 } 602 603 604 /** 605 * This is called after completing operation on the next affected file. 606 * The operations here are postponed to ensure that cancellation during the iteration is handled correctly 607 */ 608 function doneWithAffectedFile( 609 state: BuilderProgramState, 610 affected: SourceFile | Program, 611 emitKind?: BuilderFileEmit, 612 isPendingEmit?: boolean, 613 isBuildInfoEmit?: boolean 614 ) { 615 if (isBuildInfoEmit) { 616 state.buildInfoEmitPending = false; 617 } 618 else if (affected === state.program) { 619 state.changedFilesSet.clear(); 620 state.programEmitComplete = true; 621 } 622 else { 623 state.seenAffectedFiles!.add((affected as SourceFile).resolvedPath); 624 if (emitKind !== undefined) { 625 (state.seenEmittedFiles || (state.seenEmittedFiles = new Map())).set((affected as SourceFile).resolvedPath, emitKind); 626 } 627 if (isPendingEmit) { 628 state.affectedFilesPendingEmitIndex!++; 629 state.buildInfoEmitPending = true; 630 } 631 else { 632 state.affectedFilesIndex!++; 633 } 634 } 635 } 636 637 /** 638 * Returns the result with affected file 639 */ 640 function toAffectedFileResult<T>(state: BuilderProgramState, result: T, affected: SourceFile | Program): AffectedFileResult<T> { 641 doneWithAffectedFile(state, affected); 642 return { result, affected }; 643 } 644 645 /** 646 * Returns the result with affected file 647 */ 648 function toAffectedFileEmitResult( 649 state: BuilderProgramState, 650 result: EmitResult, 651 affected: SourceFile | Program, 652 emitKind: BuilderFileEmit, 653 isPendingEmit?: boolean, 654 isBuildInfoEmit?: boolean 655 ): AffectedFileResult<EmitResult> { 656 doneWithAffectedFile(state, affected, emitKind, isPendingEmit, isBuildInfoEmit); 657 return { result, affected }; 658 } 659 660 /** 661 * Gets semantic diagnostics for the file which are 662 * bindAndCheckDiagnostics (from cache) and program diagnostics 663 */ 664 function getSemanticDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { 665 return concatenate( 666 getBinderAndCheckerDiagnosticsOfFile(state, sourceFile, cancellationToken), 667 Debug.checkDefined(state.program).getProgramDiagnostics(sourceFile) 668 ); 669 } 670 671 /** 672 * Gets the binder and checker diagnostics either from cache if present, or otherwise from program and caches it 673 * 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 674 */ 675 function getBinderAndCheckerDiagnosticsOfFile(state: BuilderProgramState, sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { 676 const path = sourceFile.resolvedPath; 677 if (state.semanticDiagnosticsPerFile) { 678 const cachedDiagnostics = state.semanticDiagnosticsPerFile.get(path); 679 // Report the bind and check diagnostics from the cache if we already have those diagnostics present 680 if (cachedDiagnostics) { 681 return filterSemanticDiagnotics(cachedDiagnostics, state.compilerOptions); 682 } 683 } 684 685 // Diagnostics werent cached, get them from program, and cache the result 686 const diagnostics = Debug.checkDefined(state.program).getBindAndCheckDiagnostics(sourceFile, cancellationToken); 687 if (state.semanticDiagnosticsPerFile) { 688 state.semanticDiagnosticsPerFile.set(path, diagnostics); 689 } 690 return filterSemanticDiagnotics(diagnostics, state.compilerOptions); 691 } 692 693 export type ProgramBuildInfoDiagnostic = string | [string, readonly ReusableDiagnostic[]]; 694 export type ProgramBuilderInfoFilePendingEmit = [string, BuilderFileEmit]; 695 export interface ProgramBuildInfo { 696 fileInfos: MapLike<BuilderState.FileInfo>; 697 options: CompilerOptions; 698 referencedMap?: MapLike<string[]>; 699 exportedModulesMap?: MapLike<string[]>; 700 semanticDiagnosticsPerFile?: ProgramBuildInfoDiagnostic[]; 701 affectedFilesPendingEmit?: ProgramBuilderInfoFilePendingEmit[]; 702 } 703 704 /** 705 * Gets the program information to be emitted in buildInfo so that we can use it to create new program 706 */ 707 function getProgramBuildInfo(state: Readonly<ReusableBuilderProgramState>, getCanonicalFileName: GetCanonicalFileName): ProgramBuildInfo | undefined { 708 if (outFile(state.compilerOptions)) return undefined; 709 const currentDirectory = Debug.checkDefined(state.program).getCurrentDirectory(); 710 const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(state.compilerOptions)!, currentDirectory)); 711 const fileInfos: MapLike<BuilderState.FileInfo> = {}; 712 state.fileInfos.forEach((value, key) => { 713 const signature = state.currentAffectedFilesSignatures && state.currentAffectedFilesSignatures.get(key); 714 fileInfos[relativeToBuildInfo(key)] = signature === undefined ? value : { version: value.version, signature, affectsGlobalScope: value.affectsGlobalScope }; 715 }); 716 717 const result: ProgramBuildInfo = { 718 fileInfos, 719 options: convertToReusableCompilerOptions(state.compilerOptions, relativeToBuildInfoEnsuringAbsolutePath) 720 }; 721 if (state.referencedMap) { 722 const referencedMap: MapLike<string[]> = {}; 723 for (const key of arrayFrom(state.referencedMap.keys()).sort(compareStringsCaseSensitive)) { 724 referencedMap[relativeToBuildInfo(key)] = arrayFrom(state.referencedMap.get(key)!.keys(), relativeToBuildInfo).sort(compareStringsCaseSensitive); 725 } 726 result.referencedMap = referencedMap; 727 } 728 729 if (state.exportedModulesMap) { 730 const exportedModulesMap: MapLike<string[]> = {}; 731 for (const key of arrayFrom(state.exportedModulesMap.keys()).sort(compareStringsCaseSensitive)) { 732 const newValue = state.currentAffectedFilesExportedModulesMap && state.currentAffectedFilesExportedModulesMap.get(key); 733 // Not in temporary cache, use existing value 734 if (newValue === undefined) exportedModulesMap[relativeToBuildInfo(key)] = arrayFrom(state.exportedModulesMap.get(key)!.keys(), relativeToBuildInfo).sort(compareStringsCaseSensitive); 735 // Value in cache and has updated value map, use that 736 else if (newValue) exportedModulesMap[relativeToBuildInfo(key)] = arrayFrom(newValue.keys(), relativeToBuildInfo).sort(compareStringsCaseSensitive); 737 } 738 result.exportedModulesMap = exportedModulesMap; 739 } 740 741 if (state.semanticDiagnosticsPerFile) { 742 const semanticDiagnosticsPerFile: ProgramBuildInfoDiagnostic[] = []; 743 for (const key of arrayFrom(state.semanticDiagnosticsPerFile.keys()).sort(compareStringsCaseSensitive)) { 744 const value = state.semanticDiagnosticsPerFile.get(key)!; 745 semanticDiagnosticsPerFile.push( 746 value.length ? 747 [ 748 relativeToBuildInfo(key), 749 state.hasReusableDiagnostic ? 750 value as readonly ReusableDiagnostic[] : 751 convertToReusableDiagnostics(value as readonly Diagnostic[], relativeToBuildInfo) 752 ] : 753 relativeToBuildInfo(key) 754 ); 755 } 756 result.semanticDiagnosticsPerFile = semanticDiagnosticsPerFile; 757 } 758 759 if (state.affectedFilesPendingEmit) { 760 const affectedFilesPendingEmit: ProgramBuilderInfoFilePendingEmit[] = []; 761 const seenFiles = new Set<Path>(); 762 for (const path of state.affectedFilesPendingEmit.slice(state.affectedFilesPendingEmitIndex).sort(compareStringsCaseSensitive)) { 763 if (tryAddToSet(seenFiles, path)) { 764 affectedFilesPendingEmit.push([relativeToBuildInfo(path), state.affectedFilesPendingEmitKind!.get(path)!]); 765 } 766 } 767 result.affectedFilesPendingEmit = affectedFilesPendingEmit; 768 } 769 770 return result; 771 772 function relativeToBuildInfoEnsuringAbsolutePath(path: string) { 773 return relativeToBuildInfo(getNormalizedAbsolutePath(path, currentDirectory)); 774 } 775 776 function relativeToBuildInfo(path: string) { 777 return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory, path, getCanonicalFileName)); 778 } 779 } 780 781 function convertToReusableCompilerOptions(options: CompilerOptions, relativeToBuildInfo: (path: string) => string) { 782 const result: CompilerOptions = {}; 783 const { optionsNameMap } = getOptionsNameMap(); 784 785 for (const name in options) { 786 if (hasProperty(options, name)) { 787 result[name] = convertToReusableCompilerOptionValue( 788 optionsNameMap.get(name.toLowerCase()), 789 options[name] as CompilerOptionsValue, 790 relativeToBuildInfo 791 ); 792 } 793 } 794 if (result.configFilePath) { 795 result.configFilePath = relativeToBuildInfo(result.configFilePath); 796 } 797 return result; 798 } 799 800 function convertToReusableCompilerOptionValue(option: CommandLineOption | undefined, value: CompilerOptionsValue, relativeToBuildInfo: (path: string) => string) { 801 if (option) { 802 if (option.type === "list") { 803 const values = value as readonly (string | number)[]; 804 if (option.element.isFilePath && values.length) { 805 return values.map(relativeToBuildInfo); 806 } 807 } 808 else if (option.isFilePath) { 809 return relativeToBuildInfo(value as string); 810 } 811 } 812 return value; 813 } 814 815 function convertToReusableDiagnostics(diagnostics: readonly Diagnostic[], relativeToBuildInfo: (path: string) => string): readonly ReusableDiagnostic[] { 816 Debug.assert(!!diagnostics.length); 817 return diagnostics.map(diagnostic => { 818 const result: ReusableDiagnostic = convertToReusableDiagnosticRelatedInformation(diagnostic, relativeToBuildInfo); 819 result.reportsUnnecessary = diagnostic.reportsUnnecessary; 820 result.reportDeprecated = diagnostic.reportsDeprecated; 821 result.source = diagnostic.source; 822 result.skippedOn = diagnostic.skippedOn; 823 const { relatedInformation } = diagnostic; 824 result.relatedInformation = relatedInformation ? 825 relatedInformation.length ? 826 relatedInformation.map(r => convertToReusableDiagnosticRelatedInformation(r, relativeToBuildInfo)) : 827 [] : 828 undefined; 829 return result; 830 }); 831 } 832 833 function convertToReusableDiagnosticRelatedInformation(diagnostic: DiagnosticRelatedInformation, relativeToBuildInfo: (path: string) => string): ReusableDiagnosticRelatedInformation { 834 const { file } = diagnostic; 835 return { 836 ...diagnostic, 837 file: file ? relativeToBuildInfo(file.resolvedPath) : undefined 838 }; 839 } 840 841 export enum BuilderProgramKind { 842 SemanticDiagnosticsBuilderProgram, 843 EmitAndSemanticDiagnosticsBuilderProgram 844 } 845 846 export interface BuilderCreationParameters { 847 newProgram: Program; 848 host: BuilderProgramHost; 849 oldProgram: BuilderProgram | undefined; 850 configFileParsingDiagnostics: readonly Diagnostic[]; 851 } 852 853 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 { 854 let host: BuilderProgramHost; 855 let newProgram: Program; 856 let oldProgram: BuilderProgram; 857 if (newProgramOrRootNames === undefined) { 858 Debug.assert(hostOrOptions === undefined); 859 host = oldProgramOrHost as CompilerHost; 860 oldProgram = configFileParsingDiagnosticsOrOldProgram as BuilderProgram; 861 Debug.assert(!!oldProgram); 862 newProgram = oldProgram.getProgram(); 863 } 864 else if (isArray(newProgramOrRootNames)) { 865 oldProgram = configFileParsingDiagnosticsOrOldProgram as BuilderProgram; 866 newProgram = createProgram({ 867 rootNames: newProgramOrRootNames, 868 options: hostOrOptions as CompilerOptions, 869 host: oldProgramOrHost as CompilerHost, 870 oldProgram: oldProgram && oldProgram.getProgramOrUndefined(), 871 configFileParsingDiagnostics, 872 projectReferences 873 }); 874 host = oldProgramOrHost as CompilerHost; 875 } 876 else { 877 newProgram = newProgramOrRootNames; 878 host = hostOrOptions as BuilderProgramHost; 879 oldProgram = oldProgramOrHost as BuilderProgram; 880 configFileParsingDiagnostics = configFileParsingDiagnosticsOrOldProgram as readonly Diagnostic[]; 881 } 882 return { host, newProgram, oldProgram, configFileParsingDiagnostics: configFileParsingDiagnostics || emptyArray }; 883 } 884 885 export function createBuilderProgram(kind: BuilderProgramKind.SemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): SemanticDiagnosticsBuilderProgram; 886 export function createBuilderProgram(kind: BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram, builderCreationParameters: BuilderCreationParameters): EmitAndSemanticDiagnosticsBuilderProgram; 887 export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, host, oldProgram, configFileParsingDiagnostics }: BuilderCreationParameters) { 888 // Return same program if underlying program doesnt change 889 let oldState = oldProgram && oldProgram.getState(); 890 if (oldState && newProgram === oldState.program && configFileParsingDiagnostics === newProgram.getConfigFileParsingDiagnostics()) { 891 newProgram = undefined!; // TODO: GH#18217 892 oldState = undefined; 893 return oldProgram; 894 } 895 896 /** 897 * Create the canonical file name for identity 898 */ 899 const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); 900 /** 901 * Computing hash to for signature verification 902 */ 903 const computeHash = maybeBind(host, host.createHash); 904 let state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState); 905 let backupState: BuilderProgramState | undefined; 906 newProgram.getProgramBuildInfo = () => getProgramBuildInfo(state, getCanonicalFileName); 907 908 // To ensure that we arent storing any references to old program or new program without state 909 newProgram = undefined!; // TODO: GH#18217 910 oldProgram = undefined; 911 oldState = undefined; 912 913 const builderProgram = createRedirectedBuilderProgram(state, configFileParsingDiagnostics); 914 builderProgram.getState = () => state; 915 builderProgram.backupState = () => { 916 Debug.assert(backupState === undefined); 917 backupState = cloneBuilderProgramState(state); 918 }; 919 builderProgram.restoreState = () => { 920 state = Debug.checkDefined(backupState); 921 backupState = undefined; 922 }; 923 builderProgram.getAllDependencies = sourceFile => BuilderState.getAllDependencies(state, Debug.checkDefined(state.program), sourceFile); 924 builderProgram.getSemanticDiagnostics = getSemanticDiagnostics; 925 builderProgram.emit = emit; 926 builderProgram.releaseProgram = () => { 927 releaseCache(state); 928 backupState = undefined; 929 }; 930 931 if (kind === BuilderProgramKind.SemanticDiagnosticsBuilderProgram) { 932 (builderProgram as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; 933 } 934 else if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { 935 (builderProgram as EmitAndSemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile; 936 (builderProgram as EmitAndSemanticDiagnosticsBuilderProgram).emitNextAffectedFile = emitNextAffectedFile; 937 builderProgram.emitBuildInfo = emitBuildInfo; 938 } 939 else { 940 notImplemented(); 941 } 942 943 return builderProgram; 944 945 function emitBuildInfo(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult { 946 if (state.buildInfoEmitPending) { 947 const result = Debug.checkDefined(state.program).emitBuildInfo(writeFile || maybeBind(host, host.writeFile), cancellationToken); 948 state.buildInfoEmitPending = false; 949 return result; 950 } 951 return emitSkippedWithNoDiagnostics; 952 } 953 954 /** 955 * Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete 956 * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host 957 * in that order would be used to write the files 958 */ 959 function emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): AffectedFileResult<EmitResult> { 960 let affected = getNextAffectedFile(state, cancellationToken, computeHash); 961 let emitKind = BuilderFileEmit.Full; 962 let isPendingEmitFile = false; 963 if (!affected) { 964 if (!outFile(state.compilerOptions)) { 965 const pendingAffectedFile = getNextAffectedFilePendingEmit(state); 966 if (!pendingAffectedFile) { 967 if (!state.buildInfoEmitPending) { 968 return undefined; 969 } 970 971 const affected = Debug.checkDefined(state.program); 972 return toAffectedFileEmitResult( 973 state, 974 // When whole program is affected, do emit only once (eg when --out or --outFile is specified) 975 // Otherwise just affected file 976 affected.emitBuildInfo(writeFile || maybeBind(host, host.writeFile), cancellationToken), 977 affected, 978 /*emitKind*/ BuilderFileEmit.Full, 979 /*isPendingEmitFile*/ false, 980 /*isBuildInfoEmit*/ true 981 ); 982 } 983 ({ affectedFile: affected, emitKind } = pendingAffectedFile); 984 isPendingEmitFile = true; 985 } 986 else { 987 const program = Debug.checkDefined(state.program); 988 if (state.programEmitComplete) return undefined; 989 affected = program; 990 } 991 } 992 993 return toAffectedFileEmitResult( 994 state, 995 // When whole program is affected, do emit only once (eg when --out or --outFile is specified) 996 // Otherwise just affected file 997 Debug.checkDefined(state.program).emit( 998 affected === state.program ? undefined : affected as SourceFile, 999 writeFile || maybeBind(host, host.writeFile), 1000 cancellationToken, 1001 emitOnlyDtsFiles || emitKind === BuilderFileEmit.DtsOnly, 1002 customTransformers 1003 ), 1004 affected, 1005 emitKind, 1006 isPendingEmitFile, 1007 ); 1008 } 1009 1010 /** 1011 * Emits the JavaScript and declaration files. 1012 * When targetSource file is specified, emits the files corresponding to that source file, 1013 * otherwise for the whole program. 1014 * In case of EmitAndSemanticDiagnosticsBuilderProgram, when targetSourceFile is specified, 1015 * it is assumed that that file is handled from affected file list. If targetSourceFile is not specified, 1016 * it will only emit all the affected files instead of whole program 1017 * 1018 * The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host 1019 * in that order would be used to write the files 1020 */ 1021 function emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult { 1022 let restorePendingEmitOnHandlingNoEmitSuccess = false; 1023 let savedAffectedFilesPendingEmit; 1024 let savedAffectedFilesPendingEmitKind; 1025 let savedAffectedFilesPendingEmitIndex; 1026 // Backup and restore affected pendings emit state for non emit Builder if noEmitOnError is enabled and emitBuildInfo could be written in case there are errors 1027 // This ensures pending files to emit is updated in tsbuildinfo 1028 // Note that when there are no errors, emit proceeds as if everything is emitted as it is callers reponsibility to write the files to disk if at all (because its builder that doesnt track files to emit) 1029 if (kind !== BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram && 1030 !targetSourceFile && 1031 !outFile(state.compilerOptions) && 1032 !state.compilerOptions.noEmit && 1033 state.compilerOptions.noEmitOnError) { 1034 restorePendingEmitOnHandlingNoEmitSuccess = true; 1035 savedAffectedFilesPendingEmit = state.affectedFilesPendingEmit && state.affectedFilesPendingEmit.slice(); 1036 savedAffectedFilesPendingEmitKind = state.affectedFilesPendingEmitKind && new Map(state.affectedFilesPendingEmitKind); 1037 savedAffectedFilesPendingEmitIndex = state.affectedFilesPendingEmitIndex; 1038 } 1039 1040 if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { 1041 assertSourceFileOkWithoutNextAffectedCall(state, targetSourceFile); 1042 } 1043 const result = handleNoEmitOptions(builderProgram, targetSourceFile, writeFile, cancellationToken); 1044 if (result) return result; 1045 1046 if (restorePendingEmitOnHandlingNoEmitSuccess) { 1047 state.affectedFilesPendingEmit = savedAffectedFilesPendingEmit; 1048 state.affectedFilesPendingEmitKind = savedAffectedFilesPendingEmitKind; 1049 state.affectedFilesPendingEmitIndex = savedAffectedFilesPendingEmitIndex; 1050 } 1051 1052 // Emit only affected files if using builder for emit 1053 if (!targetSourceFile && kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) { 1054 // Emit and report any errors we ran into. 1055 let sourceMaps: SourceMapEmitResult[] = []; 1056 let emitSkipped = false; 1057 let diagnostics: Diagnostic[] | undefined; 1058 let emittedFiles: string[] = []; 1059 1060 let affectedEmitResult: AffectedFileResult<EmitResult>; 1061 while (affectedEmitResult = emitNextAffectedFile(writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers)) { 1062 emitSkipped = emitSkipped || affectedEmitResult.result.emitSkipped; 1063 diagnostics = addRange(diagnostics, affectedEmitResult.result.diagnostics); 1064 emittedFiles = addRange(emittedFiles, affectedEmitResult.result.emittedFiles); 1065 sourceMaps = addRange(sourceMaps, affectedEmitResult.result.sourceMaps); 1066 } 1067 return { 1068 emitSkipped, 1069 diagnostics: diagnostics || emptyArray, 1070 emittedFiles, 1071 sourceMaps 1072 }; 1073 } 1074 return Debug.checkDefined(state.program).emit(targetSourceFile, writeFile || maybeBind(host, host.writeFile), cancellationToken, emitOnlyDtsFiles, customTransformers); 1075 } 1076 1077 /** 1078 * Return the semantic diagnostics for the next affected file or undefined if iteration is complete 1079 * If provided ignoreSourceFile would be called before getting the diagnostics and would ignore the sourceFile if the returned value was true 1080 */ 1081 function getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult<readonly Diagnostic[]> { 1082 while (true) { 1083 const affected = getNextAffectedFile(state, cancellationToken, computeHash); 1084 if (!affected) { 1085 // Done 1086 return undefined; 1087 } 1088 else if (affected === state.program) { 1089 // When whole program is affected, get all semantic diagnostics (eg when --out or --outFile is specified) 1090 return toAffectedFileResult( 1091 state, 1092 state.program.getSemanticDiagnostics(/*targetSourceFile*/ undefined, cancellationToken), 1093 affected 1094 ); 1095 } 1096 1097 // Add file to affected file pending emit to handle for later emit time 1098 // 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 1099 if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram || state.compilerOptions.noEmit || state.compilerOptions.noEmitOnError) { 1100 addToAffectedFilesPendingEmit(state, (affected as SourceFile).resolvedPath, BuilderFileEmit.Full); 1101 } 1102 1103 // Get diagnostics for the affected file if its not ignored 1104 if (ignoreSourceFile && ignoreSourceFile(affected as SourceFile)) { 1105 // Get next affected file 1106 doneWithAffectedFile(state, affected); 1107 continue; 1108 } 1109 1110 return toAffectedFileResult( 1111 state, 1112 getSemanticDiagnosticsOfFile(state, affected as SourceFile, cancellationToken), 1113 affected 1114 ); 1115 } 1116 } 1117 1118 /** 1119 * Gets the semantic diagnostics from the program corresponding to this state of file (if provided) or whole program 1120 * The semantic diagnostics are cached and managed here 1121 * Note that it is assumed that when asked about semantic diagnostics through this API, 1122 * the file has been taken out of affected files so it is safe to use cache or get from program and cache the diagnostics 1123 * In case of SemanticDiagnosticsBuilderProgram if the source file is not provided, 1124 * it will iterate through all the affected files, to ensure that cache stays valid and yet provide a way to get all semantic diagnostics 1125 */ 1126 function getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] { 1127 assertSourceFileOkWithoutNextAffectedCall(state, sourceFile); 1128 const compilerOptions = Debug.checkDefined(state.program).getCompilerOptions(); 1129 if (outFile(compilerOptions)) { 1130 Debug.assert(!state.semanticDiagnosticsPerFile); 1131 // We dont need to cache the diagnostics just return them from program 1132 return Debug.checkDefined(state.program).getSemanticDiagnostics(sourceFile, cancellationToken); 1133 } 1134 1135 if (sourceFile) { 1136 return getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken); 1137 } 1138 1139 // When semantic builder asks for diagnostics of the whole program, 1140 // ensure that all the affected files are handled 1141 // eslint-disable-next-line no-empty 1142 while (getSemanticDiagnosticsOfNextAffectedFile(cancellationToken)) { 1143 } 1144 1145 let diagnostics: Diagnostic[] | undefined; 1146 for (const sourceFile of Debug.checkDefined(state.program).getSourceFiles()) { 1147 diagnostics = addRange(diagnostics, getSemanticDiagnosticsOfFile(state, sourceFile, cancellationToken)); 1148 } 1149 return diagnostics || emptyArray; 1150 } 1151 } 1152 1153 function addToAffectedFilesPendingEmit(state: BuilderProgramState, affectedFilePendingEmit: Path, kind: BuilderFileEmit) { 1154 if (!state.affectedFilesPendingEmit) state.affectedFilesPendingEmit = []; 1155 if (!state.affectedFilesPendingEmitKind) state.affectedFilesPendingEmitKind = new Map(); 1156 1157 const existingKind = state.affectedFilesPendingEmitKind.get(affectedFilePendingEmit); 1158 state.affectedFilesPendingEmit.push(affectedFilePendingEmit); 1159 state.affectedFilesPendingEmitKind.set(affectedFilePendingEmit, existingKind || kind); 1160 1161 // affectedFilesPendingEmitIndex === undefined 1162 // - means the emit state.affectedFilesPendingEmit was undefined before adding current affected files 1163 // so start from 0 as array would be affectedFilesPendingEmit 1164 // else, continue to iterate from existing index, the current set is appended to existing files 1165 if (state.affectedFilesPendingEmitIndex === undefined) { 1166 state.affectedFilesPendingEmitIndex = 0; 1167 } 1168 } 1169 1170 function getMapOfReferencedSet(mapLike: MapLike<readonly string[]> | undefined, toPath: (path: string) => Path): ReadonlyESMap<Path, BuilderState.ReferencedSet> | undefined { 1171 if (!mapLike) return undefined; 1172 const map = new Map<Path, BuilderState.ReferencedSet>(); 1173 // Copies keys/values from template. Note that for..in will not throw if 1174 // template is undefined, and instead will just exit the loop. 1175 for (const key in mapLike) { 1176 if (hasProperty(mapLike, key)) { 1177 map.set(toPath(key), new Set(mapLike[key].map(toPath))); 1178 } 1179 } 1180 return map; 1181 } 1182 1183 export function createBuildProgramUsingProgramBuildInfo(program: ProgramBuildInfo, buildInfoPath: string, host: ReadBuildProgramHost): EmitAndSemanticDiagnosticsBuilderProgram { 1184 const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory())); 1185 const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); 1186 1187 const fileInfos = new Map<Path, BuilderState.FileInfo>(); 1188 for (const key in program.fileInfos) { 1189 if (hasProperty(program.fileInfos, key)) { 1190 fileInfos.set(toPath(key), program.fileInfos[key]); 1191 } 1192 } 1193 1194 const state: ReusableBuilderProgramState = { 1195 fileInfos, 1196 compilerOptions: convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath), 1197 referencedMap: getMapOfReferencedSet(program.referencedMap, toPath), 1198 exportedModulesMap: getMapOfReferencedSet(program.exportedModulesMap, toPath), 1199 semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toPath(isString(value) ? value : value[0]), value => isString(value) ? emptyArray : value[1]), 1200 hasReusableDiagnostic: true, 1201 affectedFilesPendingEmit: map(program.affectedFilesPendingEmit, value => toPath(value[0])), 1202 affectedFilesPendingEmitKind: program.affectedFilesPendingEmit && arrayToMap(program.affectedFilesPendingEmit, value => toPath(value[0]), value => value[1]), 1203 affectedFilesPendingEmitIndex: program.affectedFilesPendingEmit && 0, 1204 }; 1205 return { 1206 getState: () => state, 1207 backupState: noop, 1208 restoreState: noop, 1209 getProgram: notImplemented, 1210 getProgramOrUndefined: returnUndefined, 1211 releaseProgram: noop, 1212 getCompilerOptions: () => state.compilerOptions, 1213 getSourceFile: notImplemented, 1214 getSourceFiles: notImplemented, 1215 getOptionsDiagnostics: notImplemented, 1216 getGlobalDiagnostics: notImplemented, 1217 getConfigFileParsingDiagnostics: notImplemented, 1218 getSyntacticDiagnostics: notImplemented, 1219 getDeclarationDiagnostics: notImplemented, 1220 getSemanticDiagnostics: notImplemented, 1221 emit: notImplemented, 1222 getAllDependencies: notImplemented, 1223 getCurrentDirectory: notImplemented, 1224 emitNextAffectedFile: notImplemented, 1225 getSemanticDiagnosticsOfNextAffectedFile: notImplemented, 1226 emitBuildInfo: notImplemented, 1227 close: noop, 1228 }; 1229 1230 function toPath(path: string) { 1231 return ts.toPath(path, buildInfoDirectory, getCanonicalFileName); 1232 } 1233 1234 function toAbsolutePath(path: string) { 1235 return getNormalizedAbsolutePath(path, buildInfoDirectory); 1236 } 1237 } 1238 1239 export function createRedirectedBuilderProgram(state: { program: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: readonly Diagnostic[]): BuilderProgram { 1240 return { 1241 getState: notImplemented, 1242 backupState: noop, 1243 restoreState: noop, 1244 getProgram, 1245 getProgramOrUndefined: () => state.program, 1246 releaseProgram: () => state.program = undefined, 1247 getCompilerOptions: () => state.compilerOptions, 1248 getSourceFile: fileName => getProgram().getSourceFile(fileName), 1249 getSourceFiles: () => getProgram().getSourceFiles(), 1250 getOptionsDiagnostics: cancellationToken => getProgram().getOptionsDiagnostics(cancellationToken), 1251 getGlobalDiagnostics: cancellationToken => getProgram().getGlobalDiagnostics(cancellationToken), 1252 getConfigFileParsingDiagnostics: () => configFileParsingDiagnostics, 1253 getSyntacticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSyntacticDiagnostics(sourceFile, cancellationToken), 1254 getDeclarationDiagnostics: (sourceFile, cancellationToken) => getProgram().getDeclarationDiagnostics(sourceFile, cancellationToken), 1255 getSemanticDiagnostics: (sourceFile, cancellationToken) => getProgram().getSemanticDiagnostics(sourceFile, cancellationToken), 1256 emit: (sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers) => getProgram().emit(sourceFile, writeFile, cancellationToken, emitOnlyDts, customTransformers), 1257 emitBuildInfo: (writeFile, cancellationToken) => getProgram().emitBuildInfo(writeFile, cancellationToken), 1258 getAllDependencies: notImplemented, 1259 getCurrentDirectory: () => getProgram().getCurrentDirectory(), 1260 close: noop, 1261 }; 1262 1263 function getProgram() { 1264 return Debug.checkDefined(state.program); 1265 } 1266 } 1267} 1268