1namespace ts { 2 const minimumDate = new Date(-8640000000000000); 3 const maximumDate = new Date(8640000000000000); 4 5 export interface BuildOptions { 6 dry?: boolean; 7 force?: boolean; 8 verbose?: boolean; 9 10 /*@internal*/ clean?: boolean; 11 /*@internal*/ watch?: boolean; 12 /*@internal*/ help?: boolean; 13 14 /*@internal*/ preserveWatchOutput?: boolean; 15 /*@internal*/ listEmittedFiles?: boolean; 16 /*@internal*/ listFiles?: boolean; 17 /*@internal*/ explainFiles?: boolean; 18 /*@internal*/ pretty?: boolean; 19 incremental?: boolean; 20 assumeChangesOnlyAffectDirectDependencies?: boolean; 21 22 traceResolution?: boolean; 23 /* @internal */ diagnostics?: boolean; 24 /* @internal */ extendedDiagnostics?: boolean; 25 /* @internal */ locale?: string; 26 /* @internal */ generateCpuProfile?: string; 27 /* @internal */ generateTrace?: string; 28 29 [option: string]: CompilerOptionsValue | undefined; 30 } 31 32 enum BuildResultFlags { 33 None = 0, 34 35 /** 36 * No errors of any kind occurred during build 37 */ 38 Success = 1 << 0, 39 /** 40 * None of the .d.ts files emitted by this build were 41 * different from the existing files on disk 42 */ 43 DeclarationOutputUnchanged = 1 << 1, 44 45 ConfigFileErrors = 1 << 2, 46 SyntaxErrors = 1 << 3, 47 TypeErrors = 1 << 4, 48 DeclarationEmitErrors = 1 << 5, 49 EmitErrors = 1 << 6, 50 51 AnyErrors = ConfigFileErrors | SyntaxErrors | TypeErrors | DeclarationEmitErrors | EmitErrors 52 } 53 54 /*@internal*/ 55 export type ResolvedConfigFilePath = ResolvedConfigFileName & Path; 56 57 function getOrCreateValueFromConfigFileMap<T>(configFileMap: ESMap<ResolvedConfigFilePath, T>, resolved: ResolvedConfigFilePath, createT: () => T): T { 58 const existingValue = configFileMap.get(resolved); 59 let newValue: T | undefined; 60 if (!existingValue) { 61 newValue = createT(); 62 configFileMap.set(resolved, newValue); 63 } 64 return existingValue || newValue!; 65 } 66 67 function getOrCreateValueMapFromConfigFileMap<K extends string, V>(configFileMap: ESMap<ResolvedConfigFilePath, ESMap<K, V>>, resolved: ResolvedConfigFilePath): ESMap<K, V> { 68 return getOrCreateValueFromConfigFileMap(configFileMap, resolved, () => new Map()); 69 } 70 71 /*@internal*/ 72 /** Helper to use now method instead of current date for testing purposes to get consistent baselines */ 73 export function getCurrentTime(host: { now?(): Date; }) { 74 return host.now ? host.now() : new Date(); 75 } 76 77 export type ReportEmitErrorSummary = (errorCount: number, filesInError: (ReportFileInError | undefined)[]) => void; 78 79 80 export interface ReportFileInError { 81 fileName: string; 82 line: number; 83 } 84 85 export interface SolutionBuilderHostBase<T extends BuilderProgram> extends ProgramHost<T> { 86 createDirectory?(path: string): void; 87 /** 88 * Should provide create directory and writeFile if done of invalidatedProjects is not invoked with 89 * writeFileCallback 90 */ 91 writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void; 92 getCustomTransformers?: (project: string) => CustomTransformers | undefined; 93 94 getModifiedTime(fileName: string): Date | undefined; 95 setModifiedTime(fileName: string, date: Date): void; 96 deleteFile(fileName: string): void; 97 getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined; 98 99 reportDiagnostic: DiagnosticReporter; // Technically we want to move it out and allow steps of actions on Solution, but for now just merge stuff in build host here 100 reportSolutionBuilderStatus: DiagnosticReporter; 101 102 // TODO: To do better with watch mode and normal build mode api that creates program and emits files 103 // This currently helps enable --diagnostics and --extendedDiagnostics 104 afterProgramEmitAndDiagnostics?(program: T): void; 105 /*@internal*/ afterEmitBundle?(config: ParsedCommandLine): void; 106 107 // For testing 108 /*@internal*/ now?(): Date; 109 } 110 111 export interface SolutionBuilderHost<T extends BuilderProgram> extends SolutionBuilderHostBase<T> { 112 reportErrorSummary?: ReportEmitErrorSummary; 113 } 114 115 export interface SolutionBuilderWithWatchHost<T extends BuilderProgram> extends SolutionBuilderHostBase<T>, WatchHost { 116 } 117 118 /*@internal*/ 119 export type BuildOrder = readonly ResolvedConfigFileName[]; 120 /*@internal*/ 121 export interface CircularBuildOrder { 122 buildOrder: BuildOrder; 123 circularDiagnostics: readonly Diagnostic[]; 124 } 125 /*@internal*/ 126 export type AnyBuildOrder = BuildOrder | CircularBuildOrder; 127 128 /*@internal*/ 129 export function isCircularBuildOrder(buildOrder: AnyBuildOrder): buildOrder is CircularBuildOrder { 130 return !!buildOrder && !!(buildOrder as CircularBuildOrder).buildOrder; 131 } 132 133 /*@internal*/ 134 export function getBuildOrderFromAnyBuildOrder(anyBuildOrder: AnyBuildOrder): BuildOrder { 135 return isCircularBuildOrder(anyBuildOrder) ? anyBuildOrder.buildOrder : anyBuildOrder; 136 } 137 138 export interface SolutionBuilder<T extends BuilderProgram> { 139 build(project?: string, cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, getCustomTransformers?: (project: string) => CustomTransformers): ExitStatus; 140 clean(project?: string): ExitStatus; 141 buildReferences(project: string, cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, getCustomTransformers?: (project: string) => CustomTransformers): ExitStatus; 142 cleanReferences(project?: string): ExitStatus; 143 getNextInvalidatedProject(cancellationToken?: CancellationToken): InvalidatedProject<T> | undefined; 144 145 // Currently used for testing but can be made public if needed: 146 /*@internal*/ getBuildOrder(): AnyBuildOrder; 147 148 // Testing only 149 /*@internal*/ getUpToDateStatusOfProject(project: string): UpToDateStatus; 150 /*@internal*/ invalidateProject(configFilePath: ResolvedConfigFilePath, reloadLevel?: ConfigFileProgramReloadLevel): void; 151 /*@internal*/ close(): void; 152 } 153 154 /** 155 * Create a function that reports watch status by writing to the system and handles the formating of the diagnostic 156 */ 157 export function createBuilderStatusReporter(system: System, pretty?: boolean): DiagnosticReporter { 158 return diagnostic => { 159 let output = pretty ? `[${formatColorAndReset(getLocaleTimeString(system), ForegroundColorEscapeSequences.Grey)}] ` : `${getLocaleTimeString(system)} - `; 160 output += `${flattenDiagnosticMessageText(diagnostic.messageText, system.newLine)}${system.newLine + system.newLine}`; 161 system.write(output); 162 }; 163 } 164 165 function createSolutionBuilderHostBase<T extends BuilderProgram>(system: System, createProgram: CreateProgram<T> | undefined, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter) { 166 const host = createProgramHost(system, createProgram) as SolutionBuilderHostBase<T>; 167 host.getModifiedTime = system.getModifiedTime ? path => system.getModifiedTime!(path) : returnUndefined; 168 host.setModifiedTime = system.setModifiedTime ? (path, date) => system.setModifiedTime!(path, date) : noop; 169 host.deleteFile = system.deleteFile ? path => system.deleteFile!(path) : noop; 170 host.reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); 171 host.reportSolutionBuilderStatus = reportSolutionBuilderStatus || createBuilderStatusReporter(system); 172 host.now = maybeBind(system, system.now); // For testing 173 return host; 174 } 175 176 export function createSolutionBuilderHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary) { 177 const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderHost<T>; 178 host.reportErrorSummary = reportErrorSummary; 179 return host; 180 } 181 182 export function createSolutionBuilderWithWatchHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter) { 183 const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost<T>; 184 const watchHost = createWatchHost(system, reportWatchStatus); 185 copyProperties(host, watchHost); 186 return host; 187 } 188 189 function getCompilerOptionsOfBuildOptions(buildOptions: BuildOptions): CompilerOptions { 190 const result = {} as CompilerOptions; 191 commonOptionsWithBuild.forEach(option => { 192 if (hasProperty(buildOptions, option.name)) result[option.name] = buildOptions[option.name]; 193 }); 194 return result; 195 } 196 197 export function createSolutionBuilder<T extends BuilderProgram>(host: SolutionBuilderHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder<T> { 198 return createSolutionBuilderWorker(/*watch*/ false, host, rootNames, defaultOptions); 199 } 200 201 export function createSolutionBuilderWithWatch<T extends BuilderProgram>(host: SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions, baseWatchOptions?: WatchOptions): SolutionBuilder<T> { 202 return createSolutionBuilderWorker(/*watch*/ true, host, rootNames, defaultOptions, baseWatchOptions); 203 } 204 205 type ConfigFileCacheEntry = ParsedCommandLine | Diagnostic; 206 interface SolutionBuilderStateCache { 207 originalReadFile: CompilerHost["readFile"]; 208 originalFileExists: CompilerHost["fileExists"]; 209 originalDirectoryExists: CompilerHost["directoryExists"]; 210 originalCreateDirectory: CompilerHost["createDirectory"]; 211 originalWriteFile: CompilerHost["writeFile"] | undefined; 212 originalReadFileWithCache: CompilerHost["readFile"]; 213 originalGetSourceFile: CompilerHost["getSourceFile"]; 214 } 215 216 interface FileWatcherWithModifiedTime { 217 callbacks: FileWatcherCallback[]; 218 watcher: FileWatcher; 219 // modified time can be undefined if it was not queried 220 // Eg. if input file timestamp was not queried because tsbuildinfo was missing but watcher for that file is created 221 modifiedTime: Date | undefined; 222 } 223 224 interface BuildInfoCacheEntry { 225 path: Path; 226 buildInfo: BuildInfo | false; 227 modifiedTime: Date; 228 latestChangedDtsTime?: Date | false; 229 } 230 231 interface SolutionBuilderState<T extends BuilderProgram = BuilderProgram> extends WatchFactory<WatchType, ResolvedConfigFileName> { 232 readonly host: SolutionBuilderHost<T>; 233 readonly hostWithWatch: SolutionBuilderWithWatchHost<T>; 234 readonly currentDirectory: string; 235 readonly getCanonicalFileName: GetCanonicalFileName; 236 readonly parseConfigFileHost: ParseConfigFileHost; 237 readonly write: ((s: string) => void) | undefined; 238 239 // State of solution 240 readonly options: BuildOptions; 241 readonly baseCompilerOptions: CompilerOptions; 242 readonly rootNames: readonly string[]; 243 readonly baseWatchOptions: WatchOptions | undefined; 244 245 readonly resolvedConfigFilePaths: ESMap<string, ResolvedConfigFilePath>; 246 readonly configFileCache: ESMap<ResolvedConfigFilePath, ConfigFileCacheEntry>; 247 /** Map from config file name to up-to-date status */ 248 readonly projectStatus: ESMap<ResolvedConfigFilePath, UpToDateStatus>; 249 readonly extendedConfigCache: ESMap<string, ExtendedConfigCacheEntry>; 250 readonly buildInfoCache: ESMap<ResolvedConfigFilePath, BuildInfoCacheEntry>; 251 252 readonly builderPrograms: ESMap<ResolvedConfigFilePath, T>; 253 readonly diagnostics: ESMap<ResolvedConfigFilePath, readonly Diagnostic[]>; 254 readonly projectPendingBuild: ESMap<ResolvedConfigFilePath, ConfigFileProgramReloadLevel>; 255 readonly projectErrorsReported: ESMap<ResolvedConfigFilePath, true>; 256 257 readonly compilerHost: CompilerHost; 258 readonly moduleResolutionCache: ModuleResolutionCache | undefined; 259 readonly typeReferenceDirectiveResolutionCache: TypeReferenceDirectiveResolutionCache | undefined; 260 261 // Mutable state 262 buildOrder: AnyBuildOrder | undefined; 263 readFileWithCache: (f: string) => string | undefined; 264 projectCompilerOptions: CompilerOptions; 265 cache: SolutionBuilderStateCache | undefined; 266 allProjectBuildPending: boolean; 267 needsSummary: boolean; 268 watchAllProjectsPending: boolean; 269 270 // Watch state 271 readonly watch: boolean; 272 readonly allWatchedWildcardDirectories: ESMap<ResolvedConfigFilePath, ESMap<string, WildcardDirectoryWatcher>>; 273 readonly allWatchedInputFiles: ESMap<ResolvedConfigFilePath, ESMap<Path, FileWatcher>>; 274 readonly allWatchedConfigFiles: ESMap<ResolvedConfigFilePath, FileWatcher>; 275 readonly allWatchedExtendedConfigFiles: ESMap<Path, SharedExtendedConfigFileWatcher<ResolvedConfigFilePath>>; 276 readonly allWatchedPackageJsonFiles: ESMap<ResolvedConfigFilePath, ESMap<Path, FileWatcher>>; 277 readonly filesWatched: ESMap<Path, FileWatcherWithModifiedTime | Date>; 278 readonly outputTimeStamps: ESMap<ResolvedConfigFilePath, ESMap<Path, Date>>; 279 280 readonly lastCachedPackageJsonLookups: ESMap<ResolvedConfigFilePath, readonly (readonly [Path, object | boolean])[] | undefined>; 281 282 timerToBuildInvalidatedProject: any; 283 reportFileChangeDetected: boolean; 284 writeLog: (s: string) => void; 285 } 286 287 function createSolutionBuilderState<T extends BuilderProgram>(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], options: BuildOptions, baseWatchOptions: WatchOptions | undefined): SolutionBuilderState<T> { 288 const host = hostOrHostWithWatch as SolutionBuilderHost<T>; 289 const hostWithWatch = hostOrHostWithWatch as SolutionBuilderWithWatchHost<T>; 290 const currentDirectory = host.getCurrentDirectory(); 291 const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); 292 293 // State of the solution 294 const baseCompilerOptions = getCompilerOptionsOfBuildOptions(options); 295 const compilerHost = createCompilerHostFromProgramHost(host, () => state.projectCompilerOptions) as CompilerHost & ReadBuildProgramHost; 296 setGetSourceFileAsHashVersioned(compilerHost); 297 compilerHost.getParsedCommandLine = fileName => parseConfigFile(state, fileName as ResolvedConfigFileName, toResolvedConfigFilePath(state, fileName as ResolvedConfigFileName)); 298 compilerHost.resolveModuleNames = maybeBind(host, host.resolveModuleNames); 299 compilerHost.resolveTypeReferenceDirectives = maybeBind(host, host.resolveTypeReferenceDirectives); 300 compilerHost.getModuleResolutionCache = maybeBind(host, host.getModuleResolutionCache); 301 const moduleResolutionCache = !compilerHost.resolveModuleNames ? createModuleResolutionCache(currentDirectory, getCanonicalFileName) : undefined; 302 const typeReferenceDirectiveResolutionCache = !compilerHost.resolveTypeReferenceDirectives ? createTypeReferenceDirectiveResolutionCache(currentDirectory, getCanonicalFileName, /*options*/ undefined, moduleResolutionCache?.getPackageJsonInfoCache()) : undefined; 303 if (!compilerHost.resolveModuleNames) { 304 const loader = (moduleName: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFile, state.projectCompilerOptions, compilerHost, moduleResolutionCache, redirectedReference, resolverMode).resolvedModule!; 305 compilerHost.resolveModuleNames = (moduleNames, containingFile, _reusedNames, redirectedReference, _options, containingSourceFile) => 306 loadWithModeAwareCache<ResolvedModuleFull>(Debug.checkEachDefined(moduleNames), Debug.checkDefined(containingSourceFile), containingFile, redirectedReference, loader); 307 compilerHost.getModuleResolutionCache = () => moduleResolutionCache; 308 } 309 if (!compilerHost.resolveTypeReferenceDirectives) { 310 const loader = (moduleName: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined, containingFileMode: SourceFile["impliedNodeFormat"] | undefined) => resolveTypeReferenceDirective(moduleName, containingFile, state.projectCompilerOptions, compilerHost, redirectedReference, state.typeReferenceDirectiveResolutionCache, containingFileMode).resolvedTypeReferenceDirective!; 311 compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile, redirectedReference, _options, containingFileMode) => 312 loadWithTypeDirectiveCache<ResolvedTypeReferenceDirective>(Debug.checkEachDefined(typeReferenceDirectiveNames), containingFile, redirectedReference, containingFileMode, loader); 313 } 314 compilerHost.getBuildInfo = (fileName, configFilePath) => getBuildInfo(state, fileName, toResolvedConfigFilePath(state, configFilePath as ResolvedConfigFileName), /*modifiedTime*/ undefined); 315 316 const { watchFile, watchDirectory, writeLog } = createWatchFactory<ResolvedConfigFileName>(hostWithWatch, options); 317 318 const state: SolutionBuilderState<T> = { 319 host, 320 hostWithWatch, 321 currentDirectory, 322 getCanonicalFileName, 323 parseConfigFileHost: parseConfigHostFromCompilerHostLike(host), 324 write: maybeBind(host, host.trace), 325 326 // State of solution 327 options, 328 baseCompilerOptions, 329 rootNames, 330 baseWatchOptions, 331 332 resolvedConfigFilePaths: new Map(), 333 configFileCache: new Map(), 334 projectStatus: new Map(), 335 extendedConfigCache: new Map(), 336 buildInfoCache: new Map(), 337 outputTimeStamps: new Map(), 338 339 builderPrograms: new Map(), 340 diagnostics: new Map(), 341 projectPendingBuild: new Map(), 342 projectErrorsReported: new Map(), 343 344 compilerHost, 345 moduleResolutionCache, 346 typeReferenceDirectiveResolutionCache, 347 348 // Mutable state 349 buildOrder: undefined, 350 readFileWithCache: f => host.readFile(f), 351 projectCompilerOptions: baseCompilerOptions, 352 cache: undefined, 353 allProjectBuildPending: true, 354 needsSummary: true, 355 watchAllProjectsPending: watch, 356 357 // Watch state 358 watch, 359 allWatchedWildcardDirectories: new Map(), 360 allWatchedInputFiles: new Map(), 361 allWatchedConfigFiles: new Map(), 362 allWatchedExtendedConfigFiles: new Map(), 363 allWatchedPackageJsonFiles: new Map(), 364 filesWatched: new Map(), 365 366 lastCachedPackageJsonLookups: new Map(), 367 368 timerToBuildInvalidatedProject: undefined, 369 reportFileChangeDetected: false, 370 watchFile, 371 watchDirectory, 372 writeLog, 373 }; 374 375 return state; 376 } 377 378 function toPath(state: SolutionBuilderState, fileName: string) { 379 return ts.toPath(fileName, state.currentDirectory, state.getCanonicalFileName); 380 } 381 382 function toResolvedConfigFilePath(state: SolutionBuilderState, fileName: ResolvedConfigFileName): ResolvedConfigFilePath { 383 const { resolvedConfigFilePaths } = state; 384 const path = resolvedConfigFilePaths.get(fileName); 385 if (path !== undefined) return path; 386 387 const resolvedPath = toPath(state, fileName) as ResolvedConfigFilePath; 388 resolvedConfigFilePaths.set(fileName, resolvedPath); 389 return resolvedPath; 390 } 391 392 function isParsedCommandLine(entry: ConfigFileCacheEntry): entry is ParsedCommandLine { 393 return !!(entry as ParsedCommandLine).options; 394 } 395 396 function getCachedParsedConfigFile(state: SolutionBuilderState, configFilePath: ResolvedConfigFilePath): ParsedCommandLine | undefined { 397 const value = state.configFileCache.get(configFilePath); 398 return value && isParsedCommandLine(value) ? value : undefined; 399 } 400 401 function parseConfigFile(state: SolutionBuilderState, configFileName: ResolvedConfigFileName, configFilePath: ResolvedConfigFilePath): ParsedCommandLine | undefined { 402 const { configFileCache } = state; 403 const value = configFileCache.get(configFilePath); 404 if (value) { 405 return isParsedCommandLine(value) ? value : undefined; 406 } 407 408 performance.mark("SolutionBuilder::beforeConfigFileParsing"); 409 let diagnostic: Diagnostic | undefined; 410 const { parseConfigFileHost, baseCompilerOptions, baseWatchOptions, extendedConfigCache, host } = state; 411 let parsed: ParsedCommandLine | undefined; 412 if (host.getParsedCommandLine) { 413 parsed = host.getParsedCommandLine(configFileName); 414 if (!parsed) diagnostic = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); 415 } 416 else { 417 parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = d => diagnostic = d; 418 parsed = getParsedCommandLineOfConfigFile(configFileName, baseCompilerOptions, parseConfigFileHost, extendedConfigCache, baseWatchOptions); 419 parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = noop; 420 } 421 configFileCache.set(configFilePath, parsed || diagnostic!); 422 performance.mark("SolutionBuilder::afterConfigFileParsing"); 423 performance.measure("SolutionBuilder::Config file parsing", "SolutionBuilder::beforeConfigFileParsing", "SolutionBuilder::afterConfigFileParsing"); 424 return parsed; 425 } 426 427 function resolveProjectName(state: SolutionBuilderState, name: string): ResolvedConfigFileName { 428 return resolveConfigFileProjectName(resolvePath(state.currentDirectory, name)); 429 } 430 431 function createBuildOrder(state: SolutionBuilderState, roots: readonly ResolvedConfigFileName[]): AnyBuildOrder { 432 const temporaryMarks = new Map<ResolvedConfigFilePath, true>(); 433 const permanentMarks = new Map<ResolvedConfigFilePath, true>(); 434 const circularityReportStack: string[] = []; 435 let buildOrder: ResolvedConfigFileName[] | undefined; 436 let circularDiagnostics: Diagnostic[] | undefined; 437 for (const root of roots) { 438 visit(root); 439 } 440 441 return circularDiagnostics ? 442 { buildOrder: buildOrder || emptyArray, circularDiagnostics } : 443 buildOrder || emptyArray; 444 445 function visit(configFileName: ResolvedConfigFileName, inCircularContext?: boolean) { 446 const projPath = toResolvedConfigFilePath(state, configFileName); 447 // Already visited 448 if (permanentMarks.has(projPath)) return; 449 // Circular 450 if (temporaryMarks.has(projPath)) { 451 if (!inCircularContext) { 452 (circularDiagnostics || (circularDiagnostics = [])).push( 453 createCompilerDiagnostic( 454 Diagnostics.Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0, 455 circularityReportStack.join("\r\n") 456 ) 457 ); 458 } 459 return; 460 } 461 462 temporaryMarks.set(projPath, true); 463 circularityReportStack.push(configFileName); 464 const parsed = parseConfigFile(state, configFileName, projPath); 465 if (parsed && parsed.projectReferences) { 466 for (const ref of parsed.projectReferences) { 467 const resolvedRefPath = resolveProjectName(state, ref.path); 468 visit(resolvedRefPath, inCircularContext || ref.circular); 469 } 470 } 471 472 circularityReportStack.pop(); 473 permanentMarks.set(projPath, true); 474 (buildOrder || (buildOrder = [])).push(configFileName); 475 } 476 } 477 478 function getBuildOrder(state: SolutionBuilderState) { 479 return state.buildOrder || createStateBuildOrder(state); 480 } 481 482 function createStateBuildOrder(state: SolutionBuilderState) { 483 const buildOrder = createBuildOrder(state, state.rootNames.map(f => resolveProjectName(state, f))); 484 485 // Clear all to ResolvedConfigFilePaths cache to start fresh 486 state.resolvedConfigFilePaths.clear(); 487 488 // TODO(rbuckton): Should be a `Set`, but that requires changing the code below that uses `mutateMapSkippingNewValues` 489 const currentProjects = new Map( 490 getBuildOrderFromAnyBuildOrder(buildOrder).map( 491 resolved => [toResolvedConfigFilePath(state, resolved), true as const]) 492 ); 493 494 const noopOnDelete = { onDeleteValue: noop }; 495 // Config file cache 496 mutateMapSkippingNewValues(state.configFileCache, currentProjects, noopOnDelete); 497 mutateMapSkippingNewValues(state.projectStatus, currentProjects, noopOnDelete); 498 mutateMapSkippingNewValues(state.builderPrograms, currentProjects, noopOnDelete); 499 mutateMapSkippingNewValues(state.diagnostics, currentProjects, noopOnDelete); 500 mutateMapSkippingNewValues(state.projectPendingBuild, currentProjects, noopOnDelete); 501 mutateMapSkippingNewValues(state.projectErrorsReported, currentProjects, noopOnDelete); 502 mutateMapSkippingNewValues(state.buildInfoCache, currentProjects, noopOnDelete); 503 mutateMapSkippingNewValues(state.outputTimeStamps, currentProjects, noopOnDelete); 504 505 // Remove watches for the program no longer in the solution 506 if (state.watch) { 507 mutateMapSkippingNewValues( 508 state.allWatchedConfigFiles, 509 currentProjects, 510 { onDeleteValue: closeFileWatcher } 511 ); 512 513 state.allWatchedExtendedConfigFiles.forEach(watcher => { 514 watcher.projects.forEach(project => { 515 if (!currentProjects.has(project)) { 516 watcher.projects.delete(project); 517 } 518 }); 519 watcher.close(); 520 }); 521 522 mutateMapSkippingNewValues( 523 state.allWatchedWildcardDirectories, 524 currentProjects, 525 { onDeleteValue: existingMap => existingMap.forEach(closeFileWatcherOf) } 526 ); 527 528 mutateMapSkippingNewValues( 529 state.allWatchedInputFiles, 530 currentProjects, 531 { onDeleteValue: existingMap => existingMap.forEach(closeFileWatcher) } 532 ); 533 534 mutateMapSkippingNewValues( 535 state.allWatchedPackageJsonFiles, 536 currentProjects, 537 { onDeleteValue: existingMap => existingMap.forEach(closeFileWatcher) } 538 ); 539 } 540 return state.buildOrder = buildOrder; 541 } 542 543 function getBuildOrderFor(state: SolutionBuilderState, project: string | undefined, onlyReferences: boolean | undefined): AnyBuildOrder | undefined { 544 const resolvedProject = project && resolveProjectName(state, project); 545 const buildOrderFromState = getBuildOrder(state); 546 if (isCircularBuildOrder(buildOrderFromState)) return buildOrderFromState; 547 if (resolvedProject) { 548 const projectPath = toResolvedConfigFilePath(state, resolvedProject); 549 const projectIndex = findIndex( 550 buildOrderFromState, 551 configFileName => toResolvedConfigFilePath(state, configFileName) === projectPath 552 ); 553 if (projectIndex === -1) return undefined; 554 } 555 const buildOrder = resolvedProject ? createBuildOrder(state, [resolvedProject]) as BuildOrder : buildOrderFromState; 556 Debug.assert(!isCircularBuildOrder(buildOrder)); 557 Debug.assert(!onlyReferences || resolvedProject !== undefined); 558 Debug.assert(!onlyReferences || buildOrder[buildOrder.length - 1] === resolvedProject); 559 return onlyReferences ? buildOrder.slice(0, buildOrder.length - 1) : buildOrder; 560 } 561 562 function enableCache(state: SolutionBuilderState) { 563 if (state.cache) { 564 disableCache(state); 565 } 566 567 const { compilerHost, host } = state; 568 569 const originalReadFileWithCache = state.readFileWithCache; 570 const originalGetSourceFile = compilerHost.getSourceFile; 571 572 const { 573 originalReadFile, originalFileExists, originalDirectoryExists, 574 originalCreateDirectory, originalWriteFile, 575 getSourceFileWithCache, readFileWithCache 576 } = changeCompilerHostLikeToUseCache( 577 host, 578 fileName => toPath(state, fileName), 579 (...args) => originalGetSourceFile.call(compilerHost, ...args) 580 ); 581 state.readFileWithCache = readFileWithCache; 582 compilerHost.getSourceFile = getSourceFileWithCache!; 583 584 state.cache = { 585 originalReadFile, 586 originalFileExists, 587 originalDirectoryExists, 588 originalCreateDirectory, 589 originalWriteFile, 590 originalReadFileWithCache, 591 originalGetSourceFile, 592 }; 593 } 594 595 function disableCache(state: SolutionBuilderState) { 596 if (!state.cache) return; 597 598 const { cache, host, compilerHost, extendedConfigCache, moduleResolutionCache, typeReferenceDirectiveResolutionCache } = state; 599 600 host.readFile = cache.originalReadFile; 601 host.fileExists = cache.originalFileExists; 602 host.directoryExists = cache.originalDirectoryExists; 603 host.createDirectory = cache.originalCreateDirectory; 604 host.writeFile = cache.originalWriteFile; 605 compilerHost.getSourceFile = cache.originalGetSourceFile; 606 state.readFileWithCache = cache.originalReadFileWithCache; 607 extendedConfigCache.clear(); 608 moduleResolutionCache?.clear(); 609 typeReferenceDirectiveResolutionCache?.clear(); 610 state.cache = undefined; 611 } 612 613 function clearProjectStatus(state: SolutionBuilderState, resolved: ResolvedConfigFilePath) { 614 state.projectStatus.delete(resolved); 615 state.diagnostics.delete(resolved); 616 } 617 618 function addProjToQueue({ projectPendingBuild }: SolutionBuilderState, proj: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { 619 const value = projectPendingBuild.get(proj); 620 if (value === undefined) { 621 projectPendingBuild.set(proj, reloadLevel); 622 } 623 else if (value < reloadLevel) { 624 projectPendingBuild.set(proj, reloadLevel); 625 } 626 } 627 628 function setupInitialBuild(state: SolutionBuilderState, cancellationToken: CancellationToken | undefined) { 629 // Set initial build if not already built 630 if (!state.allProjectBuildPending) return; 631 state.allProjectBuildPending = false; 632 if (state.options.watch) reportWatchStatus(state, Diagnostics.Starting_compilation_in_watch_mode); 633 enableCache(state); 634 const buildOrder = getBuildOrderFromAnyBuildOrder(getBuildOrder(state)); 635 buildOrder.forEach(configFileName => 636 state.projectPendingBuild.set( 637 toResolvedConfigFilePath(state, configFileName), 638 ConfigFileProgramReloadLevel.None 639 ) 640 ); 641 642 if (cancellationToken) { 643 cancellationToken.throwIfCancellationRequested(); 644 } 645 } 646 647 export enum InvalidatedProjectKind { 648 Build, 649 UpdateBundle, 650 UpdateOutputFileStamps 651 } 652 653 export interface InvalidatedProjectBase { 654 readonly kind: InvalidatedProjectKind; 655 readonly project: ResolvedConfigFileName; 656 /*@internal*/ readonly projectPath: ResolvedConfigFilePath; 657 /*@internal*/ readonly buildOrder: readonly ResolvedConfigFileName[]; 658 /** 659 * To dispose this project and ensure that all the necessary actions are taken and state is updated accordingly 660 */ 661 done(cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers): ExitStatus; 662 getCompilerOptions(): CompilerOptions; 663 getCurrentDirectory(): string; 664 } 665 666 export interface UpdateOutputFileStampsProject extends InvalidatedProjectBase { 667 readonly kind: InvalidatedProjectKind.UpdateOutputFileStamps; 668 updateOutputFileStatmps(): void; 669 } 670 671 export interface BuildInvalidedProject<T extends BuilderProgram> extends InvalidatedProjectBase { 672 readonly kind: InvalidatedProjectKind.Build; 673 /* 674 * Emitting with this builder program without the api provided for this project 675 * can result in build system going into invalid state as files written reflect the state of the project 676 */ 677 getBuilderProgram(): T | undefined; 678 getProgram(): Program | undefined; 679 getSourceFile(fileName: string): SourceFile | undefined; 680 getSourceFiles(): readonly SourceFile[]; 681 getOptionsDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; 682 getGlobalDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; 683 getConfigFileParsingDiagnostics(): readonly Diagnostic[]; 684 getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; 685 getAllDependencies(sourceFile: SourceFile): readonly string[]; 686 getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; 687 getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult<readonly Diagnostic[]>; 688 /* 689 * Calling emit directly with targetSourceFile and emitOnlyDtsFiles set to true is not advised since 690 * emit in build system is responsible in updating status of the project 691 * If called with targetSourceFile and emitOnlyDtsFiles set to true, the emit just passes to underlying builder and 692 * wont reflect the status of file as being emitted in the builder 693 * (if that emit of that source file is required it would be emitted again when making sure invalidated project is completed) 694 * This emit is not considered actual emit (and hence uptodate status is not reflected if 695 */ 696 emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult | undefined; 697 // TODO(shkamat):: investigate later if we can emit even when there are declaration diagnostics 698 // emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult<EmitResult>; 699 } 700 701 export interface UpdateBundleProject<T extends BuilderProgram> extends InvalidatedProjectBase { 702 readonly kind: InvalidatedProjectKind.UpdateBundle; 703 emit(writeFile?: WriteFileCallback, customTransformers?: CustomTransformers): EmitResult | BuildInvalidedProject<T> | undefined; 704 } 705 706 export type InvalidatedProject<T extends BuilderProgram> = UpdateOutputFileStampsProject | BuildInvalidedProject<T> | UpdateBundleProject<T>; 707 708 function doneInvalidatedProject( 709 state: SolutionBuilderState, 710 projectPath: ResolvedConfigFilePath 711 ) { 712 state.projectPendingBuild.delete(projectPath); 713 return state.diagnostics.has(projectPath) ? 714 ExitStatus.DiagnosticsPresent_OutputsSkipped : 715 ExitStatus.Success; 716 } 717 718 function createUpdateOutputFileStampsProject( 719 state: SolutionBuilderState, 720 project: ResolvedConfigFileName, 721 projectPath: ResolvedConfigFilePath, 722 config: ParsedCommandLine, 723 buildOrder: readonly ResolvedConfigFileName[] 724 ): UpdateOutputFileStampsProject { 725 let updateOutputFileStampsPending = true; 726 return { 727 kind: InvalidatedProjectKind.UpdateOutputFileStamps, 728 project, 729 projectPath, 730 buildOrder, 731 getCompilerOptions: () => config.options, 732 getCurrentDirectory: () => state.currentDirectory, 733 updateOutputFileStatmps: () => { 734 updateOutputTimestamps(state, config, projectPath); 735 updateOutputFileStampsPending = false; 736 }, 737 done: () => { 738 if (updateOutputFileStampsPending) { 739 updateOutputTimestamps(state, config, projectPath); 740 } 741 performance.mark("SolutionBuilder::Timestamps only updates"); 742 return doneInvalidatedProject(state, projectPath); 743 } 744 }; 745 } 746 747 enum BuildStep { 748 CreateProgram, 749 SyntaxDiagnostics, 750 SemanticDiagnostics, 751 Emit, 752 EmitBundle, 753 EmitBuildInfo, 754 BuildInvalidatedProjectOfBundle, 755 QueueReferencingProjects, 756 Done 757 } 758 759 function createBuildOrUpdateInvalidedProject<T extends BuilderProgram>( 760 kind: InvalidatedProjectKind.Build | InvalidatedProjectKind.UpdateBundle, 761 state: SolutionBuilderState<T>, 762 project: ResolvedConfigFileName, 763 projectPath: ResolvedConfigFilePath, 764 projectIndex: number, 765 config: ParsedCommandLine, 766 buildOrder: readonly ResolvedConfigFileName[], 767 ): BuildInvalidedProject<T> | UpdateBundleProject<T> { 768 let step = kind === InvalidatedProjectKind.Build ? BuildStep.CreateProgram : BuildStep.EmitBundle; 769 let program: T | undefined; 770 let buildResult: BuildResultFlags | undefined; 771 let invalidatedProjectOfBundle: BuildInvalidedProject<T> | undefined; 772 773 return kind === InvalidatedProjectKind.Build ? 774 { 775 kind, 776 project, 777 projectPath, 778 buildOrder, 779 getCompilerOptions: () => config.options, 780 getCurrentDirectory: () => state.currentDirectory, 781 getBuilderProgram: () => withProgramOrUndefined(identity), 782 getProgram: () => 783 withProgramOrUndefined( 784 program => program.getProgramOrUndefined() 785 ), 786 getSourceFile: fileName => 787 withProgramOrUndefined( 788 program => program.getSourceFile(fileName) 789 ), 790 getSourceFiles: () => 791 withProgramOrEmptyArray( 792 program => program.getSourceFiles() 793 ), 794 getOptionsDiagnostics: cancellationToken => 795 withProgramOrEmptyArray( 796 program => program.getOptionsDiagnostics(cancellationToken) 797 ), 798 getGlobalDiagnostics: cancellationToken => 799 withProgramOrEmptyArray( 800 program => program.getGlobalDiagnostics(cancellationToken) 801 ), 802 getConfigFileParsingDiagnostics: () => 803 withProgramOrEmptyArray( 804 program => program.getConfigFileParsingDiagnostics() 805 ), 806 getSyntacticDiagnostics: (sourceFile, cancellationToken) => 807 withProgramOrEmptyArray( 808 program => program.getSyntacticDiagnostics(sourceFile, cancellationToken) 809 ), 810 getAllDependencies: sourceFile => 811 withProgramOrEmptyArray( 812 program => program.getAllDependencies(sourceFile) 813 ), 814 getSemanticDiagnostics: (sourceFile, cancellationToken) => 815 withProgramOrEmptyArray( 816 program => program.getSemanticDiagnostics(sourceFile, cancellationToken) 817 ), 818 getSemanticDiagnosticsOfNextAffectedFile: (cancellationToken, ignoreSourceFile) => 819 withProgramOrUndefined( 820 program => 821 ((program as any as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile) && 822 (program as any as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile(cancellationToken, ignoreSourceFile) 823 ), 824 emit: (targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers) => { 825 if (targetSourceFile || emitOnlyDtsFiles) { 826 return withProgramOrUndefined( 827 program => program.emit(targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers || state.host.getCustomTransformers?.(project)) 828 ); 829 } 830 executeSteps(BuildStep.SemanticDiagnostics, cancellationToken); 831 if (step === BuildStep.EmitBuildInfo) { 832 return emitBuildInfo(writeFile, cancellationToken); 833 } 834 if (step !== BuildStep.Emit) return undefined; 835 return emit(writeFile, cancellationToken, customTransformers); 836 }, 837 done 838 } : 839 { 840 kind, 841 project, 842 projectPath, 843 buildOrder, 844 getCompilerOptions: () => config.options, 845 getCurrentDirectory: () => state.currentDirectory, 846 emit: (writeFile: WriteFileCallback | undefined, customTransformers: CustomTransformers | undefined) => { 847 if (step !== BuildStep.EmitBundle) return invalidatedProjectOfBundle; 848 return emitBundle(writeFile, customTransformers); 849 }, 850 done, 851 }; 852 853 function done(cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers) { 854 executeSteps(BuildStep.Done, cancellationToken, writeFile, customTransformers); 855 if (kind === InvalidatedProjectKind.Build) performance.mark("SolutionBuilder::Projects built"); 856 else performance.mark("SolutionBuilder::Bundles updated"); 857 return doneInvalidatedProject(state, projectPath); 858 } 859 860 function withProgramOrUndefined<U>(action: (program: T) => U | undefined): U | undefined { 861 executeSteps(BuildStep.CreateProgram); 862 return program && action(program); 863 } 864 865 function withProgramOrEmptyArray<U>(action: (program: T) => readonly U[]): readonly U[] { 866 return withProgramOrUndefined(action) || emptyArray; 867 } 868 869 function createProgram() { 870 Debug.assert(program === undefined); 871 872 if (state.options.dry) { 873 reportStatus(state, Diagnostics.A_non_dry_build_would_build_project_0, project); 874 buildResult = BuildResultFlags.Success; 875 step = BuildStep.QueueReferencingProjects; 876 return; 877 } 878 879 if (state.options.verbose) reportStatus(state, Diagnostics.Building_project_0, project); 880 881 if (config.fileNames.length === 0) { 882 reportAndStoreErrors(state, projectPath, getConfigFileParsingDiagnostics(config)); 883 // Nothing to build - must be a solution file, basically 884 buildResult = BuildResultFlags.None; 885 step = BuildStep.QueueReferencingProjects; 886 return; 887 } 888 889 const { host, compilerHost } = state; 890 state.projectCompilerOptions = config.options; 891 // Update module resolution cache if needed 892 state.moduleResolutionCache?.update(config.options); 893 state.typeReferenceDirectiveResolutionCache?.update(config.options); 894 895 // Create program 896 program = host.createProgram( 897 config.fileNames, 898 config.options, 899 compilerHost, 900 getOldProgram(state, projectPath, config), 901 getConfigFileParsingDiagnostics(config), 902 config.projectReferences 903 ); 904 if (state.watch) { 905 state.lastCachedPackageJsonLookups.set(projectPath, state.moduleResolutionCache && map( 906 state.moduleResolutionCache.getPackageJsonInfoCache().entries(), 907 ([path, data]) => ([state.host.realpath && data ? toPath(state, state.host.realpath(path)) : path, data] as const) 908 )); 909 910 state.builderPrograms.set(projectPath, program); 911 } 912 step++; 913 } 914 915 function handleDiagnostics(diagnostics: readonly Diagnostic[], errorFlags: BuildResultFlags, errorType: string) { 916 if (diagnostics.length) { 917 ({ buildResult, step } = buildErrors( 918 state, 919 projectPath, 920 program, 921 config, 922 diagnostics, 923 errorFlags, 924 errorType 925 )); 926 } 927 else { 928 step++; 929 } 930 } 931 932 function getSyntaxDiagnostics(cancellationToken?: CancellationToken) { 933 Debug.assertIsDefined(program); 934 handleDiagnostics( 935 [ 936 ...program.getConfigFileParsingDiagnostics(), 937 ...program.getOptionsDiagnostics(cancellationToken), 938 ...program.getGlobalDiagnostics(cancellationToken), 939 ...program.getSyntacticDiagnostics(/*sourceFile*/ undefined, cancellationToken) 940 ], 941 BuildResultFlags.SyntaxErrors, 942 "Syntactic" 943 ); 944 } 945 946 function getSemanticDiagnostics(cancellationToken?: CancellationToken) { 947 handleDiagnostics( 948 Debug.checkDefined(program).getSemanticDiagnostics(/*sourceFile*/ undefined, cancellationToken), 949 BuildResultFlags.TypeErrors, 950 "Semantic" 951 ); 952 } 953 954 function emit(writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitResult { 955 Debug.assertIsDefined(program); 956 Debug.assert(step === BuildStep.Emit); 957 // Before emitting lets backup state, so we can revert it back if there are declaration errors to handle emit and declaration errors correctly 958 const saved = program.saveEmitState(); 959 let declDiagnostics: Diagnostic[] | undefined; 960 const reportDeclarationDiagnostics = (d: Diagnostic) => (declDiagnostics || (declDiagnostics = [])).push(d); 961 const outputFiles: OutputFile[] = []; 962 const { emitResult } = emitFilesAndReportErrors( 963 program, 964 reportDeclarationDiagnostics, 965 /*write*/ undefined, 966 /*reportSummary*/ undefined, 967 (name, text, writeByteOrderMark, _onError, _sourceFiles, data) => outputFiles.push({ name, text, writeByteOrderMark, buildInfo: data?.buildInfo }), 968 cancellationToken, 969 /*emitOnlyDts*/ false, 970 customTransformers || state.host.getCustomTransformers?.(project) 971 ); 972 // Don't emit .d.ts if there are decl file errors 973 if (declDiagnostics) { 974 program.restoreEmitState(saved); 975 ({ buildResult, step } = buildErrors( 976 state, 977 projectPath, 978 program, 979 config, 980 declDiagnostics, 981 BuildResultFlags.DeclarationEmitErrors, 982 "Declaration file" 983 )); 984 return { 985 emitSkipped: true, 986 diagnostics: emitResult.diagnostics 987 }; 988 } 989 990 // Actual Emit 991 const { host, compilerHost } = state; 992 const resultFlags = program.hasChangedEmitSignature?.() ? BuildResultFlags.None : BuildResultFlags.DeclarationOutputUnchanged; 993 const emitterDiagnostics = createDiagnosticCollection(); 994 const emittedOutputs = new Map<Path, string>(); 995 const options = program.getCompilerOptions(); 996 const isIncremental = isIncrementalCompilation(options); 997 let outputTimeStampMap: ESMap<Path, Date> | undefined; 998 let now: Date | undefined; 999 outputFiles.forEach(({ name, text, writeByteOrderMark, buildInfo }) => { 1000 const path = toPath(state, name); 1001 emittedOutputs.set(toPath(state, name), name); 1002 if (buildInfo) setBuildInfo(state, buildInfo, projectPath, options, resultFlags); 1003 writeFile(writeFileCallback ? { writeFile: writeFileCallback } : compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); 1004 if (!isIncremental && state.watch) { 1005 (outputTimeStampMap ||= getOutputTimeStampMap(state, projectPath)!).set(path, now ||= getCurrentTime(state.host)); 1006 } 1007 }); 1008 1009 finishEmit( 1010 emitterDiagnostics, 1011 emittedOutputs, 1012 outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(config, !host.useCaseSensitiveFileNames()), 1013 resultFlags 1014 ); 1015 return emitResult; 1016 } 1017 1018 function emitBuildInfo(writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult { 1019 Debug.assertIsDefined(program); 1020 Debug.assert(step === BuildStep.EmitBuildInfo); 1021 const emitResult = program.emitBuildInfo((name, text, writeByteOrderMark, onError, sourceFiles, data) => { 1022 if (data?.buildInfo) setBuildInfo(state, data.buildInfo, projectPath, program!.getCompilerOptions(), BuildResultFlags.DeclarationOutputUnchanged); 1023 if (writeFileCallback) writeFileCallback(name, text, writeByteOrderMark, onError, sourceFiles, data); 1024 else state.compilerHost.writeFile(name, text, writeByteOrderMark, onError, sourceFiles, data); 1025 }, cancellationToken); 1026 if (emitResult.diagnostics.length) { 1027 reportErrors(state, emitResult.diagnostics); 1028 state.diagnostics.set(projectPath, [...state.diagnostics.get(projectPath)!, ...emitResult.diagnostics]); 1029 buildResult = BuildResultFlags.EmitErrors & buildResult!; 1030 } 1031 1032 if (emitResult.emittedFiles && state.write) { 1033 emitResult.emittedFiles.forEach(name => listEmittedFile(state, config, name)); 1034 } 1035 afterProgramDone(state, program, config); 1036 step = BuildStep.QueueReferencingProjects; 1037 return emitResult; 1038 } 1039 1040 function finishEmit( 1041 emitterDiagnostics: DiagnosticCollection, 1042 emittedOutputs: ESMap<Path, string>, 1043 oldestOutputFileName: string, 1044 resultFlags: BuildResultFlags 1045 ) { 1046 const emitDiagnostics = emitterDiagnostics.getDiagnostics(); 1047 if (emitDiagnostics.length) { 1048 ({ buildResult, step } = buildErrors( 1049 state, 1050 projectPath, 1051 program, 1052 config, 1053 emitDiagnostics, 1054 BuildResultFlags.EmitErrors, 1055 "Emit" 1056 )); 1057 return emitDiagnostics; 1058 } 1059 1060 if (state.write) { 1061 emittedOutputs.forEach(name => listEmittedFile(state, config, name)); 1062 } 1063 1064 // Update time stamps for rest of the outputs 1065 updateOutputTimestampsWorker(state, config, projectPath, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs); 1066 state.diagnostics.delete(projectPath); 1067 state.projectStatus.set(projectPath, { 1068 type: UpToDateStatusType.UpToDate, 1069 oldestOutputFileName 1070 }); 1071 afterProgramDone(state, program, config); 1072 step = BuildStep.QueueReferencingProjects; 1073 buildResult = resultFlags; 1074 return emitDiagnostics; 1075 } 1076 1077 function emitBundle(writeFileCallback?: WriteFileCallback, customTransformers?: CustomTransformers): EmitResult | BuildInvalidedProject<T> | undefined { 1078 Debug.assert(kind === InvalidatedProjectKind.UpdateBundle); 1079 if (state.options.dry) { 1080 reportStatus(state, Diagnostics.A_non_dry_build_would_update_output_of_project_0, project); 1081 buildResult = BuildResultFlags.Success; 1082 step = BuildStep.QueueReferencingProjects; 1083 return undefined; 1084 } 1085 1086 if (state.options.verbose) reportStatus(state, Diagnostics.Updating_output_of_project_0, project); 1087 1088 // Update js, and source map 1089 const { compilerHost } = state; 1090 state.projectCompilerOptions = config.options; 1091 const outputFiles = emitUsingBuildInfo( 1092 config, 1093 compilerHost, 1094 ref => { 1095 const refName = resolveProjectName(state, ref.path); 1096 return parseConfigFile(state, refName, toResolvedConfigFilePath(state, refName)); 1097 }, 1098 customTransformers || state.host.getCustomTransformers?.(project) 1099 ); 1100 1101 if (isString(outputFiles)) { 1102 reportStatus(state, Diagnostics.Cannot_update_output_of_project_0_because_there_was_error_reading_file_1, project, relName(state, outputFiles)); 1103 step = BuildStep.BuildInvalidatedProjectOfBundle; 1104 return invalidatedProjectOfBundle = createBuildOrUpdateInvalidedProject( 1105 InvalidatedProjectKind.Build, 1106 state, 1107 project, 1108 projectPath, 1109 projectIndex, 1110 config, 1111 buildOrder 1112 ) as BuildInvalidedProject<T>; 1113 } 1114 1115 // Actual Emit 1116 Debug.assert(!!outputFiles.length); 1117 const emitterDiagnostics = createDiagnosticCollection(); 1118 const emittedOutputs = new Map<Path, string>(); 1119 let resultFlags = BuildResultFlags.DeclarationOutputUnchanged; 1120 const existingBuildInfo = state.buildInfoCache.get(projectPath)!.buildInfo || undefined; 1121 outputFiles.forEach(({ name, text, writeByteOrderMark, buildInfo }) => { 1122 emittedOutputs.set(toPath(state, name), name); 1123 if (buildInfo) { 1124 if ((buildInfo.program as ProgramBundleEmitBuildInfo)?.outSignature !== (existingBuildInfo?.program as ProgramBundleEmitBuildInfo)?.outSignature) { 1125 resultFlags &= ~BuildResultFlags.DeclarationOutputUnchanged; 1126 } 1127 setBuildInfo(state, buildInfo, projectPath, config.options, resultFlags); 1128 } 1129 writeFile(writeFileCallback ? { writeFile: writeFileCallback } : compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); 1130 }); 1131 1132 const emitDiagnostics = finishEmit( 1133 emitterDiagnostics, 1134 emittedOutputs, 1135 outputFiles[0].name, 1136 resultFlags 1137 ); 1138 return { emitSkipped: false, diagnostics: emitDiagnostics }; 1139 } 1140 1141 function executeSteps(till: BuildStep, cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers) { 1142 while (step <= till && step < BuildStep.Done) { 1143 const currentStep = step; 1144 switch (step) { 1145 case BuildStep.CreateProgram: 1146 createProgram(); 1147 break; 1148 1149 case BuildStep.SyntaxDiagnostics: 1150 getSyntaxDiagnostics(cancellationToken); 1151 break; 1152 1153 case BuildStep.SemanticDiagnostics: 1154 getSemanticDiagnostics(cancellationToken); 1155 break; 1156 1157 case BuildStep.Emit: 1158 emit(writeFile, cancellationToken, customTransformers); 1159 break; 1160 1161 case BuildStep.EmitBuildInfo: 1162 emitBuildInfo(writeFile, cancellationToken); 1163 break; 1164 1165 case BuildStep.EmitBundle: 1166 emitBundle(writeFile, customTransformers); 1167 break; 1168 1169 case BuildStep.BuildInvalidatedProjectOfBundle: 1170 Debug.checkDefined(invalidatedProjectOfBundle).done(cancellationToken, writeFile, customTransformers); 1171 step = BuildStep.Done; 1172 break; 1173 1174 case BuildStep.QueueReferencingProjects: 1175 queueReferencingProjects(state, project, projectPath, projectIndex, config, buildOrder, Debug.checkDefined(buildResult)); 1176 step++; 1177 break; 1178 1179 // Should never be done 1180 case BuildStep.Done: 1181 default: 1182 assertType<BuildStep.Done>(step); 1183 1184 } 1185 Debug.assert(step > currentStep); 1186 } 1187 } 1188 } 1189 1190 function needsBuild({ options }: SolutionBuilderState, status: UpToDateStatus, config: ParsedCommandLine) { 1191 if (status.type !== UpToDateStatusType.OutOfDateWithPrepend || options.force) return true; 1192 return config.fileNames.length === 0 || 1193 !!getConfigFileParsingDiagnostics(config).length || 1194 !isIncrementalCompilation(config.options); 1195 } 1196 1197 interface InvalidateProjectCreateInfo { 1198 kind: InvalidatedProjectKind; 1199 status: UpToDateStatus; 1200 project: ResolvedConfigFileName; 1201 projectPath: ResolvedConfigFilePath; 1202 projectIndex: number; 1203 config: ParsedCommandLine; 1204 } 1205 1206 function getNextInvalidatedProjectCreateInfo<T extends BuilderProgram>( 1207 state: SolutionBuilderState<T>, 1208 buildOrder: AnyBuildOrder, 1209 reportQueue: boolean 1210 ): InvalidateProjectCreateInfo | undefined { 1211 if (!state.projectPendingBuild.size) return undefined; 1212 if (isCircularBuildOrder(buildOrder)) return undefined; 1213 1214 const { options, projectPendingBuild } = state; 1215 for (let projectIndex = 0; projectIndex < buildOrder.length; projectIndex++) { 1216 const project = buildOrder[projectIndex]; 1217 const projectPath = toResolvedConfigFilePath(state, project); 1218 const reloadLevel = state.projectPendingBuild.get(projectPath); 1219 if (reloadLevel === undefined) continue; 1220 1221 if (reportQueue) { 1222 reportQueue = false; 1223 reportBuildQueue(state, buildOrder); 1224 } 1225 1226 const config = parseConfigFile(state, project, projectPath); 1227 if (!config) { 1228 reportParseConfigFileDiagnostic(state, projectPath); 1229 projectPendingBuild.delete(projectPath); 1230 continue; 1231 } 1232 1233 if (reloadLevel === ConfigFileProgramReloadLevel.Full) { 1234 watchConfigFile(state, project, projectPath, config); 1235 watchExtendedConfigFiles(state, projectPath, config); 1236 watchWildCardDirectories(state, project, projectPath, config); 1237 watchInputFiles(state, project, projectPath, config); 1238 watchPackageJsonFiles(state, project, projectPath, config); 1239 } 1240 else if (reloadLevel === ConfigFileProgramReloadLevel.Partial) { 1241 // Update file names 1242 config.fileNames = getFileNamesFromConfigSpecs(config.options.configFile!.configFileSpecs!, getDirectoryPath(project), config.options, state.parseConfigFileHost); 1243 updateErrorForNoInputFiles(config.fileNames, project, config.options.configFile!.configFileSpecs!, config.errors, canJsonReportNoInputFiles(config.raw)); 1244 watchInputFiles(state, project, projectPath, config); 1245 watchPackageJsonFiles(state, project, projectPath, config); 1246 } 1247 1248 const status = getUpToDateStatus(state, config, projectPath); 1249 if (!options.force) { 1250 if (status.type === UpToDateStatusType.UpToDate) { 1251 verboseReportProjectStatus(state, project, status); 1252 reportAndStoreErrors(state, projectPath, getConfigFileParsingDiagnostics(config)); 1253 projectPendingBuild.delete(projectPath); 1254 // Up to date, skip 1255 if (options.dry) { 1256 // In a dry build, inform the user of this fact 1257 reportStatus(state, Diagnostics.Project_0_is_up_to_date, project); 1258 } 1259 continue; 1260 } 1261 1262 if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes || status.type === UpToDateStatusType.UpToDateWithInputFileText) { 1263 reportAndStoreErrors(state, projectPath, getConfigFileParsingDiagnostics(config)); 1264 return { 1265 kind: InvalidatedProjectKind.UpdateOutputFileStamps, 1266 status, 1267 project, 1268 projectPath, 1269 projectIndex, 1270 config 1271 }; 1272 } 1273 } 1274 1275 if (status.type === UpToDateStatusType.UpstreamBlocked) { 1276 verboseReportProjectStatus(state, project, status); 1277 reportAndStoreErrors(state, projectPath, getConfigFileParsingDiagnostics(config)); 1278 projectPendingBuild.delete(projectPath); 1279 if (options.verbose) { 1280 reportStatus( 1281 state, 1282 status.upstreamProjectBlocked ? 1283 Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_was_not_built : 1284 Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, 1285 project, 1286 status.upstreamProjectName 1287 ); 1288 } 1289 continue; 1290 } 1291 1292 if (status.type === UpToDateStatusType.ContainerOnly) { 1293 verboseReportProjectStatus(state, project, status); 1294 reportAndStoreErrors(state, projectPath, getConfigFileParsingDiagnostics(config)); 1295 projectPendingBuild.delete(projectPath); 1296 // Do nothing 1297 continue; 1298 } 1299 1300 return { 1301 kind: needsBuild(state, status, config) ? 1302 InvalidatedProjectKind.Build : 1303 InvalidatedProjectKind.UpdateBundle, 1304 status, 1305 project, 1306 projectPath, 1307 projectIndex, 1308 config, 1309 }; 1310 } 1311 1312 return undefined; 1313 } 1314 1315 function createInvalidatedProjectWithInfo<T extends BuilderProgram>( 1316 state: SolutionBuilderState<T>, 1317 info: InvalidateProjectCreateInfo, 1318 buildOrder: AnyBuildOrder, 1319 ) { 1320 verboseReportProjectStatus(state, info.project, info.status); 1321 return info.kind !== InvalidatedProjectKind.UpdateOutputFileStamps ? 1322 createBuildOrUpdateInvalidedProject( 1323 info.kind, 1324 state, 1325 info.project, 1326 info.projectPath, 1327 info.projectIndex, 1328 info.config, 1329 buildOrder as BuildOrder, 1330 ) : 1331 createUpdateOutputFileStampsProject( 1332 state, 1333 info.project, 1334 info.projectPath, 1335 info.config, 1336 buildOrder as BuildOrder 1337 ); 1338 } 1339 1340 function getNextInvalidatedProject<T extends BuilderProgram>( 1341 state: SolutionBuilderState<T>, 1342 buildOrder: AnyBuildOrder, 1343 reportQueue: boolean 1344 ): InvalidatedProject<T> | undefined { 1345 const info = getNextInvalidatedProjectCreateInfo(state, buildOrder, reportQueue); 1346 if (!info) return info; 1347 return createInvalidatedProjectWithInfo(state, info, buildOrder); 1348 } 1349 1350 function listEmittedFile({ write }: SolutionBuilderState, proj: ParsedCommandLine, file: string) { 1351 if (write && proj.options.listEmittedFiles) { 1352 write(`TSFILE: ${file}`); 1353 } 1354 } 1355 1356 function getOldProgram<T extends BuilderProgram>({ options, builderPrograms, compilerHost }: SolutionBuilderState<T>, proj: ResolvedConfigFilePath, parsed: ParsedCommandLine) { 1357 if (options.force) return undefined; 1358 const value = builderPrograms.get(proj); 1359 if (value) return value; 1360 return readBuilderProgram(parsed.options, compilerHost) as any as T; 1361 } 1362 1363 function afterProgramDone<T extends BuilderProgram>( 1364 state: SolutionBuilderState<T>, 1365 program: T | undefined, 1366 config: ParsedCommandLine 1367 ) { 1368 if (program) { 1369 if (state.write) listFiles(program, state.write); 1370 if (state.host.afterProgramEmitAndDiagnostics) { 1371 state.host.afterProgramEmitAndDiagnostics(program); 1372 } 1373 program.releaseProgram(); 1374 } 1375 else if (state.host.afterEmitBundle) { 1376 state.host.afterEmitBundle(config); 1377 } 1378 state.projectCompilerOptions = state.baseCompilerOptions; 1379 } 1380 1381 function buildErrors<T extends BuilderProgram>( 1382 state: SolutionBuilderState<T>, 1383 resolvedPath: ResolvedConfigFilePath, 1384 program: T | undefined, 1385 config: ParsedCommandLine, 1386 diagnostics: readonly Diagnostic[], 1387 buildResult: BuildResultFlags, 1388 errorType: string, 1389 ) { 1390 // Since buildinfo has changeset and diagnostics when doing multi file emit, only --out cannot emit buildinfo if it has errors 1391 const canEmitBuildInfo = program && !outFile(program.getCompilerOptions()); 1392 1393 reportAndStoreErrors(state, resolvedPath, diagnostics); 1394 state.projectStatus.set(resolvedPath, { type: UpToDateStatusType.Unbuildable, reason: `${errorType} errors` }); 1395 if (canEmitBuildInfo) return { buildResult, step: BuildStep.EmitBuildInfo }; 1396 afterProgramDone(state, program, config); 1397 return { buildResult, step: BuildStep.QueueReferencingProjects }; 1398 } 1399 1400 function isFileWatcherWithModifiedTime(value: FileWatcherWithModifiedTime | Date): value is FileWatcherWithModifiedTime { 1401 return !!(value as FileWatcherWithModifiedTime).watcher; 1402 } 1403 1404 function getModifiedTime(state: SolutionBuilderState, fileName: string): Date { 1405 const path = toPath(state, fileName); 1406 const existing = state.filesWatched.get(path); 1407 if (state.watch && !!existing) { 1408 if (!isFileWatcherWithModifiedTime(existing)) return existing; 1409 if (existing.modifiedTime) return existing.modifiedTime; 1410 } 1411 // In watch mode we store the modified times in the cache 1412 // This is either Date | FileWatcherWithModifiedTime because we query modified times first and 1413 // then after complete compilation of the project, watch the files so we dont want to loose these modified times. 1414 const result = ts.getModifiedTime(state.host, fileName); 1415 if (state.watch) { 1416 if (existing) (existing as FileWatcherWithModifiedTime).modifiedTime = result; 1417 else state.filesWatched.set(path, result); 1418 } 1419 return result; 1420 } 1421 1422 function watchFile(state: SolutionBuilderState, file: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined, watchType: WatchType, project?: ResolvedConfigFileName): FileWatcher { 1423 const path = toPath(state, file); 1424 const existing = state.filesWatched.get(path); 1425 if (existing && isFileWatcherWithModifiedTime(existing)) { 1426 existing.callbacks.push(callback); 1427 } 1428 else { 1429 const watcher = state.watchFile( 1430 file, 1431 (fileName, eventKind, modifiedTime) => { 1432 const existing = Debug.checkDefined(state.filesWatched.get(path)); 1433 Debug.assert(isFileWatcherWithModifiedTime(existing)); 1434 existing.modifiedTime = modifiedTime; 1435 existing.callbacks.forEach(cb => cb(fileName, eventKind, modifiedTime)); 1436 }, 1437 pollingInterval, 1438 options, 1439 watchType, 1440 project 1441 ); 1442 state.filesWatched.set(path, { callbacks: [callback], watcher, modifiedTime: existing }); 1443 } 1444 1445 return { 1446 close: () => { 1447 const existing = Debug.checkDefined(state.filesWatched.get(path)); 1448 Debug.assert(isFileWatcherWithModifiedTime(existing)); 1449 if (existing.callbacks.length === 1) { 1450 state.filesWatched.delete(path); 1451 closeFileWatcherOf(existing); 1452 } 1453 else { 1454 unorderedRemoveItem(existing.callbacks, callback); 1455 } 1456 } 1457 }; 1458 } 1459 1460 function getOutputTimeStampMap(state: SolutionBuilderState, resolvedConfigFilePath: ResolvedConfigFilePath) { 1461 // Output timestamps are stored only in watch mode 1462 if (!state.watch) return undefined; 1463 let result = state.outputTimeStamps.get(resolvedConfigFilePath); 1464 if (!result) state.outputTimeStamps.set(resolvedConfigFilePath, result = new Map()); 1465 return result; 1466 } 1467 1468 function setBuildInfo( 1469 state: SolutionBuilderState, 1470 buildInfo: BuildInfo, 1471 resolvedConfigPath: ResolvedConfigFilePath, 1472 options: CompilerOptions, 1473 resultFlags: BuildResultFlags, 1474 ) { 1475 const buildInfoPath = getTsBuildInfoEmitOutputFilePath(options)!; 1476 const existing = getBuildInfoCacheEntry(state, buildInfoPath, resolvedConfigPath); 1477 const modifiedTime = getCurrentTime(state.host); 1478 if (existing) { 1479 existing.buildInfo = buildInfo; 1480 existing.modifiedTime = modifiedTime; 1481 if (!(resultFlags & BuildResultFlags.DeclarationOutputUnchanged)) existing.latestChangedDtsTime = modifiedTime; 1482 } 1483 else { 1484 state.buildInfoCache.set(resolvedConfigPath, { 1485 path: toPath(state, buildInfoPath), 1486 buildInfo, 1487 modifiedTime, 1488 latestChangedDtsTime: resultFlags & BuildResultFlags.DeclarationOutputUnchanged ? undefined : modifiedTime, 1489 }); 1490 } 1491 } 1492 1493 function getBuildInfoCacheEntry(state: SolutionBuilderState, buildInfoPath: string, resolvedConfigPath: ResolvedConfigFilePath) { 1494 const path = toPath(state, buildInfoPath); 1495 const existing = state.buildInfoCache.get(resolvedConfigPath); 1496 return existing?.path === path ? existing : undefined; 1497 } 1498 1499 function getBuildInfo(state: SolutionBuilderState, buildInfoPath: string, resolvedConfigPath: ResolvedConfigFilePath, modifiedTime: Date | undefined): BuildInfo | undefined { 1500 const path = toPath(state, buildInfoPath); 1501 const existing = state.buildInfoCache.get(resolvedConfigPath); 1502 if (existing !== undefined && existing.path === path) { 1503 return existing.buildInfo || undefined; 1504 } 1505 const value = state.readFileWithCache(buildInfoPath); 1506 const buildInfo = value ? ts.getBuildInfo(buildInfoPath, value) : undefined; 1507 state.buildInfoCache.set(resolvedConfigPath, { path, buildInfo: buildInfo || false, modifiedTime: modifiedTime || missingFileModifiedTime }); 1508 return buildInfo; 1509 } 1510 1511 function checkConfigFileUpToDateStatus(state: SolutionBuilderState, configFile: string, oldestOutputFileTime: Date, oldestOutputFileName: string): Status.OutOfDateWithSelf | undefined { 1512 // Check tsconfig time 1513 const tsconfigTime = getModifiedTime(state, configFile); 1514 if (oldestOutputFileTime < tsconfigTime) { 1515 return { 1516 type: UpToDateStatusType.OutOfDateWithSelf, 1517 outOfDateOutputFileName: oldestOutputFileName, 1518 newerInputFileName: configFile 1519 }; 1520 } 1521 } 1522 1523 function getUpToDateStatusWorker(state: SolutionBuilderState, project: ParsedCommandLine, resolvedPath: ResolvedConfigFilePath): UpToDateStatus { 1524 // Container if no files are specified in the project 1525 if (!project.fileNames.length && !canJsonReportNoInputFiles(project.raw)) { 1526 return { 1527 type: UpToDateStatusType.ContainerOnly 1528 }; 1529 } 1530 1531 // Fast check to see if reference projects are upto date and error free 1532 let referenceStatuses; 1533 const force = !!state.options.force; 1534 if (project.projectReferences) { 1535 state.projectStatus.set(resolvedPath, { type: UpToDateStatusType.ComputingUpstream }); 1536 for (const ref of project.projectReferences) { 1537 const resolvedRef = resolveProjectReferencePath(ref); 1538 const resolvedRefPath = toResolvedConfigFilePath(state, resolvedRef); 1539 const resolvedConfig = parseConfigFile(state, resolvedRef, resolvedRefPath)!; 1540 const refStatus = getUpToDateStatus(state, resolvedConfig, resolvedRefPath); 1541 1542 // Its a circular reference ignore the status of this project 1543 if (refStatus.type === UpToDateStatusType.ComputingUpstream || 1544 refStatus.type === UpToDateStatusType.ContainerOnly) { // Container only ignore this project 1545 continue; 1546 } 1547 1548 // An upstream project is blocked 1549 if (refStatus.type === UpToDateStatusType.Unbuildable || 1550 refStatus.type === UpToDateStatusType.UpstreamBlocked) { 1551 return { 1552 type: UpToDateStatusType.UpstreamBlocked, 1553 upstreamProjectName: ref.path, 1554 upstreamProjectBlocked: refStatus.type === UpToDateStatusType.UpstreamBlocked 1555 }; 1556 } 1557 1558 // If the upstream project is out of date, then so are we (someone shouldn't have asked, though?) 1559 if (refStatus.type !== UpToDateStatusType.UpToDate) { 1560 return { 1561 type: UpToDateStatusType.UpstreamOutOfDate, 1562 upstreamProjectName: ref.path 1563 }; 1564 } 1565 1566 if (!force) (referenceStatuses ||= []).push({ ref, refStatus, resolvedRefPath, resolvedConfig }); 1567 } 1568 } 1569 if (force) return { type: UpToDateStatusType.ForceBuild }; 1570 1571 // Check buildinfo first 1572 const { host } = state; 1573 const buildInfoPath = getTsBuildInfoEmitOutputFilePath(project.options); 1574 let oldestOutputFileName: string | undefined; 1575 let oldestOutputFileTime = maximumDate; 1576 let buildInfoTime: Date | undefined; 1577 let buildInfoProgram: ProgramBuildInfo | undefined; 1578 let buildInfoVersionMap: ESMap<Path, string> | undefined; 1579 if (buildInfoPath) { 1580 const buildInfoCacheEntry = getBuildInfoCacheEntry(state, buildInfoPath, resolvedPath); 1581 buildInfoTime = buildInfoCacheEntry?.modifiedTime || ts.getModifiedTime(host, buildInfoPath); 1582 if (buildInfoTime === missingFileModifiedTime) { 1583 if (!buildInfoCacheEntry) { 1584 state.buildInfoCache.set(resolvedPath, { 1585 path: toPath(state, buildInfoPath), 1586 buildInfo: false, 1587 modifiedTime: buildInfoTime 1588 }); 1589 } 1590 return { 1591 type: UpToDateStatusType.OutputMissing, 1592 missingOutputFileName: buildInfoPath 1593 }; 1594 } 1595 1596 const buildInfo = getBuildInfo(state, buildInfoPath, resolvedPath, buildInfoTime); 1597 if (!buildInfo) { 1598 // Error reading buildInfo 1599 return { 1600 type: UpToDateStatusType.ErrorReadingFile, 1601 fileName: buildInfoPath 1602 }; 1603 } 1604 if ((buildInfo.bundle || buildInfo.program) && buildInfo.version !== version) { 1605 return { 1606 type: UpToDateStatusType.TsVersionOutputOfDate, 1607 version: buildInfo.version 1608 }; 1609 } 1610 1611 if (buildInfo.program) { 1612 // If there are pending changes that are not emitted, project is out of date 1613 // When there are syntax errors, changeFileSet will have list of files changed (irrespective of noEmit) 1614 // But in case of semantic error we need special treatment. 1615 // Checking presence of affectedFilesPendingEmit list is fast and good way to tell if there were semantic errors and file emit was blocked 1616 // But if noEmit is true, affectedFilesPendingEmit will have file list even if there are no semantic errors to preserve list of files to be emitted when running with noEmit false 1617 // So with noEmit set to true, check on semantic diagnostics needs to be explicit as oppose to when it is false when only files pending emit is sufficient 1618 if ((buildInfo.program as ProgramMultiFileEmitBuildInfo).changeFileSet?.length || 1619 (!project.options.noEmit ? 1620 (buildInfo.program as ProgramMultiFileEmitBuildInfo).affectedFilesPendingEmit?.length : 1621 some((buildInfo.program as ProgramMultiFileEmitBuildInfo).semanticDiagnosticsPerFile, isArray)) 1622 ) { 1623 return { 1624 type: UpToDateStatusType.OutOfDateBuildInfo, 1625 buildInfoFile: buildInfoPath 1626 }; 1627 } 1628 buildInfoProgram = buildInfo.program; 1629 } 1630 1631 oldestOutputFileTime = buildInfoTime; 1632 oldestOutputFileName = buildInfoPath; 1633 } 1634 1635 // Check input files 1636 let newestInputFileName: string = undefined!; 1637 let newestInputFileTime = minimumDate; 1638 /** True if input file has changed timestamp but text is not changed, we can then do only timestamp updates on output to make it look up-to-date later */ 1639 let pseudoInputUpToDate = false; 1640 // Get timestamps of input files 1641 for (const inputFile of project.fileNames) { 1642 const inputTime = getModifiedTime(state, inputFile); 1643 if (inputTime === missingFileModifiedTime) { 1644 return { 1645 type: UpToDateStatusType.Unbuildable, 1646 reason: `${inputFile} does not exist` 1647 }; 1648 } 1649 1650 // If an buildInfo is older than the newest input, we can stop checking 1651 if (buildInfoTime && buildInfoTime < inputTime) { 1652 let version: string | undefined; 1653 let currentVersion: string | undefined; 1654 if (buildInfoProgram) { 1655 // Read files and see if they are same, read is anyways cached 1656 if (!buildInfoVersionMap) buildInfoVersionMap = getBuildInfoFileVersionMap(buildInfoProgram, buildInfoPath!, host); 1657 version = buildInfoVersionMap.get(toPath(state, inputFile)); 1658 const text = version ? state.readFileWithCache(inputFile) : undefined; 1659 currentVersion = text !== undefined ? (host.createHash || generateDjb2Hash)(text) : undefined; 1660 if (version && version === currentVersion) pseudoInputUpToDate = true; 1661 } 1662 1663 if (!version || version !== currentVersion) { 1664 return { 1665 type: UpToDateStatusType.OutOfDateWithSelf, 1666 outOfDateOutputFileName: buildInfoPath!, 1667 newerInputFileName: inputFile 1668 }; 1669 } 1670 } 1671 1672 if (inputTime > newestInputFileTime) { 1673 newestInputFileName = inputFile; 1674 newestInputFileTime = inputTime; 1675 } 1676 } 1677 1678 // Now see if all outputs are newer than the newest input 1679 // Dont check output timestamps if we have buildinfo telling us output is uptodate 1680 if (!buildInfoPath) { 1681 // Collect the expected outputs of this project 1682 const outputs = getAllProjectOutputs(project, !host.useCaseSensitiveFileNames()); 1683 const outputTimeStampMap = getOutputTimeStampMap(state, resolvedPath); 1684 for (const output of outputs) { 1685 const path = toPath(state, output); 1686 // Output is missing; can stop checking 1687 let outputTime = outputTimeStampMap?.get(path); 1688 if (!outputTime) { 1689 outputTime = ts.getModifiedTime(state.host, output); 1690 outputTimeStampMap?.set(path, outputTime); 1691 } 1692 1693 if (outputTime === missingFileModifiedTime) { 1694 return { 1695 type: UpToDateStatusType.OutputMissing, 1696 missingOutputFileName: output 1697 }; 1698 } 1699 1700 // If an output is older than the newest input, we can stop checking 1701 if (outputTime < newestInputFileTime) { 1702 return { 1703 type: UpToDateStatusType.OutOfDateWithSelf, 1704 outOfDateOutputFileName: output, 1705 newerInputFileName: newestInputFileName 1706 }; 1707 } 1708 1709 // No need to get newestDeclarationFileContentChangedTime since thats needed only for composite projects 1710 // And composite projects are the only ones that can be referenced 1711 if (outputTime < oldestOutputFileTime) { 1712 oldestOutputFileTime = outputTime; 1713 oldestOutputFileName = output; 1714 } 1715 } 1716 } 1717 1718 const buildInfoCacheEntry = state.buildInfoCache.get(resolvedPath); 1719 /** Inputs are up-to-date, just need either timestamp update or bundle prepend manipulation to make it look up-to-date */ 1720 let pseudoUpToDate = false; 1721 let usesPrepend = false; 1722 let upstreamChangedProject: string | undefined; 1723 if (referenceStatuses) { 1724 for (const { ref, refStatus, resolvedConfig, resolvedRefPath } of referenceStatuses) { 1725 usesPrepend = usesPrepend || !!(ref.prepend); 1726 // If the upstream project's newest file is older than our oldest output, we 1727 // can't be out of date because of it 1728 if (refStatus.newestInputFileTime && refStatus.newestInputFileTime <= oldestOutputFileTime) { 1729 continue; 1730 } 1731 1732 // Check if tsbuildinfo path is shared, then we need to rebuild 1733 if (buildInfoCacheEntry && hasSameBuildInfo(state, buildInfoCacheEntry, resolvedRefPath)) { 1734 return { 1735 type: UpToDateStatusType.OutOfDateWithUpstream, 1736 outOfDateOutputFileName: buildInfoPath!, 1737 newerProjectName: ref.path 1738 }; 1739 } 1740 1741 // If the upstream project has only change .d.ts files, and we've built 1742 // *after* those files, then we're "psuedo up to date" and eligible for a fast rebuild 1743 const newestDeclarationFileContentChangedTime = getLatestChangedDtsTime(state, resolvedConfig.options, resolvedRefPath); 1744 if (newestDeclarationFileContentChangedTime && newestDeclarationFileContentChangedTime <= oldestOutputFileTime) { 1745 pseudoUpToDate = true; 1746 upstreamChangedProject = ref.path; 1747 continue; 1748 } 1749 1750 // We have an output older than an upstream output - we are out of date 1751 Debug.assert(oldestOutputFileName !== undefined, "Should have an oldest output filename here"); 1752 return { 1753 type: UpToDateStatusType.OutOfDateWithUpstream, 1754 outOfDateOutputFileName: oldestOutputFileName, 1755 newerProjectName: ref.path 1756 }; 1757 } 1758 } 1759 1760 // Check tsconfig time 1761 const configStatus = checkConfigFileUpToDateStatus(state, project.options.configFilePath!, oldestOutputFileTime, oldestOutputFileName!); 1762 if (configStatus) return configStatus; 1763 1764 // Check extended config time 1765 const extendedConfigStatus = forEach(project.options.configFile!.extendedSourceFiles || emptyArray, configFile => checkConfigFileUpToDateStatus(state, configFile, oldestOutputFileTime, oldestOutputFileName!)); 1766 if (extendedConfigStatus) return extendedConfigStatus; 1767 1768 // Check package file time 1769 const dependentPackageFileStatus = forEach( 1770 state.lastCachedPackageJsonLookups.get(resolvedPath) || emptyArray, 1771 ([path]) => checkConfigFileUpToDateStatus(state, path, oldestOutputFileTime, oldestOutputFileName!) 1772 ); 1773 if (dependentPackageFileStatus) return dependentPackageFileStatus; 1774 1775 if (usesPrepend && pseudoUpToDate) { 1776 return { 1777 type: UpToDateStatusType.OutOfDateWithPrepend, 1778 outOfDateOutputFileName: oldestOutputFileName!, 1779 newerProjectName: upstreamChangedProject! 1780 }; 1781 } 1782 1783 // Up to date 1784 return { 1785 type: pseudoUpToDate ? 1786 UpToDateStatusType.UpToDateWithUpstreamTypes : 1787 pseudoInputUpToDate ? 1788 UpToDateStatusType.UpToDateWithInputFileText : 1789 UpToDateStatusType.UpToDate, 1790 newestInputFileTime, 1791 newestInputFileName, 1792 oldestOutputFileName: oldestOutputFileName! 1793 }; 1794 } 1795 1796 function hasSameBuildInfo(state: SolutionBuilderState, buildInfoCacheEntry: BuildInfoCacheEntry, resolvedRefPath: ResolvedConfigFilePath) { 1797 const refBuildInfo = state.buildInfoCache.get(resolvedRefPath)!; 1798 return refBuildInfo.path === buildInfoCacheEntry.path; 1799 } 1800 1801 function getUpToDateStatus(state: SolutionBuilderState, project: ParsedCommandLine | undefined, resolvedPath: ResolvedConfigFilePath): UpToDateStatus { 1802 if (project === undefined) { 1803 return { type: UpToDateStatusType.Unbuildable, reason: "File deleted mid-build" }; 1804 } 1805 1806 const prior = state.projectStatus.get(resolvedPath); 1807 if (prior !== undefined) { 1808 return prior; 1809 } 1810 1811 performance.mark("SolutionBuilder::beforeUpToDateCheck"); 1812 const actual = getUpToDateStatusWorker(state, project, resolvedPath); 1813 performance.mark("SolutionBuilder::afterUpToDateCheck"); 1814 performance.measure("SolutionBuilder::Up-to-date check", "SolutionBuilder::beforeUpToDateCheck", "SolutionBuilder::afterUpToDateCheck"); 1815 state.projectStatus.set(resolvedPath, actual); 1816 return actual; 1817 } 1818 1819 function updateOutputTimestampsWorker( 1820 state: SolutionBuilderState, 1821 proj: ParsedCommandLine, 1822 projectPath: ResolvedConfigFilePath, 1823 verboseMessage: DiagnosticMessage, 1824 skipOutputs?: ESMap<Path, string> 1825 ) { 1826 if (proj.options.noEmit) return; 1827 let now: Date | undefined; 1828 const buildInfoPath = getTsBuildInfoEmitOutputFilePath(proj.options); 1829 if (buildInfoPath) { 1830 // For incremental projects, only buildinfo needs to be upto date with timestamp check 1831 // as we dont check output files for up-to-date ness 1832 if (!skipOutputs?.has(toPath(state, buildInfoPath))) { 1833 if (!!state.options.verbose) reportStatus(state, verboseMessage, proj.options.configFilePath!); 1834 state.host.setModifiedTime(buildInfoPath, now = getCurrentTime(state.host)); 1835 getBuildInfoCacheEntry(state, buildInfoPath, projectPath)!.modifiedTime = now; 1836 } 1837 state.outputTimeStamps.delete(projectPath); 1838 return; 1839 } 1840 1841 const { host } = state; 1842 const outputs = getAllProjectOutputs(proj, !host.useCaseSensitiveFileNames()); 1843 const outputTimeStampMap = getOutputTimeStampMap(state, projectPath); 1844 const modifiedOutputs = outputTimeStampMap ? new Set<Path>() : undefined; 1845 if (!skipOutputs || outputs.length !== skipOutputs.size) { 1846 let reportVerbose = !!state.options.verbose; 1847 for (const file of outputs) { 1848 const path = toPath(state, file); 1849 if (skipOutputs?.has(path)) continue; 1850 if (reportVerbose) { 1851 reportVerbose = false; 1852 reportStatus(state, verboseMessage, proj.options.configFilePath!); 1853 } 1854 host.setModifiedTime(file, now ||= getCurrentTime(state.host)); 1855 // Store output timestamps in a map because non incremental build will need to check them to determine up-to-dateness 1856 if (outputTimeStampMap) { 1857 outputTimeStampMap.set(path, now); 1858 modifiedOutputs!.add(path); 1859 } 1860 } 1861 } 1862 1863 // Clear out timestamps not in output list any more 1864 outputTimeStampMap?.forEach((_value, key) => { 1865 if (!skipOutputs?.has(key) && !modifiedOutputs!.has(key)) outputTimeStampMap.delete(key); 1866 }); 1867 } 1868 1869 function getLatestChangedDtsTime(state: SolutionBuilderState, options: CompilerOptions, resolvedConfigPath: ResolvedConfigFilePath) { 1870 if (!options.composite) return undefined; 1871 const entry = Debug.checkDefined(state.buildInfoCache.get(resolvedConfigPath)); 1872 if (entry.latestChangedDtsTime !== undefined) return entry.latestChangedDtsTime || undefined; 1873 const latestChangedDtsTime = entry.buildInfo && entry.buildInfo.program && entry.buildInfo.program.latestChangedDtsFile ? 1874 state.host.getModifiedTime(getNormalizedAbsolutePath(entry.buildInfo.program.latestChangedDtsFile, getDirectoryPath(entry.path))) : 1875 undefined; 1876 entry.latestChangedDtsTime = latestChangedDtsTime || false; 1877 return latestChangedDtsTime; 1878 } 1879 1880 function updateOutputTimestamps(state: SolutionBuilderState, proj: ParsedCommandLine, resolvedPath: ResolvedConfigFilePath) { 1881 if (state.options.dry) { 1882 return reportStatus(state, Diagnostics.A_non_dry_build_would_update_timestamps_for_output_of_project_0, proj.options.configFilePath!); 1883 } 1884 updateOutputTimestampsWorker(state, proj, resolvedPath, Diagnostics.Updating_output_timestamps_of_project_0); 1885 state.projectStatus.set(resolvedPath, { 1886 type: UpToDateStatusType.UpToDate, 1887 oldestOutputFileName: getFirstProjectOutput(proj, !state.host.useCaseSensitiveFileNames()) 1888 }); 1889 } 1890 1891 function queueReferencingProjects( 1892 state: SolutionBuilderState, 1893 project: ResolvedConfigFileName, 1894 projectPath: ResolvedConfigFilePath, 1895 projectIndex: number, 1896 config: ParsedCommandLine, 1897 buildOrder: readonly ResolvedConfigFileName[], 1898 buildResult: BuildResultFlags 1899 ) { 1900 // Queue only if there are no errors 1901 if (buildResult & BuildResultFlags.AnyErrors) return; 1902 // Only composite projects can be referenced by other projects 1903 if (!config.options.composite) return; 1904 // Always use build order to queue projects 1905 for (let index = projectIndex + 1; index < buildOrder.length; index++) { 1906 const nextProject = buildOrder[index]; 1907 const nextProjectPath = toResolvedConfigFilePath(state, nextProject); 1908 if (state.projectPendingBuild.has(nextProjectPath)) continue; 1909 1910 const nextProjectConfig = parseConfigFile(state, nextProject, nextProjectPath); 1911 if (!nextProjectConfig || !nextProjectConfig.projectReferences) continue; 1912 for (const ref of nextProjectConfig.projectReferences) { 1913 const resolvedRefPath = resolveProjectName(state, ref.path); 1914 if (toResolvedConfigFilePath(state, resolvedRefPath) !== projectPath) continue; 1915 // If the project is referenced with prepend, always build downstream projects, 1916 // If declaration output is changed, build the project 1917 // otherwise mark the project UpToDateWithUpstreamTypes so it updates output time stamps 1918 const status = state.projectStatus.get(nextProjectPath); 1919 if (status) { 1920 switch (status.type) { 1921 case UpToDateStatusType.UpToDate: 1922 if (buildResult & BuildResultFlags.DeclarationOutputUnchanged) { 1923 if (ref.prepend) { 1924 state.projectStatus.set(nextProjectPath, { 1925 type: UpToDateStatusType.OutOfDateWithPrepend, 1926 outOfDateOutputFileName: status.oldestOutputFileName, 1927 newerProjectName: project 1928 }); 1929 } 1930 else { 1931 status.type = UpToDateStatusType.UpToDateWithUpstreamTypes; 1932 } 1933 break; 1934 } 1935 // falls through 1936 1937 case UpToDateStatusType.UpToDateWithInputFileText: 1938 case UpToDateStatusType.UpToDateWithUpstreamTypes: 1939 case UpToDateStatusType.OutOfDateWithPrepend: 1940 if (!(buildResult & BuildResultFlags.DeclarationOutputUnchanged)) { 1941 state.projectStatus.set(nextProjectPath, { 1942 type: UpToDateStatusType.OutOfDateWithUpstream, 1943 outOfDateOutputFileName: status.type === UpToDateStatusType.OutOfDateWithPrepend ? status.outOfDateOutputFileName : status.oldestOutputFileName, 1944 newerProjectName: project 1945 }); 1946 } 1947 break; 1948 1949 case UpToDateStatusType.UpstreamBlocked: 1950 if (toResolvedConfigFilePath(state, resolveProjectName(state, status.upstreamProjectName)) === projectPath) { 1951 clearProjectStatus(state, nextProjectPath); 1952 } 1953 break; 1954 } 1955 } 1956 addProjToQueue(state, nextProjectPath, ConfigFileProgramReloadLevel.None); 1957 break; 1958 } 1959 } 1960 } 1961 1962 function build(state: SolutionBuilderState, project?: string, cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, getCustomTransformers?: (project: string) => CustomTransformers, onlyReferences?: boolean): ExitStatus { 1963 performance.mark("SolutionBuilder::beforeBuild"); 1964 const result = buildWorker(state, project, cancellationToken, writeFile, getCustomTransformers, onlyReferences); 1965 performance.mark("SolutionBuilder::afterBuild"); 1966 performance.measure("SolutionBuilder::Build", "SolutionBuilder::beforeBuild", "SolutionBuilder::afterBuild"); 1967 return result; 1968 } 1969 1970 function buildWorker(state: SolutionBuilderState, project: string | undefined, cancellationToken: CancellationToken | undefined, writeFile: WriteFileCallback | undefined, getCustomTransformers: ((project: string) => CustomTransformers) | undefined, onlyReferences: boolean | undefined): ExitStatus { 1971 const buildOrder = getBuildOrderFor(state, project, onlyReferences); 1972 if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; 1973 1974 setupInitialBuild(state, cancellationToken); 1975 1976 let reportQueue = true; 1977 let successfulProjects = 0; 1978 while (true) { 1979 const invalidatedProject = getNextInvalidatedProject(state, buildOrder, reportQueue); 1980 if (!invalidatedProject) break; 1981 reportQueue = false; 1982 invalidatedProject.done(cancellationToken, writeFile, getCustomTransformers?.(invalidatedProject.project)); 1983 if (!state.diagnostics.has(invalidatedProject.projectPath)) successfulProjects++; 1984 } 1985 1986 disableCache(state); 1987 reportErrorSummary(state, buildOrder); 1988 startWatching(state, buildOrder); 1989 1990 return isCircularBuildOrder(buildOrder) 1991 ? ExitStatus.ProjectReferenceCycle_OutputsSkipped 1992 : !buildOrder.some(p => state.diagnostics.has(toResolvedConfigFilePath(state, p))) 1993 ? ExitStatus.Success 1994 : successfulProjects 1995 ? ExitStatus.DiagnosticsPresent_OutputsGenerated 1996 : ExitStatus.DiagnosticsPresent_OutputsSkipped; 1997 } 1998 1999 function clean(state: SolutionBuilderState, project?: string, onlyReferences?: boolean): ExitStatus { 2000 performance.mark("SolutionBuilder::beforeClean"); 2001 const result = cleanWorker(state, project, onlyReferences); 2002 performance.mark("SolutionBuilder::afterClean"); 2003 performance.measure("SolutionBuilder::Clean", "SolutionBuilder::beforeClean", "SolutionBuilder::afterClean"); 2004 return result; 2005 } 2006 2007 function cleanWorker(state: SolutionBuilderState, project: string | undefined, onlyReferences: boolean | undefined) { 2008 const buildOrder = getBuildOrderFor(state, project, onlyReferences); 2009 if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; 2010 2011 if (isCircularBuildOrder(buildOrder)) { 2012 reportErrors(state, buildOrder.circularDiagnostics); 2013 return ExitStatus.ProjectReferenceCycle_OutputsSkipped; 2014 } 2015 2016 const { options, host } = state; 2017 const filesToDelete = options.dry ? [] as string[] : undefined; 2018 for (const proj of buildOrder) { 2019 const resolvedPath = toResolvedConfigFilePath(state, proj); 2020 const parsed = parseConfigFile(state, proj, resolvedPath); 2021 if (parsed === undefined) { 2022 // File has gone missing; fine to ignore here 2023 reportParseConfigFileDiagnostic(state, resolvedPath); 2024 continue; 2025 } 2026 const outputs = getAllProjectOutputs(parsed, !host.useCaseSensitiveFileNames()); 2027 if (!outputs.length) continue; 2028 const inputFileNames = new Set(parsed.fileNames.map(f => toPath(state, f))); 2029 for (const output of outputs) { 2030 // If output name is same as input file name, do not delete and ignore the error 2031 if (inputFileNames.has(toPath(state, output))) continue; 2032 if (host.fileExists(output)) { 2033 if (filesToDelete) { 2034 filesToDelete.push(output); 2035 } 2036 else { 2037 host.deleteFile(output); 2038 invalidateProject(state, resolvedPath, ConfigFileProgramReloadLevel.None); 2039 } 2040 } 2041 } 2042 } 2043 2044 if (filesToDelete) { 2045 reportStatus(state, Diagnostics.A_non_dry_build_would_delete_the_following_files_Colon_0, filesToDelete.map(f => `\r\n * ${f}`).join("")); 2046 } 2047 2048 return ExitStatus.Success; 2049 } 2050 2051 function invalidateProject(state: SolutionBuilderState, resolved: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { 2052 // If host implements getParsedCommandLine, we cant get list of files from parseConfigFileHost 2053 if (state.host.getParsedCommandLine && reloadLevel === ConfigFileProgramReloadLevel.Partial) { 2054 reloadLevel = ConfigFileProgramReloadLevel.Full; 2055 } 2056 if (reloadLevel === ConfigFileProgramReloadLevel.Full) { 2057 state.configFileCache.delete(resolved); 2058 state.buildOrder = undefined; 2059 } 2060 state.needsSummary = true; 2061 clearProjectStatus(state, resolved); 2062 addProjToQueue(state, resolved, reloadLevel); 2063 enableCache(state); 2064 } 2065 2066 function invalidateProjectAndScheduleBuilds(state: SolutionBuilderState, resolvedPath: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { 2067 state.reportFileChangeDetected = true; 2068 invalidateProject(state, resolvedPath, reloadLevel); 2069 scheduleBuildInvalidatedProject(state, 250, /*changeDetected*/ true); 2070 } 2071 2072 function scheduleBuildInvalidatedProject(state: SolutionBuilderState, time: number, changeDetected: boolean) { 2073 const { hostWithWatch } = state; 2074 if (!hostWithWatch.setTimeout || !hostWithWatch.clearTimeout) { 2075 return; 2076 } 2077 if (state.timerToBuildInvalidatedProject) { 2078 hostWithWatch.clearTimeout(state.timerToBuildInvalidatedProject); 2079 } 2080 state.timerToBuildInvalidatedProject = hostWithWatch.setTimeout(buildNextInvalidatedProject, time, state, changeDetected); 2081 } 2082 2083 function buildNextInvalidatedProject(state: SolutionBuilderState, changeDetected: boolean) { 2084 performance.mark("SolutionBuilder::beforeBuild"); 2085 const buildOrder = buildNextInvalidatedProjectWorker(state, changeDetected); 2086 performance.mark("SolutionBuilder::afterBuild"); 2087 performance.measure("SolutionBuilder::Build", "SolutionBuilder::beforeBuild", "SolutionBuilder::afterBuild"); 2088 if (buildOrder) reportErrorSummary(state, buildOrder); 2089 } 2090 2091 function buildNextInvalidatedProjectWorker(state: SolutionBuilderState, changeDetected: boolean) { 2092 state.timerToBuildInvalidatedProject = undefined; 2093 if (state.reportFileChangeDetected) { 2094 state.reportFileChangeDetected = false; 2095 state.projectErrorsReported.clear(); 2096 reportWatchStatus(state, Diagnostics.File_change_detected_Starting_incremental_compilation); 2097 } 2098 let projectsBuilt = 0; 2099 const buildOrder = getBuildOrder(state); 2100 const invalidatedProject = getNextInvalidatedProject(state, buildOrder, /*reportQueue*/ false); 2101 if (invalidatedProject) { 2102 invalidatedProject.done(); 2103 projectsBuilt++; 2104 while (state.projectPendingBuild.size) { 2105 // If already scheduled, skip 2106 if (state.timerToBuildInvalidatedProject) return; 2107 // Before scheduling check if the next project needs build 2108 const info = getNextInvalidatedProjectCreateInfo(state, buildOrder, /*reportQueue*/ false); 2109 if (!info) break; // Nothing to build any more 2110 if (info.kind !== InvalidatedProjectKind.UpdateOutputFileStamps && (changeDetected || projectsBuilt === 5)) { 2111 // Schedule next project for build 2112 scheduleBuildInvalidatedProject(state, 100, /*changeDetected*/ false); 2113 return; 2114 } 2115 const project = createInvalidatedProjectWithInfo(state, info, buildOrder); 2116 project.done(); 2117 if (info.kind !== InvalidatedProjectKind.UpdateOutputFileStamps) projectsBuilt++; 2118 } 2119 } 2120 disableCache(state); 2121 return buildOrder; 2122 } 2123 2124 function watchConfigFile(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine | undefined) { 2125 if (!state.watch || state.allWatchedConfigFiles.has(resolvedPath)) return; 2126 state.allWatchedConfigFiles.set(resolvedPath, watchFile( 2127 state, 2128 resolved, 2129 () => invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.Full), 2130 PollingInterval.High, 2131 parsed?.watchOptions, 2132 WatchType.ConfigFile, 2133 resolved 2134 )); 2135 } 2136 2137 function watchExtendedConfigFiles(state: SolutionBuilderState, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine | undefined) { 2138 updateSharedExtendedConfigFileWatcher( 2139 resolvedPath, 2140 parsed?.options, 2141 state.allWatchedExtendedConfigFiles, 2142 (extendedConfigFileName, extendedConfigFilePath) => watchFile( 2143 state, 2144 extendedConfigFileName, 2145 () => state.allWatchedExtendedConfigFiles.get(extendedConfigFilePath)?.projects.forEach(projectConfigFilePath => 2146 invalidateProjectAndScheduleBuilds(state, projectConfigFilePath, ConfigFileProgramReloadLevel.Full)), 2147 PollingInterval.High, 2148 parsed?.watchOptions, 2149 WatchType.ExtendedConfigFile, 2150 ), 2151 fileName => toPath(state, fileName), 2152 ); 2153 } 2154 2155 function watchWildCardDirectories(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { 2156 if (!state.watch) return; 2157 updateWatchingWildcardDirectories( 2158 getOrCreateValueMapFromConfigFileMap(state.allWatchedWildcardDirectories, resolvedPath), 2159 new Map(getEntries(parsed.wildcardDirectories!)), 2160 (dir, flags) => state.watchDirectory( 2161 dir, 2162 fileOrDirectory => { 2163 if (isIgnoredFileFromWildCardWatching({ 2164 watchedDirPath: toPath(state, dir), 2165 fileOrDirectory, 2166 fileOrDirectoryPath: toPath(state, fileOrDirectory), 2167 configFileName: resolved, 2168 currentDirectory: state.currentDirectory, 2169 options: parsed.options, 2170 program: state.builderPrograms.get(resolvedPath) || getCachedParsedConfigFile(state, resolvedPath)?.fileNames, 2171 useCaseSensitiveFileNames: state.parseConfigFileHost.useCaseSensitiveFileNames, 2172 writeLog: s => state.writeLog(s), 2173 toPath: fileName => toPath(state, fileName) 2174 })) return; 2175 2176 invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.Partial); 2177 }, 2178 flags, 2179 parsed?.watchOptions, 2180 WatchType.WildcardDirectory, 2181 resolved 2182 ) 2183 ); 2184 } 2185 2186 function watchInputFiles(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { 2187 if (!state.watch) return; 2188 mutateMap( 2189 getOrCreateValueMapFromConfigFileMap(state.allWatchedInputFiles, resolvedPath), 2190 arrayToMap(parsed.fileNames, fileName => toPath(state, fileName)), 2191 { 2192 createNewValue: (_path, input) => watchFile( 2193 state, 2194 input, 2195 () => invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.None), 2196 PollingInterval.Low, 2197 parsed?.watchOptions, 2198 WatchType.SourceFile, 2199 resolved 2200 ), 2201 onDeleteValue: closeFileWatcher, 2202 } 2203 ); 2204 } 2205 2206 function watchPackageJsonFiles(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { 2207 if (!state.watch || !state.lastCachedPackageJsonLookups) return; 2208 mutateMap( 2209 getOrCreateValueMapFromConfigFileMap(state.allWatchedPackageJsonFiles, resolvedPath), 2210 new Map(state.lastCachedPackageJsonLookups.get(resolvedPath)), 2211 { 2212 createNewValue: (path, _input) => watchFile( 2213 state, 2214 path, 2215 () => invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.None), 2216 PollingInterval.High, 2217 parsed?.watchOptions, 2218 WatchType.PackageJson, 2219 resolved 2220 ), 2221 onDeleteValue: closeFileWatcher, 2222 } 2223 ); 2224 } 2225 2226 function startWatching(state: SolutionBuilderState, buildOrder: AnyBuildOrder) { 2227 if (!state.watchAllProjectsPending) return; 2228 performance.mark("SolutionBuilder::beforeWatcherCreation"); 2229 state.watchAllProjectsPending = false; 2230 for (const resolved of getBuildOrderFromAnyBuildOrder(buildOrder)) { 2231 const resolvedPath = toResolvedConfigFilePath(state, resolved); 2232 const cfg = parseConfigFile(state, resolved, resolvedPath); 2233 // Watch this file 2234 watchConfigFile(state, resolved, resolvedPath, cfg); 2235 watchExtendedConfigFiles(state, resolvedPath, cfg); 2236 if (cfg) { 2237 // Update watchers for wildcard directories 2238 watchWildCardDirectories(state, resolved, resolvedPath, cfg); 2239 2240 // Watch input files 2241 watchInputFiles(state, resolved, resolvedPath, cfg); 2242 2243 // Watch package json files 2244 watchPackageJsonFiles(state, resolved, resolvedPath, cfg); 2245 } 2246 } 2247 performance.mark("SolutionBuilder::afterWatcherCreation"); 2248 performance.measure("SolutionBuilder::Watcher creation", "SolutionBuilder::beforeWatcherCreation", "SolutionBuilder::afterWatcherCreation"); 2249 } 2250 2251 function stopWatching(state: SolutionBuilderState) { 2252 clearMap(state.allWatchedConfigFiles, closeFileWatcher); 2253 clearMap(state.allWatchedExtendedConfigFiles, closeFileWatcherOf); 2254 clearMap(state.allWatchedWildcardDirectories, watchedWildcardDirectories => clearMap(watchedWildcardDirectories, closeFileWatcherOf)); 2255 clearMap(state.allWatchedInputFiles, watchedWildcardDirectories => clearMap(watchedWildcardDirectories, closeFileWatcher)); 2256 clearMap(state.allWatchedPackageJsonFiles, watchedPacageJsonFiles => clearMap(watchedPacageJsonFiles, closeFileWatcher)); 2257 } 2258 2259 /** 2260 * A SolutionBuilder has an immutable set of rootNames that are the "entry point" projects, but 2261 * can dynamically add/remove other projects based on changes on the rootNames' references 2262 */ 2263 function createSolutionBuilderWorker<T extends BuilderProgram>(watch: false, host: SolutionBuilderHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder<T>; 2264 function createSolutionBuilderWorker<T extends BuilderProgram>(watch: true, host: SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions, baseWatchOptions?: WatchOptions): SolutionBuilder<T>; 2265 function createSolutionBuilderWorker<T extends BuilderProgram>(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], options: BuildOptions, baseWatchOptions?: WatchOptions): SolutionBuilder<T> { 2266 const state = createSolutionBuilderState(watch, hostOrHostWithWatch, rootNames, options, baseWatchOptions); 2267 return { 2268 build: (project, cancellationToken, writeFile, getCustomTransformers) => build(state, project, cancellationToken, writeFile, getCustomTransformers), 2269 clean: project => clean(state, project), 2270 buildReferences: (project, cancellationToken, writeFile, getCustomTransformers) => build(state, project, cancellationToken, writeFile, getCustomTransformers, /*onlyReferences*/ true), 2271 cleanReferences: project => clean(state, project, /*onlyReferences*/ true), 2272 getNextInvalidatedProject: cancellationToken => { 2273 setupInitialBuild(state, cancellationToken); 2274 return getNextInvalidatedProject(state, getBuildOrder(state), /*reportQueue*/ false); 2275 }, 2276 getBuildOrder: () => getBuildOrder(state), 2277 getUpToDateStatusOfProject: project => { 2278 const configFileName = resolveProjectName(state, project); 2279 const configFilePath = toResolvedConfigFilePath(state, configFileName); 2280 return getUpToDateStatus(state, parseConfigFile(state, configFileName, configFilePath), configFilePath); 2281 }, 2282 invalidateProject: (configFilePath, reloadLevel) => invalidateProject(state, configFilePath, reloadLevel || ConfigFileProgramReloadLevel.None), 2283 close: () => stopWatching(state), 2284 }; 2285 } 2286 2287 function relName(state: SolutionBuilderState, path: string): string { 2288 return convertToRelativePath(path, state.currentDirectory, f => state.getCanonicalFileName(f)); 2289 } 2290 2291 function reportStatus(state: SolutionBuilderState, message: DiagnosticMessage, ...args: string[]) { 2292 state.host.reportSolutionBuilderStatus(createCompilerDiagnostic(message, ...args)); 2293 } 2294 2295 function reportWatchStatus(state: SolutionBuilderState, message: DiagnosticMessage, ...args: (string | number | undefined)[]) { 2296 state.hostWithWatch.onWatchStatusChange?.(createCompilerDiagnostic(message, ...args), state.host.getNewLine(), state.baseCompilerOptions); 2297 } 2298 2299 function reportErrors({ host }: SolutionBuilderState, errors: readonly Diagnostic[]) { 2300 errors.forEach(err => host.reportDiagnostic(err)); 2301 } 2302 2303 function reportAndStoreErrors(state: SolutionBuilderState, proj: ResolvedConfigFilePath, errors: readonly Diagnostic[]) { 2304 reportErrors(state, errors); 2305 state.projectErrorsReported.set(proj, true); 2306 if (errors.length) { 2307 state.diagnostics.set(proj, errors); 2308 } 2309 } 2310 2311 function reportParseConfigFileDiagnostic(state: SolutionBuilderState, proj: ResolvedConfigFilePath) { 2312 reportAndStoreErrors(state, proj, [state.configFileCache.get(proj) as Diagnostic]); 2313 } 2314 2315 function reportErrorSummary(state: SolutionBuilderState, buildOrder: AnyBuildOrder) { 2316 if (!state.needsSummary) return; 2317 state.needsSummary = false; 2318 const canReportSummary = state.watch || !!state.host.reportErrorSummary; 2319 const { diagnostics } = state; 2320 let totalErrors = 0; 2321 let filesInError: (ReportFileInError | undefined)[] = []; 2322 if (isCircularBuildOrder(buildOrder)) { 2323 reportBuildQueue(state, buildOrder.buildOrder); 2324 reportErrors(state, buildOrder.circularDiagnostics); 2325 if (canReportSummary) totalErrors += getErrorCountForSummary(buildOrder.circularDiagnostics); 2326 if (canReportSummary) filesInError = [...filesInError, ...getFilesInErrorForSummary(buildOrder.circularDiagnostics)]; 2327 } 2328 else { 2329 // Report errors from the other projects 2330 buildOrder.forEach(project => { 2331 const projectPath = toResolvedConfigFilePath(state, project); 2332 if (!state.projectErrorsReported.has(projectPath)) { 2333 reportErrors(state, diagnostics.get(projectPath) || emptyArray); 2334 } 2335 }); 2336 if (canReportSummary) diagnostics.forEach(singleProjectErrors => totalErrors += getErrorCountForSummary(singleProjectErrors)); 2337 if (canReportSummary) diagnostics.forEach(singleProjectErrors => [...filesInError, ...getFilesInErrorForSummary(singleProjectErrors)]); 2338 } 2339 2340 if (state.watch) { 2341 reportWatchStatus(state, getWatchErrorSummaryDiagnosticMessage(totalErrors), totalErrors); 2342 } 2343 else if (state.host.reportErrorSummary) { 2344 state.host.reportErrorSummary(totalErrors, filesInError); 2345 } 2346 } 2347 2348 /** 2349 * Report the build ordering inferred from the current project graph if we're in verbose mode 2350 */ 2351 function reportBuildQueue(state: SolutionBuilderState, buildQueue: readonly ResolvedConfigFileName[]) { 2352 if (state.options.verbose) { 2353 reportStatus(state, Diagnostics.Projects_in_this_build_Colon_0, buildQueue.map(s => "\r\n * " + relName(state, s)).join("")); 2354 } 2355 } 2356 2357 function reportUpToDateStatus(state: SolutionBuilderState, configFileName: string, status: UpToDateStatus) { 2358 switch (status.type) { 2359 case UpToDateStatusType.OutOfDateWithSelf: 2360 return reportStatus( 2361 state, 2362 Diagnostics.Project_0_is_out_of_date_because_output_1_is_older_than_input_2, 2363 relName(state, configFileName), 2364 relName(state, status.outOfDateOutputFileName), 2365 relName(state, status.newerInputFileName) 2366 ); 2367 case UpToDateStatusType.OutOfDateWithUpstream: 2368 return reportStatus( 2369 state, 2370 Diagnostics.Project_0_is_out_of_date_because_output_1_is_older_than_input_2, 2371 relName(state, configFileName), 2372 relName(state, status.outOfDateOutputFileName), 2373 relName(state, status.newerProjectName) 2374 ); 2375 case UpToDateStatusType.OutputMissing: 2376 return reportStatus( 2377 state, 2378 Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, 2379 relName(state, configFileName), 2380 relName(state, status.missingOutputFileName) 2381 ); 2382 case UpToDateStatusType.ErrorReadingFile: 2383 return reportStatus( 2384 state, 2385 Diagnostics.Project_0_is_out_of_date_because_there_was_error_reading_file_1, 2386 relName(state, configFileName), 2387 relName(state, status.fileName) 2388 ); 2389 case UpToDateStatusType.OutOfDateBuildInfo: 2390 return reportStatus( 2391 state, 2392 Diagnostics.Project_0_is_out_of_date_because_buildinfo_file_1_indicates_that_some_of_the_changes_were_not_emitted, 2393 relName(state, configFileName), 2394 relName(state, status.buildInfoFile) 2395 ); 2396 case UpToDateStatusType.UpToDate: 2397 if (status.newestInputFileTime !== undefined) { 2398 return reportStatus( 2399 state, 2400 Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_output_2, 2401 relName(state, configFileName), 2402 relName(state, status.newestInputFileName || ""), 2403 relName(state, status.oldestOutputFileName || "") 2404 ); 2405 } 2406 // Don't report anything for "up to date because it was already built" -- too verbose 2407 break; 2408 case UpToDateStatusType.OutOfDateWithPrepend: 2409 return reportStatus( 2410 state, 2411 Diagnostics.Project_0_is_out_of_date_because_output_of_its_dependency_1_has_changed, 2412 relName(state, configFileName), 2413 relName(state, status.newerProjectName) 2414 ); 2415 case UpToDateStatusType.UpToDateWithUpstreamTypes: 2416 return reportStatus( 2417 state, 2418 Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, 2419 relName(state, configFileName) 2420 ); 2421 case UpToDateStatusType.UpToDateWithInputFileText: 2422 return reportStatus( 2423 state, 2424 Diagnostics.Project_0_is_up_to_date_but_needs_to_update_timestamps_of_output_files_that_are_older_than_input_files, 2425 relName(state, configFileName) 2426 ); 2427 case UpToDateStatusType.UpstreamOutOfDate: 2428 return reportStatus( 2429 state, 2430 Diagnostics.Project_0_is_out_of_date_because_its_dependency_1_is_out_of_date, 2431 relName(state, configFileName), 2432 relName(state, status.upstreamProjectName) 2433 ); 2434 case UpToDateStatusType.UpstreamBlocked: 2435 return reportStatus( 2436 state, 2437 status.upstreamProjectBlocked ? 2438 Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_was_not_built : 2439 Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors, 2440 relName(state, configFileName), 2441 relName(state, status.upstreamProjectName) 2442 ); 2443 case UpToDateStatusType.Unbuildable: 2444 return reportStatus( 2445 state, 2446 Diagnostics.Failed_to_parse_file_0_Colon_1, 2447 relName(state, configFileName), 2448 status.reason 2449 ); 2450 case UpToDateStatusType.TsVersionOutputOfDate: 2451 return reportStatus( 2452 state, 2453 Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, 2454 relName(state, configFileName), 2455 status.version, 2456 version 2457 ); 2458 case UpToDateStatusType.ForceBuild: 2459 return reportStatus( 2460 state, 2461 Diagnostics.Project_0_is_being_forcibly_rebuilt, 2462 relName(state, configFileName) 2463 ); 2464 case UpToDateStatusType.ContainerOnly: 2465 // Don't report status on "solution" projects 2466 // falls through 2467 case UpToDateStatusType.ComputingUpstream: 2468 // Should never leak from getUptoDateStatusWorker 2469 break; 2470 default: 2471 assertType<never>(status); 2472 } 2473 } 2474 2475 /** 2476 * Report the up-to-date status of a project if we're in verbose mode 2477 */ 2478 function verboseReportProjectStatus(state: SolutionBuilderState, configFileName: string, status: UpToDateStatus) { 2479 if (state.options.verbose) { 2480 reportUpToDateStatus(state, configFileName, status); 2481 } 2482 } 2483} 2484