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