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