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<T>(configFileMap: ESMap<ResolvedConfigFilePath, ESMap<string, T>>, resolved: ResolvedConfigFilePath): ESMap<string, T> { 68 return getOrCreateValueFromConfigFileMap<ESMap<string, T>>(configFileMap, resolved, () => new Map()); 69 } 70 71 function newer(date1: Date, date2: Date): Date { 72 return date2 > date1 ? date2 : date1; 73 } 74 75 function isDeclarationFile(fileName: string) { 76 return fileExtensionIs(fileName, Extension.Dts) || fileExtensionIs(fileName, Extension.Dets); 77 } 78 79 export type ReportEmitErrorSummary = (errorCount: number) => void; 80 81 export interface SolutionBuilderHostBase<T extends BuilderProgram> extends ProgramHost<T> { 82 createDirectory?(path: string): void; 83 /** 84 * Should provide create directory and writeFile if done of invalidatedProjects is not invoked with 85 * writeFileCallback 86 */ 87 writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void; 88 89 getModifiedTime(fileName: string): Date | undefined; 90 setModifiedTime(fileName: string, date: Date): void; 91 deleteFile(fileName: string): void; 92 getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined; 93 94 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 95 reportSolutionBuilderStatus: DiagnosticReporter; 96 97 // TODO: To do better with watch mode and normal build mode api that creates program and emits files 98 // This currently helps enable --diagnostics and --extendedDiagnostics 99 afterProgramEmitAndDiagnostics?(program: T): void; 100 /*@internal*/ afterEmitBundle?(config: ParsedCommandLine): void; 101 102 // For testing 103 /*@internal*/ now?(): Date; 104 } 105 106 export interface SolutionBuilderHost<T extends BuilderProgram> extends SolutionBuilderHostBase<T> { 107 reportErrorSummary?: ReportEmitErrorSummary; 108 } 109 110 export interface SolutionBuilderWithWatchHost<T extends BuilderProgram> extends SolutionBuilderHostBase<T>, WatchHost { 111 } 112 113 /*@internal*/ 114 export type BuildOrder = readonly ResolvedConfigFileName[]; 115 /*@internal*/ 116 export interface CircularBuildOrder { 117 buildOrder: BuildOrder; 118 circularDiagnostics: readonly Diagnostic[]; 119 } 120 /*@internal*/ 121 export type AnyBuildOrder = BuildOrder | CircularBuildOrder; 122 123 /*@internal*/ 124 export function isCircularBuildOrder(buildOrder: AnyBuildOrder): buildOrder is CircularBuildOrder { 125 return !!buildOrder && !!(buildOrder as CircularBuildOrder).buildOrder; 126 } 127 128 /*@internal*/ 129 export function getBuildOrderFromAnyBuildOrder(anyBuildOrder: AnyBuildOrder): BuildOrder { 130 return isCircularBuildOrder(anyBuildOrder) ? anyBuildOrder.buildOrder : anyBuildOrder; 131 } 132 133 export interface SolutionBuilder<T extends BuilderProgram> { 134 build(project?: string, cancellationToken?: CancellationToken): ExitStatus; 135 clean(project?: string): ExitStatus; 136 buildReferences(project: string, cancellationToken?: CancellationToken): ExitStatus; 137 cleanReferences(project?: string): ExitStatus; 138 getNextInvalidatedProject(cancellationToken?: CancellationToken): InvalidatedProject<T> | undefined; 139 140 // Currently used for testing but can be made public if needed: 141 /*@internal*/ getBuildOrder(): AnyBuildOrder; 142 143 // Testing only 144 /*@internal*/ getUpToDateStatusOfProject(project: string): UpToDateStatus; 145 /*@internal*/ invalidateProject(configFilePath: ResolvedConfigFilePath, reloadLevel?: ConfigFileProgramReloadLevel): void; 146 /*@internal*/ buildNextInvalidatedProject(): void; 147 /*@internal*/ getAllParsedConfigs(): readonly ParsedCommandLine[]; 148 /*@internal*/ close(): void; 149 } 150 151 /** 152 * Create a function that reports watch status by writing to the system and handles the formating of the diagnostic 153 */ 154 export function createBuilderStatusReporter(system: System, pretty?: boolean): DiagnosticReporter { 155 return diagnostic => { 156 let output = pretty ? `[${formatColorAndReset(getLocaleTimeString(system), ForegroundColorEscapeSequences.Grey)}] ` : `${getLocaleTimeString(system)} - `; 157 output += `${flattenDiagnosticMessageText(diagnostic.messageText, system.newLine)}${system.newLine + system.newLine}`; 158 system.write(output); 159 }; 160 } 161 162 function createSolutionBuilderHostBase<T extends BuilderProgram>(system: System, createProgram: CreateProgram<T> | undefined, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter) { 163 const host = createProgramHost(system, createProgram) as SolutionBuilderHostBase<T>; 164 host.getModifiedTime = system.getModifiedTime ? path => system.getModifiedTime!(path) : returnUndefined; 165 host.setModifiedTime = system.setModifiedTime ? (path, date) => system.setModifiedTime!(path, date) : noop; 166 host.deleteFile = system.deleteFile ? path => system.deleteFile!(path) : noop; 167 host.reportDiagnostic = reportDiagnostic || createDiagnosticReporter(system); 168 host.reportSolutionBuilderStatus = reportSolutionBuilderStatus || createBuilderStatusReporter(system); 169 host.now = maybeBind(system, system.now); // For testing 170 return host; 171 } 172 173 export function createSolutionBuilderHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary) { 174 const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderHost<T>; 175 host.reportErrorSummary = reportErrorSummary; 176 return host; 177 } 178 179 export function createSolutionBuilderWithWatchHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter) { 180 const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost<T>; 181 const watchHost = createWatchHost(system, reportWatchStatus); 182 copyProperties(host, watchHost); 183 return host; 184 } 185 186 function getCompilerOptionsOfBuildOptions(buildOptions: BuildOptions): CompilerOptions { 187 const result = {} as CompilerOptions; 188 commonOptionsWithBuild.forEach(option => { 189 if (hasProperty(buildOptions, option.name)) result[option.name] = buildOptions[option.name]; 190 }); 191 return result; 192 } 193 194 export function createSolutionBuilder<T extends BuilderProgram>(host: SolutionBuilderHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder<T> { 195 return createSolutionBuilderWorker(/*watch*/ false, host, rootNames, defaultOptions); 196 } 197 198 export function createSolutionBuilderWithWatch<T extends BuilderProgram>(host: SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions, baseWatchOptions?: WatchOptions): SolutionBuilder<T> { 199 return createSolutionBuilderWorker(/*watch*/ true, host, rootNames, defaultOptions, baseWatchOptions); 200 } 201 202 type ConfigFileCacheEntry = ParsedCommandLine | Diagnostic; 203 interface SolutionBuilderStateCache { 204 originalReadFile: CompilerHost["readFile"]; 205 originalFileExists: CompilerHost["fileExists"]; 206 originalDirectoryExists: CompilerHost["directoryExists"]; 207 originalCreateDirectory: CompilerHost["createDirectory"]; 208 originalWriteFile: CompilerHost["writeFile"] | undefined; 209 originalReadFileWithCache: CompilerHost["readFile"]; 210 originalGetSourceFile: CompilerHost["getSourceFile"]; 211 } 212 213 interface SolutionBuilderState<T extends BuilderProgram = BuilderProgram> extends WatchFactory<WatchType, ResolvedConfigFileName> { 214 readonly host: SolutionBuilderHost<T>; 215 readonly hostWithWatch: SolutionBuilderWithWatchHost<T>; 216 readonly currentDirectory: string; 217 readonly getCanonicalFileName: GetCanonicalFileName; 218 readonly parseConfigFileHost: ParseConfigFileHost; 219 readonly write: ((s: string) => void) | undefined; 220 221 // State of solution 222 readonly options: BuildOptions; 223 readonly baseCompilerOptions: CompilerOptions; 224 readonly rootNames: readonly string[]; 225 readonly baseWatchOptions: WatchOptions | undefined; 226 227 readonly resolvedConfigFilePaths: ESMap<string, ResolvedConfigFilePath>; 228 readonly configFileCache: ESMap<ResolvedConfigFilePath, ConfigFileCacheEntry>; 229 /** Map from config file name to up-to-date status */ 230 readonly projectStatus: ESMap<ResolvedConfigFilePath, UpToDateStatus>; 231 readonly buildInfoChecked: ESMap<ResolvedConfigFilePath, true>; 232 readonly extendedConfigCache: ESMap<string, ExtendedConfigCacheEntry>; 233 234 readonly builderPrograms: ESMap<ResolvedConfigFilePath, T>; 235 readonly diagnostics: ESMap<ResolvedConfigFilePath, readonly Diagnostic[]>; 236 readonly projectPendingBuild: ESMap<ResolvedConfigFilePath, ConfigFileProgramReloadLevel>; 237 readonly projectErrorsReported: ESMap<ResolvedConfigFilePath, true>; 238 239 readonly compilerHost: CompilerHost; 240 readonly moduleResolutionCache: ModuleResolutionCache | undefined; 241 242 // Mutable state 243 buildOrder: AnyBuildOrder | undefined; 244 readFileWithCache: (f: string) => string | undefined; 245 projectCompilerOptions: CompilerOptions; 246 cache: SolutionBuilderStateCache | undefined; 247 allProjectBuildPending: boolean; 248 needsSummary: boolean; 249 watchAllProjectsPending: boolean; 250 currentInvalidatedProject: InvalidatedProject<T> | undefined; 251 252 // Watch state 253 readonly watch: boolean; 254 readonly allWatchedWildcardDirectories: ESMap<ResolvedConfigFilePath, ESMap<string, WildcardDirectoryWatcher>>; 255 readonly allWatchedInputFiles: ESMap<ResolvedConfigFilePath, ESMap<Path, FileWatcher>>; 256 readonly allWatchedConfigFiles: ESMap<ResolvedConfigFilePath, FileWatcher>; 257 readonly allWatchedExtendedConfigFiles: ESMap<Path, SharedExtendedConfigFileWatcher<ResolvedConfigFilePath>>; 258 259 timerToBuildInvalidatedProject: any; 260 reportFileChangeDetected: boolean; 261 writeLog: (s: string) => void; 262 } 263 264 function createSolutionBuilderState<T extends BuilderProgram>(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], options: BuildOptions, baseWatchOptions: WatchOptions | undefined): SolutionBuilderState<T> { 265 const host = hostOrHostWithWatch as SolutionBuilderHost<T>; 266 const hostWithWatch = hostOrHostWithWatch as SolutionBuilderWithWatchHost<T>; 267 const currentDirectory = host.getCurrentDirectory(); 268 const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); 269 270 // State of the solution 271 const baseCompilerOptions = getCompilerOptionsOfBuildOptions(options); 272 const compilerHost = createCompilerHostFromProgramHost(host, () => state.projectCompilerOptions); 273 setGetSourceFileAsHashVersioned(compilerHost, host); 274 compilerHost.getParsedCommandLine = fileName => parseConfigFile(state, fileName as ResolvedConfigFileName, toResolvedConfigFilePath(state, fileName as ResolvedConfigFileName)); 275 compilerHost.resolveModuleNames = maybeBind(host, host.resolveModuleNames); 276 compilerHost.resolveTypeReferenceDirectives = maybeBind(host, host.resolveTypeReferenceDirectives); 277 const moduleResolutionCache = !compilerHost.resolveModuleNames ? createModuleResolutionCache(currentDirectory, getCanonicalFileName) : undefined; 278 if (!compilerHost.resolveModuleNames) { 279 const loader = (moduleName: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFile, state.projectCompilerOptions, compilerHost, moduleResolutionCache, redirectedReference).resolvedModule!; 280 compilerHost.resolveModuleNames = (moduleNames, containingFile, _reusedNames, redirectedReference) => 281 loadWithLocalCache<ResolvedModuleFull>(Debug.checkEachDefined(moduleNames), containingFile, redirectedReference, loader); 282 } 283 284 const { watchFile, watchDirectory, writeLog } = createWatchFactory<ResolvedConfigFileName>(hostWithWatch, options); 285 286 const state: SolutionBuilderState<T> = { 287 host, 288 hostWithWatch, 289 currentDirectory, 290 getCanonicalFileName, 291 parseConfigFileHost: parseConfigHostFromCompilerHostLike(host), 292 write: maybeBind(host, host.trace), 293 294 // State of solution 295 options, 296 baseCompilerOptions, 297 rootNames, 298 baseWatchOptions, 299 300 resolvedConfigFilePaths: new Map(), 301 configFileCache: new Map(), 302 projectStatus: new Map(), 303 buildInfoChecked: new Map(), 304 extendedConfigCache: new Map(), 305 306 builderPrograms: new Map(), 307 diagnostics: new Map(), 308 projectPendingBuild: new Map(), 309 projectErrorsReported: new Map(), 310 311 compilerHost, 312 moduleResolutionCache, 313 314 // Mutable state 315 buildOrder: undefined, 316 readFileWithCache: f => host.readFile(f), 317 projectCompilerOptions: baseCompilerOptions, 318 cache: undefined, 319 allProjectBuildPending: true, 320 needsSummary: true, 321 watchAllProjectsPending: watch, 322 currentInvalidatedProject: undefined, 323 324 // Watch state 325 watch, 326 allWatchedWildcardDirectories: new Map(), 327 allWatchedInputFiles: new Map(), 328 allWatchedConfigFiles: new Map(), 329 allWatchedExtendedConfigFiles: new Map(), 330 331 timerToBuildInvalidatedProject: undefined, 332 reportFileChangeDetected: false, 333 watchFile, 334 watchDirectory, 335 writeLog, 336 }; 337 338 return state; 339 } 340 341 function toPath(state: SolutionBuilderState, fileName: string) { 342 return ts.toPath(fileName, state.currentDirectory, state.getCanonicalFileName); 343 } 344 345 function toResolvedConfigFilePath(state: SolutionBuilderState, fileName: ResolvedConfigFileName): ResolvedConfigFilePath { 346 const { resolvedConfigFilePaths } = state; 347 const path = resolvedConfigFilePaths.get(fileName); 348 if (path !== undefined) return path; 349 350 const resolvedPath = toPath(state, fileName) as ResolvedConfigFilePath; 351 resolvedConfigFilePaths.set(fileName, resolvedPath); 352 return resolvedPath; 353 } 354 355 function isParsedCommandLine(entry: ConfigFileCacheEntry): entry is ParsedCommandLine { 356 return !!(entry as ParsedCommandLine).options; 357 } 358 359 function parseConfigFile(state: SolutionBuilderState, configFileName: ResolvedConfigFileName, configFilePath: ResolvedConfigFilePath): ParsedCommandLine | undefined { 360 const { configFileCache } = state; 361 const value = configFileCache.get(configFilePath); 362 if (value) { 363 return isParsedCommandLine(value) ? value : undefined; 364 } 365 366 let diagnostic: Diagnostic | undefined; 367 const { parseConfigFileHost, baseCompilerOptions, baseWatchOptions, extendedConfigCache, host } = state; 368 let parsed: ParsedCommandLine | undefined; 369 if (host.getParsedCommandLine) { 370 parsed = host.getParsedCommandLine(configFileName); 371 if (!parsed) diagnostic = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); 372 } 373 else { 374 parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = d => diagnostic = d; 375 parsed = getParsedCommandLineOfConfigFile(configFileName, baseCompilerOptions, parseConfigFileHost, extendedConfigCache, baseWatchOptions); 376 parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = noop; 377 } 378 configFileCache.set(configFilePath, parsed || diagnostic!); 379 return parsed; 380 } 381 382 function resolveProjectName(state: SolutionBuilderState, name: string): ResolvedConfigFileName { 383 return resolveConfigFileProjectName(resolvePath(state.currentDirectory, name)); 384 } 385 386 function createBuildOrder(state: SolutionBuilderState, roots: readonly ResolvedConfigFileName[]): AnyBuildOrder { 387 const temporaryMarks = new Map<ResolvedConfigFilePath, true>(); 388 const permanentMarks = new Map<ResolvedConfigFilePath, true>(); 389 const circularityReportStack: string[] = []; 390 let buildOrder: ResolvedConfigFileName[] | undefined; 391 let circularDiagnostics: Diagnostic[] | undefined; 392 for (const root of roots) { 393 visit(root); 394 } 395 396 return circularDiagnostics ? 397 { buildOrder: buildOrder || emptyArray, circularDiagnostics } : 398 buildOrder || emptyArray; 399 400 function visit(configFileName: ResolvedConfigFileName, inCircularContext?: boolean) { 401 const projPath = toResolvedConfigFilePath(state, configFileName); 402 // Already visited 403 if (permanentMarks.has(projPath)) return; 404 // Circular 405 if (temporaryMarks.has(projPath)) { 406 if (!inCircularContext) { 407 (circularDiagnostics || (circularDiagnostics = [])).push( 408 createCompilerDiagnostic( 409 Diagnostics.Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0, 410 circularityReportStack.join("\r\n") 411 ) 412 ); 413 } 414 return; 415 } 416 417 temporaryMarks.set(projPath, true); 418 circularityReportStack.push(configFileName); 419 const parsed = parseConfigFile(state, configFileName, projPath); 420 if (parsed && parsed.projectReferences) { 421 for (const ref of parsed.projectReferences) { 422 const resolvedRefPath = resolveProjectName(state, ref.path); 423 visit(resolvedRefPath, inCircularContext || ref.circular); 424 } 425 } 426 427 circularityReportStack.pop(); 428 permanentMarks.set(projPath, true); 429 (buildOrder || (buildOrder = [])).push(configFileName); 430 } 431 } 432 433 function getBuildOrder(state: SolutionBuilderState) { 434 return state.buildOrder || createStateBuildOrder(state); 435 } 436 437 function createStateBuildOrder(state: SolutionBuilderState) { 438 const buildOrder = createBuildOrder(state, state.rootNames.map(f => resolveProjectName(state, f))); 439 440 // Clear all to ResolvedConfigFilePaths cache to start fresh 441 state.resolvedConfigFilePaths.clear(); 442 443 // TODO(rbuckton): Should be a `Set`, but that requires changing the code below that uses `mutateMapSkippingNewValues` 444 const currentProjects = new Map( 445 getBuildOrderFromAnyBuildOrder(buildOrder).map( 446 resolved => [toResolvedConfigFilePath(state, resolved), true as true]) 447 ); 448 449 const noopOnDelete = { onDeleteValue: noop }; 450 // Config file cache 451 mutateMapSkippingNewValues(state.configFileCache, currentProjects, noopOnDelete); 452 mutateMapSkippingNewValues(state.projectStatus, currentProjects, noopOnDelete); 453 mutateMapSkippingNewValues(state.buildInfoChecked, currentProjects, noopOnDelete); 454 mutateMapSkippingNewValues(state.builderPrograms, currentProjects, noopOnDelete); 455 mutateMapSkippingNewValues(state.diagnostics, currentProjects, noopOnDelete); 456 mutateMapSkippingNewValues(state.projectPendingBuild, currentProjects, noopOnDelete); 457 mutateMapSkippingNewValues(state.projectErrorsReported, currentProjects, noopOnDelete); 458 459 // Remove watches for the program no longer in the solution 460 if (state.watch) { 461 mutateMapSkippingNewValues( 462 state.allWatchedConfigFiles, 463 currentProjects, 464 { onDeleteValue: closeFileWatcher } 465 ); 466 467 state.allWatchedExtendedConfigFiles.forEach(watcher => { 468 watcher.projects.forEach(project => { 469 if (!currentProjects.has(project)) { 470 watcher.projects.delete(project); 471 } 472 }); 473 watcher.close(); 474 }); 475 476 mutateMapSkippingNewValues( 477 state.allWatchedWildcardDirectories, 478 currentProjects, 479 { onDeleteValue: existingMap => existingMap.forEach(closeFileWatcherOf) } 480 ); 481 482 mutateMapSkippingNewValues( 483 state.allWatchedInputFiles, 484 currentProjects, 485 { onDeleteValue: existingMap => existingMap.forEach(closeFileWatcher) } 486 ); 487 } 488 return state.buildOrder = buildOrder; 489 } 490 491 function getBuildOrderFor(state: SolutionBuilderState, project: string | undefined, onlyReferences: boolean | undefined): AnyBuildOrder | undefined { 492 const resolvedProject = project && resolveProjectName(state, project); 493 const buildOrderFromState = getBuildOrder(state); 494 if (isCircularBuildOrder(buildOrderFromState)) return buildOrderFromState; 495 if (resolvedProject) { 496 const projectPath = toResolvedConfigFilePath(state, resolvedProject); 497 const projectIndex = findIndex( 498 buildOrderFromState, 499 configFileName => toResolvedConfigFilePath(state, configFileName) === projectPath 500 ); 501 if (projectIndex === -1) return undefined; 502 } 503 const buildOrder = resolvedProject ? createBuildOrder(state, [resolvedProject]) as BuildOrder : buildOrderFromState; 504 Debug.assert(!isCircularBuildOrder(buildOrder)); 505 Debug.assert(!onlyReferences || resolvedProject !== undefined); 506 Debug.assert(!onlyReferences || buildOrder[buildOrder.length - 1] === resolvedProject); 507 return onlyReferences ? buildOrder.slice(0, buildOrder.length - 1) : buildOrder; 508 } 509 510 function enableCache(state: SolutionBuilderState) { 511 if (state.cache) { 512 disableCache(state); 513 } 514 515 const { compilerHost, host } = state; 516 517 const originalReadFileWithCache = state.readFileWithCache; 518 const originalGetSourceFile = compilerHost.getSourceFile; 519 520 const { 521 originalReadFile, originalFileExists, originalDirectoryExists, 522 originalCreateDirectory, originalWriteFile, 523 getSourceFileWithCache, readFileWithCache 524 } = changeCompilerHostLikeToUseCache( 525 host, 526 fileName => toPath(state, fileName), 527 (...args) => originalGetSourceFile.call(compilerHost, ...args) 528 ); 529 state.readFileWithCache = readFileWithCache; 530 compilerHost.getSourceFile = getSourceFileWithCache!; 531 532 state.cache = { 533 originalReadFile, 534 originalFileExists, 535 originalDirectoryExists, 536 originalCreateDirectory, 537 originalWriteFile, 538 originalReadFileWithCache, 539 originalGetSourceFile, 540 }; 541 } 542 543 function disableCache(state: SolutionBuilderState) { 544 if (!state.cache) return; 545 546 const { cache, host, compilerHost, extendedConfigCache, moduleResolutionCache } = state; 547 548 host.readFile = cache.originalReadFile; 549 host.fileExists = cache.originalFileExists; 550 host.directoryExists = cache.originalDirectoryExists; 551 host.createDirectory = cache.originalCreateDirectory; 552 host.writeFile = cache.originalWriteFile; 553 compilerHost.getSourceFile = cache.originalGetSourceFile; 554 state.readFileWithCache = cache.originalReadFileWithCache; 555 extendedConfigCache.clear(); 556 if (moduleResolutionCache) { 557 moduleResolutionCache.directoryToModuleNameMap.clear(); 558 moduleResolutionCache.moduleNameToDirectoryMap.clear(); 559 } 560 state.cache = undefined; 561 } 562 563 function clearProjectStatus(state: SolutionBuilderState, resolved: ResolvedConfigFilePath) { 564 state.projectStatus.delete(resolved); 565 state.diagnostics.delete(resolved); 566 } 567 568 function addProjToQueue({ projectPendingBuild }: SolutionBuilderState, proj: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { 569 const value = projectPendingBuild.get(proj); 570 if (value === undefined) { 571 projectPendingBuild.set(proj, reloadLevel); 572 } 573 else if (value < reloadLevel) { 574 projectPendingBuild.set(proj, reloadLevel); 575 } 576 } 577 578 function setupInitialBuild(state: SolutionBuilderState, cancellationToken: CancellationToken | undefined) { 579 // Set initial build if not already built 580 if (!state.allProjectBuildPending) return; 581 state.allProjectBuildPending = false; 582 if (state.options.watch) { reportWatchStatus(state, Diagnostics.Starting_compilation_in_watch_mode); } 583 enableCache(state); 584 const buildOrder = getBuildOrderFromAnyBuildOrder(getBuildOrder(state)); 585 buildOrder.forEach(configFileName => 586 state.projectPendingBuild.set( 587 toResolvedConfigFilePath(state, configFileName), 588 ConfigFileProgramReloadLevel.None 589 ) 590 ); 591 592 if (cancellationToken) { 593 cancellationToken.throwIfCancellationRequested(); 594 } 595 } 596 597 export enum InvalidatedProjectKind { 598 Build, 599 UpdateBundle, 600 UpdateOutputFileStamps 601 } 602 603 export interface InvalidatedProjectBase { 604 readonly kind: InvalidatedProjectKind; 605 readonly project: ResolvedConfigFileName; 606 /*@internal*/ readonly projectPath: ResolvedConfigFilePath; 607 /*@internal*/ readonly buildOrder: readonly ResolvedConfigFileName[]; 608 /** 609 * To dispose this project and ensure that all the necessary actions are taken and state is updated accordingly 610 */ 611 done(cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers): ExitStatus; 612 getCompilerOptions(): CompilerOptions; 613 getCurrentDirectory(): string; 614 } 615 616 export interface UpdateOutputFileStampsProject extends InvalidatedProjectBase { 617 readonly kind: InvalidatedProjectKind.UpdateOutputFileStamps; 618 updateOutputFileStatmps(): void; 619 } 620 621 export interface BuildInvalidedProject<T extends BuilderProgram> extends InvalidatedProjectBase { 622 readonly kind: InvalidatedProjectKind.Build; 623 /* 624 * Emitting with this builder program without the api provided for this project 625 * can result in build system going into invalid state as files written reflect the state of the project 626 */ 627 getBuilderProgram(): T | undefined; 628 getProgram(): Program | undefined; 629 getSourceFile(fileName: string): SourceFile | undefined; 630 getSourceFiles(): readonly SourceFile[]; 631 getOptionsDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; 632 getGlobalDiagnostics(cancellationToken?: CancellationToken): readonly Diagnostic[]; 633 getConfigFileParsingDiagnostics(): readonly Diagnostic[]; 634 getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; 635 getAllDependencies(sourceFile: SourceFile): readonly string[]; 636 getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[]; 637 getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult<readonly Diagnostic[]>; 638 /* 639 * Calling emit directly with targetSourceFile and emitOnlyDtsFiles set to true is not advised since 640 * emit in build system is responsible in updating status of the project 641 * If called with targetSourceFile and emitOnlyDtsFiles set to true, the emit just passes to underlying builder and 642 * wont reflect the status of file as being emitted in the builder 643 * (if that emit of that source file is required it would be emitted again when making sure invalidated project is completed) 644 * This emit is not considered actual emit (and hence uptodate status is not reflected if 645 */ 646 emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult | undefined; 647 // TODO(shkamat):: investigate later if we can emit even when there are declaration diagnostics 648 // emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult<EmitResult>; 649 } 650 651 export interface UpdateBundleProject<T extends BuilderProgram> extends InvalidatedProjectBase { 652 readonly kind: InvalidatedProjectKind.UpdateBundle; 653 emit(writeFile?: WriteFileCallback, customTransformers?: CustomTransformers): EmitResult | BuildInvalidedProject<T> | undefined; 654 } 655 656 export type InvalidatedProject<T extends BuilderProgram> = UpdateOutputFileStampsProject | BuildInvalidedProject<T> | UpdateBundleProject<T>; 657 658 function doneInvalidatedProject( 659 state: SolutionBuilderState, 660 projectPath: ResolvedConfigFilePath 661 ) { 662 state.projectPendingBuild.delete(projectPath); 663 state.currentInvalidatedProject = undefined; 664 return state.diagnostics.has(projectPath) ? 665 ExitStatus.DiagnosticsPresent_OutputsSkipped : 666 ExitStatus.Success; 667 } 668 669 function createUpdateOutputFileStampsProject( 670 state: SolutionBuilderState, 671 project: ResolvedConfigFileName, 672 projectPath: ResolvedConfigFilePath, 673 config: ParsedCommandLine, 674 buildOrder: readonly ResolvedConfigFileName[] 675 ): UpdateOutputFileStampsProject { 676 let updateOutputFileStampsPending = true; 677 return { 678 kind: InvalidatedProjectKind.UpdateOutputFileStamps, 679 project, 680 projectPath, 681 buildOrder, 682 getCompilerOptions: () => config.options, 683 getCurrentDirectory: () => state.currentDirectory, 684 updateOutputFileStatmps: () => { 685 updateOutputTimestamps(state, config, projectPath); 686 updateOutputFileStampsPending = false; 687 }, 688 done: () => { 689 if (updateOutputFileStampsPending) { 690 updateOutputTimestamps(state, config, projectPath); 691 } 692 return doneInvalidatedProject(state, projectPath); 693 } 694 }; 695 } 696 697 enum BuildStep { 698 CreateProgram, 699 SyntaxDiagnostics, 700 SemanticDiagnostics, 701 Emit, 702 EmitBundle, 703 EmitBuildInfo, 704 BuildInvalidatedProjectOfBundle, 705 QueueReferencingProjects, 706 Done 707 } 708 709 function createBuildOrUpdateInvalidedProject<T extends BuilderProgram>( 710 kind: InvalidatedProjectKind.Build | InvalidatedProjectKind.UpdateBundle, 711 state: SolutionBuilderState<T>, 712 project: ResolvedConfigFileName, 713 projectPath: ResolvedConfigFilePath, 714 projectIndex: number, 715 config: ParsedCommandLine, 716 buildOrder: readonly ResolvedConfigFileName[], 717 ): BuildInvalidedProject<T> | UpdateBundleProject<T> { 718 let step = kind === InvalidatedProjectKind.Build ? BuildStep.CreateProgram : BuildStep.EmitBundle; 719 let program: T | undefined; 720 let buildResult: BuildResultFlags | undefined; 721 let invalidatedProjectOfBundle: BuildInvalidedProject<T> | undefined; 722 723 return kind === InvalidatedProjectKind.Build ? 724 { 725 kind, 726 project, 727 projectPath, 728 buildOrder, 729 getCompilerOptions: () => config.options, 730 getCurrentDirectory: () => state.currentDirectory, 731 getBuilderProgram: () => withProgramOrUndefined(identity), 732 getProgram: () => 733 withProgramOrUndefined( 734 program => program.getProgramOrUndefined() 735 ), 736 getSourceFile: fileName => 737 withProgramOrUndefined( 738 program => program.getSourceFile(fileName) 739 ), 740 getSourceFiles: () => 741 withProgramOrEmptyArray( 742 program => program.getSourceFiles() 743 ), 744 getOptionsDiagnostics: cancellationToken => 745 withProgramOrEmptyArray( 746 program => program.getOptionsDiagnostics(cancellationToken) 747 ), 748 getGlobalDiagnostics: cancellationToken => 749 withProgramOrEmptyArray( 750 program => program.getGlobalDiagnostics(cancellationToken) 751 ), 752 getConfigFileParsingDiagnostics: () => 753 withProgramOrEmptyArray( 754 program => program.getConfigFileParsingDiagnostics() 755 ), 756 getSyntacticDiagnostics: (sourceFile, cancellationToken) => 757 withProgramOrEmptyArray( 758 program => program.getSyntacticDiagnostics(sourceFile, cancellationToken) 759 ), 760 getAllDependencies: sourceFile => 761 withProgramOrEmptyArray( 762 program => program.getAllDependencies(sourceFile) 763 ), 764 getSemanticDiagnostics: (sourceFile, cancellationToken) => 765 withProgramOrEmptyArray( 766 program => program.getSemanticDiagnostics(sourceFile, cancellationToken) 767 ), 768 getSemanticDiagnosticsOfNextAffectedFile: (cancellationToken, ignoreSourceFile) => 769 withProgramOrUndefined( 770 program => 771 ((program as any as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile) && 772 (program as any as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile(cancellationToken, ignoreSourceFile) 773 ), 774 emit: (targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers) => { 775 if (targetSourceFile || emitOnlyDtsFiles) { 776 return withProgramOrUndefined( 777 program => program.emit(targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers) 778 ); 779 } 780 executeSteps(BuildStep.SemanticDiagnostics, cancellationToken); 781 if (step === BuildStep.EmitBuildInfo) { 782 return emitBuildInfo(writeFile, cancellationToken); 783 } 784 if (step !== BuildStep.Emit) return undefined; 785 return emit(writeFile, cancellationToken, customTransformers); 786 }, 787 done 788 } : 789 { 790 kind, 791 project, 792 projectPath, 793 buildOrder, 794 getCompilerOptions: () => config.options, 795 getCurrentDirectory: () => state.currentDirectory, 796 emit: (writeFile: WriteFileCallback | undefined, customTransformers: CustomTransformers | undefined) => { 797 if (step !== BuildStep.EmitBundle) return invalidatedProjectOfBundle; 798 return emitBundle(writeFile, customTransformers); 799 }, 800 done, 801 }; 802 803 function done(cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers) { 804 executeSteps(BuildStep.Done, cancellationToken, writeFile, customTransformers); 805 return doneInvalidatedProject(state, projectPath); 806 } 807 808 function withProgramOrUndefined<U>(action: (program: T) => U | undefined): U | undefined { 809 executeSteps(BuildStep.CreateProgram); 810 return program && action(program); 811 } 812 813 function withProgramOrEmptyArray<U>(action: (program: T) => readonly U[]): readonly U[] { 814 return withProgramOrUndefined(action) || emptyArray; 815 } 816 817 function createProgram() { 818 Debug.assert(program === undefined); 819 820 if (state.options.dry) { 821 reportStatus(state, Diagnostics.A_non_dry_build_would_build_project_0, project); 822 buildResult = BuildResultFlags.Success; 823 step = BuildStep.QueueReferencingProjects; 824 return; 825 } 826 827 if (state.options.verbose) reportStatus(state, Diagnostics.Building_project_0, project); 828 829 if (config.fileNames.length === 0) { 830 reportAndStoreErrors(state, projectPath, getConfigFileParsingDiagnostics(config)); 831 // Nothing to build - must be a solution file, basically 832 buildResult = BuildResultFlags.None; 833 step = BuildStep.QueueReferencingProjects; 834 return; 835 } 836 837 const { host, compilerHost } = state; 838 state.projectCompilerOptions = config.options; 839 // Update module resolution cache if needed 840 updateModuleResolutionCache(state, project, config); 841 842 // Create program 843 program = host.createProgram( 844 config.fileNames, 845 config.options, 846 compilerHost, 847 getOldProgram(state, projectPath, config), 848 getConfigFileParsingDiagnostics(config), 849 config.projectReferences 850 ); 851 if (state.watch) { 852 state.builderPrograms.set(projectPath, program); 853 } 854 step++; 855 } 856 857 function handleDiagnostics(diagnostics: readonly Diagnostic[], errorFlags: BuildResultFlags, errorType: string) { 858 if (diagnostics.length) { 859 ({ buildResult, step } = buildErrors( 860 state, 861 projectPath, 862 program, 863 config, 864 diagnostics, 865 errorFlags, 866 errorType 867 )); 868 } 869 else { 870 step++; 871 } 872 } 873 874 function getSyntaxDiagnostics(cancellationToken?: CancellationToken) { 875 Debug.assertIsDefined(program); 876 handleDiagnostics( 877 [ 878 ...program.getConfigFileParsingDiagnostics(), 879 ...program.getOptionsDiagnostics(cancellationToken), 880 ...program.getGlobalDiagnostics(cancellationToken), 881 ...program.getSyntacticDiagnostics(/*sourceFile*/ undefined, cancellationToken) 882 ], 883 BuildResultFlags.SyntaxErrors, 884 "Syntactic" 885 ); 886 } 887 888 function getSemanticDiagnostics(cancellationToken?: CancellationToken) { 889 handleDiagnostics( 890 Debug.checkDefined(program).getSemanticDiagnostics(/*sourceFile*/ undefined, cancellationToken), 891 BuildResultFlags.TypeErrors, 892 "Semantic" 893 ); 894 } 895 896 function emit(writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitResult { 897 Debug.assertIsDefined(program); 898 Debug.assert(step === BuildStep.Emit); 899 // Before emitting lets backup state, so we can revert it back if there are declaration errors to handle emit and declaration errors correctly 900 program.backupState(); 901 let declDiagnostics: Diagnostic[] | undefined; 902 const reportDeclarationDiagnostics = (d: Diagnostic) => (declDiagnostics || (declDiagnostics = [])).push(d); 903 const outputFiles: OutputFile[] = []; 904 const { emitResult } = emitFilesAndReportErrors( 905 program, 906 reportDeclarationDiagnostics, 907 /*write*/ undefined, 908 /*reportSummary*/ undefined, 909 (name, text, writeByteOrderMark) => outputFiles.push({ name, text, writeByteOrderMark }), 910 cancellationToken, 911 /*emitOnlyDts*/ false, 912 customTransformers 913 ); 914 // Don't emit .d.ts if there are decl file errors 915 if (declDiagnostics) { 916 program.restoreState(); 917 ({ buildResult, step } = buildErrors( 918 state, 919 projectPath, 920 program, 921 config, 922 declDiagnostics, 923 BuildResultFlags.DeclarationEmitErrors, 924 "Declaration file" 925 )); 926 return { 927 emitSkipped: true, 928 diagnostics: emitResult.diagnostics 929 }; 930 } 931 932 // Actual Emit 933 const { host, compilerHost } = state; 934 let resultFlags = BuildResultFlags.DeclarationOutputUnchanged; 935 let newestDeclarationFileContentChangedTime = minimumDate; 936 let anyDtsChanged = false; 937 const emitterDiagnostics = createDiagnosticCollection(); 938 const emittedOutputs = new Map<Path, string>(); 939 outputFiles.forEach(({ name, text, writeByteOrderMark }) => { 940 let priorChangeTime: Date | undefined; 941 if (!anyDtsChanged && isDeclarationFile(name)) { 942 // Check for unchanged .d.ts files 943 if (host.fileExists(name) && state.readFileWithCache(name) === text) { 944 priorChangeTime = host.getModifiedTime(name); 945 } 946 else { 947 resultFlags &= ~BuildResultFlags.DeclarationOutputUnchanged; 948 anyDtsChanged = true; 949 } 950 } 951 952 emittedOutputs.set(toPath(state, name), name); 953 writeFile(writeFileCallback ? { writeFile: writeFileCallback } : compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); 954 if (priorChangeTime !== undefined) { 955 newestDeclarationFileContentChangedTime = newer(priorChangeTime, newestDeclarationFileContentChangedTime); 956 } 957 }); 958 959 finishEmit( 960 emitterDiagnostics, 961 emittedOutputs, 962 newestDeclarationFileContentChangedTime, 963 /*newestDeclarationFileContentChangedTimeIsMaximumDate*/ anyDtsChanged, 964 outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(config, !host.useCaseSensitiveFileNames()), 965 resultFlags 966 ); 967 return emitResult; 968 } 969 970 function emitBuildInfo(writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult { 971 Debug.assertIsDefined(program); 972 Debug.assert(step === BuildStep.EmitBuildInfo); 973 const emitResult = program.emitBuildInfo(writeFileCallback, cancellationToken); 974 if (emitResult.diagnostics.length) { 975 reportErrors(state, emitResult.diagnostics); 976 state.diagnostics.set(projectPath, [...state.diagnostics.get(projectPath)!, ...emitResult.diagnostics]); 977 buildResult = BuildResultFlags.EmitErrors & buildResult!; 978 } 979 980 if (emitResult.emittedFiles && state.write) { 981 emitResult.emittedFiles.forEach(name => listEmittedFile(state, config, name)); 982 } 983 afterProgramDone(state, program, config); 984 step = BuildStep.QueueReferencingProjects; 985 return emitResult; 986 } 987 988 function finishEmit( 989 emitterDiagnostics: DiagnosticCollection, 990 emittedOutputs: ESMap<Path, string>, 991 priorNewestUpdateTime: Date, 992 newestDeclarationFileContentChangedTimeIsMaximumDate: boolean, 993 oldestOutputFileName: string, 994 resultFlags: BuildResultFlags 995 ) { 996 const emitDiagnostics = emitterDiagnostics.getDiagnostics(); 997 if (emitDiagnostics.length) { 998 ({ buildResult, step } = buildErrors( 999 state, 1000 projectPath, 1001 program, 1002 config, 1003 emitDiagnostics, 1004 BuildResultFlags.EmitErrors, 1005 "Emit" 1006 )); 1007 return emitDiagnostics; 1008 } 1009 1010 if (state.write) { 1011 emittedOutputs.forEach(name => listEmittedFile(state, config, name)); 1012 } 1013 1014 // Update time stamps for rest of the outputs 1015 const newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(state, config, priorNewestUpdateTime, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs); 1016 state.diagnostics.delete(projectPath); 1017 state.projectStatus.set(projectPath, { 1018 type: UpToDateStatusType.UpToDate, 1019 newestDeclarationFileContentChangedTime: newestDeclarationFileContentChangedTimeIsMaximumDate ? 1020 maximumDate : 1021 newestDeclarationFileContentChangedTime, 1022 oldestOutputFileName 1023 }); 1024 afterProgramDone(state, program, config); 1025 step = BuildStep.QueueReferencingProjects; 1026 buildResult = resultFlags; 1027 return emitDiagnostics; 1028 } 1029 1030 function emitBundle(writeFileCallback?: WriteFileCallback, customTransformers?: CustomTransformers): EmitResult | BuildInvalidedProject<T> | undefined { 1031 Debug.assert(kind === InvalidatedProjectKind.UpdateBundle); 1032 if (state.options.dry) { 1033 reportStatus(state, Diagnostics.A_non_dry_build_would_update_output_of_project_0, project); 1034 buildResult = BuildResultFlags.Success; 1035 step = BuildStep.QueueReferencingProjects; 1036 return undefined; 1037 } 1038 1039 if (state.options.verbose) reportStatus(state, Diagnostics.Updating_output_of_project_0, project); 1040 1041 // Update js, and source map 1042 const { compilerHost } = state; 1043 state.projectCompilerOptions = config.options; 1044 const outputFiles = emitUsingBuildInfo( 1045 config, 1046 compilerHost, 1047 ref => { 1048 const refName = resolveProjectName(state, ref.path); 1049 return parseConfigFile(state, refName, toResolvedConfigFilePath(state, refName)); 1050 }, 1051 customTransformers 1052 ); 1053 1054 if (isString(outputFiles)) { 1055 reportStatus(state, Diagnostics.Cannot_update_output_of_project_0_because_there_was_error_reading_file_1, project, relName(state, outputFiles)); 1056 step = BuildStep.BuildInvalidatedProjectOfBundle; 1057 return invalidatedProjectOfBundle = createBuildOrUpdateInvalidedProject( 1058 InvalidatedProjectKind.Build, 1059 state, 1060 project, 1061 projectPath, 1062 projectIndex, 1063 config, 1064 buildOrder 1065 ) as BuildInvalidedProject<T>; 1066 } 1067 1068 // Actual Emit 1069 Debug.assert(!!outputFiles.length); 1070 const emitterDiagnostics = createDiagnosticCollection(); 1071 const emittedOutputs = new Map<Path, string>(); 1072 outputFiles.forEach(({ name, text, writeByteOrderMark }) => { 1073 emittedOutputs.set(toPath(state, name), name); 1074 writeFile(writeFileCallback ? { writeFile: writeFileCallback } : compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); 1075 }); 1076 1077 const emitDiagnostics = finishEmit( 1078 emitterDiagnostics, 1079 emittedOutputs, 1080 minimumDate, 1081 /*newestDeclarationFileContentChangedTimeIsMaximumDate*/ false, 1082 outputFiles[0].name, 1083 BuildResultFlags.DeclarationOutputUnchanged 1084 ); 1085 return { emitSkipped: false, diagnostics: emitDiagnostics }; 1086 } 1087 1088 function executeSteps(till: BuildStep, cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers) { 1089 while (step <= till && step < BuildStep.Done) { 1090 const currentStep = step; 1091 switch (step) { 1092 case BuildStep.CreateProgram: 1093 createProgram(); 1094 break; 1095 1096 case BuildStep.SyntaxDiagnostics: 1097 getSyntaxDiagnostics(cancellationToken); 1098 break; 1099 1100 case BuildStep.SemanticDiagnostics: 1101 getSemanticDiagnostics(cancellationToken); 1102 break; 1103 1104 case BuildStep.Emit: 1105 emit(writeFile, cancellationToken, customTransformers); 1106 break; 1107 1108 case BuildStep.EmitBuildInfo: 1109 emitBuildInfo(writeFile, cancellationToken); 1110 break; 1111 1112 case BuildStep.EmitBundle: 1113 emitBundle(writeFile, customTransformers); 1114 break; 1115 1116 case BuildStep.BuildInvalidatedProjectOfBundle: 1117 Debug.checkDefined(invalidatedProjectOfBundle).done(cancellationToken); 1118 step = BuildStep.Done; 1119 break; 1120 1121 case BuildStep.QueueReferencingProjects: 1122 queueReferencingProjects(state, project, projectPath, projectIndex, config, buildOrder, Debug.checkDefined(buildResult)); 1123 step++; 1124 break; 1125 1126 // Should never be done 1127 case BuildStep.Done: 1128 default: 1129 assertType<BuildStep.Done>(step); 1130 1131 } 1132 Debug.assert(step > currentStep); 1133 } 1134 } 1135 } 1136 1137 function needsBuild({ options }: SolutionBuilderState, status: UpToDateStatus, config: ParsedCommandLine) { 1138 if (status.type !== UpToDateStatusType.OutOfDateWithPrepend || options.force) return true; 1139 return config.fileNames.length === 0 || 1140 !!getConfigFileParsingDiagnostics(config).length || 1141 !isIncrementalCompilation(config.options); 1142 } 1143 1144 function getNextInvalidatedProject<T extends BuilderProgram>( 1145 state: SolutionBuilderState<T>, 1146 buildOrder: AnyBuildOrder, 1147 reportQueue: boolean 1148 ): InvalidatedProject<T> | undefined { 1149 if (!state.projectPendingBuild.size) return undefined; 1150 if (isCircularBuildOrder(buildOrder)) return undefined; 1151 if (state.currentInvalidatedProject) { 1152 // Only if same buildOrder the currentInvalidated project can be sent again 1153 return arrayIsEqualTo(state.currentInvalidatedProject.buildOrder, buildOrder) ? 1154 state.currentInvalidatedProject : 1155 undefined; 1156 } 1157 1158 const { options, projectPendingBuild } = state; 1159 for (let projectIndex = 0; projectIndex < buildOrder.length; projectIndex++) { 1160 const project = buildOrder[projectIndex]; 1161 const projectPath = toResolvedConfigFilePath(state, project); 1162 const reloadLevel = state.projectPendingBuild.get(projectPath); 1163 if (reloadLevel === undefined) continue; 1164 1165 if (reportQueue) { 1166 reportQueue = false; 1167 reportBuildQueue(state, buildOrder); 1168 } 1169 1170 const config = parseConfigFile(state, project, projectPath); 1171 if (!config) { 1172 reportParseConfigFileDiagnostic(state, projectPath); 1173 projectPendingBuild.delete(projectPath); 1174 continue; 1175 } 1176 1177 if (reloadLevel === ConfigFileProgramReloadLevel.Full) { 1178 watchConfigFile(state, project, projectPath, config); 1179 watchExtendedConfigFiles(state, projectPath, config); 1180 watchWildCardDirectories(state, project, projectPath, config); 1181 watchInputFiles(state, project, projectPath, config); 1182 } 1183 else if (reloadLevel === ConfigFileProgramReloadLevel.Partial) { 1184 // Update file names 1185 config.fileNames = getFileNamesFromConfigSpecs(config.options.configFile!.configFileSpecs!, getDirectoryPath(project), config.options, state.parseConfigFileHost); 1186 updateErrorForNoInputFiles(config.fileNames, project, config.options.configFile!.configFileSpecs!, config.errors, canJsonReportNoInputFiles(config.raw)); 1187 watchInputFiles(state, project, projectPath, config); 1188 } 1189 1190 const status = getUpToDateStatus(state, config, projectPath); 1191 verboseReportProjectStatus(state, project, status); 1192 if (!options.force) { 1193 if (status.type === UpToDateStatusType.UpToDate) { 1194 reportAndStoreErrors(state, projectPath, getConfigFileParsingDiagnostics(config)); 1195 projectPendingBuild.delete(projectPath); 1196 // Up to date, skip 1197 if (options.dry) { 1198 // In a dry build, inform the user of this fact 1199 reportStatus(state, Diagnostics.Project_0_is_up_to_date, project); 1200 } 1201 continue; 1202 } 1203 1204 if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) { 1205 reportAndStoreErrors(state, projectPath, getConfigFileParsingDiagnostics(config)); 1206 return createUpdateOutputFileStampsProject( 1207 state, 1208 project, 1209 projectPath, 1210 config, 1211 buildOrder 1212 ); 1213 } 1214 } 1215 1216 if (status.type === UpToDateStatusType.UpstreamBlocked) { 1217 reportAndStoreErrors(state, projectPath, getConfigFileParsingDiagnostics(config)); 1218 projectPendingBuild.delete(projectPath); 1219 if (options.verbose) { 1220 reportStatus( 1221 state, 1222 status.upstreamProjectBlocked ? 1223 Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_was_not_built : 1224 Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, 1225 project, 1226 status.upstreamProjectName 1227 ); 1228 } 1229 continue; 1230 } 1231 1232 if (status.type === UpToDateStatusType.ContainerOnly) { 1233 reportAndStoreErrors(state, projectPath, getConfigFileParsingDiagnostics(config)); 1234 projectPendingBuild.delete(projectPath); 1235 // Do nothing 1236 continue; 1237 } 1238 1239 return createBuildOrUpdateInvalidedProject( 1240 needsBuild(state, status, config) ? 1241 InvalidatedProjectKind.Build : 1242 InvalidatedProjectKind.UpdateBundle, 1243 state, 1244 project, 1245 projectPath, 1246 projectIndex, 1247 config, 1248 buildOrder, 1249 ); 1250 } 1251 1252 return undefined; 1253 } 1254 1255 function listEmittedFile({ write }: SolutionBuilderState, proj: ParsedCommandLine, file: string) { 1256 if (write && proj.options.listEmittedFiles) { 1257 write(`TSFILE: ${file}`); 1258 } 1259 } 1260 1261 function getOldProgram<T extends BuilderProgram>({ options, builderPrograms, compilerHost }: SolutionBuilderState<T>, proj: ResolvedConfigFilePath, parsed: ParsedCommandLine) { 1262 if (options.force) return undefined; 1263 const value = builderPrograms.get(proj); 1264 if (value) return value; 1265 return readBuilderProgram(parsed.options, compilerHost) as any as T; 1266 } 1267 1268 function afterProgramDone<T extends BuilderProgram>( 1269 state: SolutionBuilderState<T>, 1270 program: T | undefined, 1271 config: ParsedCommandLine 1272 ) { 1273 if (program) { 1274 if (program && state.write) listFiles(program, state.write); 1275 if (state.host.afterProgramEmitAndDiagnostics) { 1276 state.host.afterProgramEmitAndDiagnostics(program); 1277 } 1278 program.releaseProgram(); 1279 } 1280 else if (state.host.afterEmitBundle) { 1281 state.host.afterEmitBundle(config); 1282 } 1283 state.projectCompilerOptions = state.baseCompilerOptions; 1284 } 1285 1286 function buildErrors<T extends BuilderProgram>( 1287 state: SolutionBuilderState<T>, 1288 resolvedPath: ResolvedConfigFilePath, 1289 program: T | undefined, 1290 config: ParsedCommandLine, 1291 diagnostics: readonly Diagnostic[], 1292 buildResult: BuildResultFlags, 1293 errorType: string, 1294 ) { 1295 const canEmitBuildInfo = !(buildResult & BuildResultFlags.SyntaxErrors) && program && !outFile(program.getCompilerOptions()); 1296 1297 reportAndStoreErrors(state, resolvedPath, diagnostics); 1298 state.projectStatus.set(resolvedPath, { type: UpToDateStatusType.Unbuildable, reason: `${errorType} errors` }); 1299 if (canEmitBuildInfo) return { buildResult, step: BuildStep.EmitBuildInfo }; 1300 afterProgramDone(state, program, config); 1301 return { buildResult, step: BuildStep.QueueReferencingProjects }; 1302 } 1303 1304 function updateModuleResolutionCache( 1305 state: SolutionBuilderState, 1306 proj: ResolvedConfigFileName, 1307 config: ParsedCommandLine 1308 ) { 1309 if (!state.moduleResolutionCache) return; 1310 1311 // Update module resolution cache if needed 1312 const { moduleResolutionCache } = state; 1313 const projPath = toPath(state, proj); 1314 if (moduleResolutionCache.directoryToModuleNameMap.redirectsMap.size === 0) { 1315 // The own map will be for projectCompilerOptions 1316 Debug.assert(moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.size === 0); 1317 moduleResolutionCache.directoryToModuleNameMap.redirectsMap.set(projPath, moduleResolutionCache.directoryToModuleNameMap.ownMap); 1318 moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.set(projPath, moduleResolutionCache.moduleNameToDirectoryMap.ownMap); 1319 } 1320 else { 1321 // Set correct own map 1322 Debug.assert(moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.size > 0); 1323 1324 const ref: ResolvedProjectReference = { 1325 sourceFile: config.options.configFile!, 1326 commandLine: config 1327 }; 1328 moduleResolutionCache.directoryToModuleNameMap.setOwnMap(moduleResolutionCache.directoryToModuleNameMap.getOrCreateMapOfCacheRedirects(ref)); 1329 moduleResolutionCache.moduleNameToDirectoryMap.setOwnMap(moduleResolutionCache.moduleNameToDirectoryMap.getOrCreateMapOfCacheRedirects(ref)); 1330 } 1331 moduleResolutionCache.directoryToModuleNameMap.setOwnOptions(config.options); 1332 moduleResolutionCache.moduleNameToDirectoryMap.setOwnOptions(config.options); 1333 } 1334 1335 function checkConfigFileUpToDateStatus(state: SolutionBuilderState, configFile: string, oldestOutputFileTime: Date, oldestOutputFileName: string): Status.OutOfDateWithSelf | undefined { 1336 // Check tsconfig time 1337 const tsconfigTime = state.host.getModifiedTime(configFile) || missingFileModifiedTime; 1338 if (oldestOutputFileTime < tsconfigTime) { 1339 return { 1340 type: UpToDateStatusType.OutOfDateWithSelf, 1341 outOfDateOutputFileName: oldestOutputFileName, 1342 newerInputFileName: configFile 1343 }; 1344 } 1345 } 1346 1347 function getUpToDateStatusWorker(state: SolutionBuilderState, project: ParsedCommandLine, resolvedPath: ResolvedConfigFilePath): UpToDateStatus { 1348 let newestInputFileName: string = undefined!; 1349 let newestInputFileTime = minimumDate; 1350 const { host } = state; 1351 // Get timestamps of input files 1352 for (const inputFile of project.fileNames) { 1353 if (!host.fileExists(inputFile)) { 1354 return { 1355 type: UpToDateStatusType.Unbuildable, 1356 reason: `${inputFile} does not exist` 1357 }; 1358 } 1359 1360 const inputTime = host.getModifiedTime(inputFile) || missingFileModifiedTime; 1361 if (inputTime > newestInputFileTime) { 1362 newestInputFileName = inputFile; 1363 newestInputFileTime = inputTime; 1364 } 1365 } 1366 1367 // Container if no files are specified in the project 1368 if (!project.fileNames.length && !canJsonReportNoInputFiles(project.raw)) { 1369 return { 1370 type: UpToDateStatusType.ContainerOnly 1371 }; 1372 } 1373 1374 // Collect the expected outputs of this project 1375 const outputs = getAllProjectOutputs(project, !host.useCaseSensitiveFileNames()); 1376 1377 // Now see if all outputs are newer than the newest input 1378 let oldestOutputFileName = "(none)"; 1379 let oldestOutputFileTime = maximumDate; 1380 let newestOutputFileName = "(none)"; 1381 let newestOutputFileTime = minimumDate; 1382 let missingOutputFileName: string | undefined; 1383 let newestDeclarationFileContentChangedTime = minimumDate; 1384 let isOutOfDateWithInputs = false; 1385 for (const output of outputs) { 1386 // Output is missing; can stop checking 1387 // Don't immediately return because we can still be upstream-blocked, which is a higher-priority status 1388 if (!host.fileExists(output)) { 1389 missingOutputFileName = output; 1390 break; 1391 } 1392 1393 const outputTime = host.getModifiedTime(output) || missingFileModifiedTime; 1394 if (outputTime < oldestOutputFileTime) { 1395 oldestOutputFileTime = outputTime; 1396 oldestOutputFileName = output; 1397 } 1398 1399 // If an output is older than the newest input, we can stop checking 1400 // Don't immediately return because we can still be upstream-blocked, which is a higher-priority status 1401 if (outputTime < newestInputFileTime) { 1402 isOutOfDateWithInputs = true; 1403 break; 1404 } 1405 1406 if (outputTime > newestOutputFileTime) { 1407 newestOutputFileTime = outputTime; 1408 newestOutputFileName = output; 1409 } 1410 1411 // Keep track of when the most recent time a .d.ts file was changed. 1412 // In addition to file timestamps, we also keep track of when a .d.ts file 1413 // had its file touched but not had its contents changed - this allows us 1414 // to skip a downstream typecheck 1415 if (isDeclarationFile(output)) { 1416 const outputModifiedTime = host.getModifiedTime(output) || missingFileModifiedTime; 1417 newestDeclarationFileContentChangedTime = newer(newestDeclarationFileContentChangedTime, outputModifiedTime); 1418 } 1419 } 1420 1421 let pseudoUpToDate = false; 1422 let usesPrepend = false; 1423 let upstreamChangedProject: string | undefined; 1424 if (project.projectReferences) { 1425 state.projectStatus.set(resolvedPath, { type: UpToDateStatusType.ComputingUpstream }); 1426 for (const ref of project.projectReferences) { 1427 usesPrepend = usesPrepend || !!(ref.prepend); 1428 const resolvedRef = resolveProjectReferencePath(ref); 1429 const resolvedRefPath = toResolvedConfigFilePath(state, resolvedRef); 1430 const refStatus = getUpToDateStatus(state, parseConfigFile(state, resolvedRef, resolvedRefPath), resolvedRefPath); 1431 1432 // Its a circular reference ignore the status of this project 1433 if (refStatus.type === UpToDateStatusType.ComputingUpstream || 1434 refStatus.type === UpToDateStatusType.ContainerOnly) { // Container only ignore this project 1435 continue; 1436 } 1437 1438 // An upstream project is blocked 1439 if (refStatus.type === UpToDateStatusType.Unbuildable || 1440 refStatus.type === UpToDateStatusType.UpstreamBlocked) { 1441 return { 1442 type: UpToDateStatusType.UpstreamBlocked, 1443 upstreamProjectName: ref.path, 1444 upstreamProjectBlocked: refStatus.type === UpToDateStatusType.UpstreamBlocked 1445 }; 1446 } 1447 1448 // If the upstream project is out of date, then so are we (someone shouldn't have asked, though?) 1449 if (refStatus.type !== UpToDateStatusType.UpToDate) { 1450 return { 1451 type: UpToDateStatusType.UpstreamOutOfDate, 1452 upstreamProjectName: ref.path 1453 }; 1454 } 1455 1456 // Check oldest output file name only if there is no missing output file name 1457 if (!missingOutputFileName) { 1458 // If the upstream project's newest file is older than our oldest output, we 1459 // can't be out of date because of it 1460 if (refStatus.newestInputFileTime && refStatus.newestInputFileTime <= oldestOutputFileTime) { 1461 continue; 1462 } 1463 1464 // If the upstream project has only change .d.ts files, and we've built 1465 // *after* those files, then we're "psuedo up to date" and eligible for a fast rebuild 1466 if (refStatus.newestDeclarationFileContentChangedTime && refStatus.newestDeclarationFileContentChangedTime <= oldestOutputFileTime) { 1467 pseudoUpToDate = true; 1468 upstreamChangedProject = ref.path; 1469 continue; 1470 } 1471 1472 // We have an output older than an upstream output - we are out of date 1473 Debug.assert(oldestOutputFileName !== undefined, "Should have an oldest output filename here"); 1474 return { 1475 type: UpToDateStatusType.OutOfDateWithUpstream, 1476 outOfDateOutputFileName: oldestOutputFileName, 1477 newerProjectName: ref.path 1478 }; 1479 } 1480 } 1481 } 1482 1483 if (missingOutputFileName !== undefined) { 1484 return { 1485 type: UpToDateStatusType.OutputMissing, 1486 missingOutputFileName 1487 }; 1488 } 1489 1490 if (isOutOfDateWithInputs) { 1491 return { 1492 type: UpToDateStatusType.OutOfDateWithSelf, 1493 outOfDateOutputFileName: oldestOutputFileName, 1494 newerInputFileName: newestInputFileName 1495 }; 1496 } 1497 else { 1498 // Check tsconfig time 1499 const configStatus = checkConfigFileUpToDateStatus(state, project.options.configFilePath!, oldestOutputFileTime, oldestOutputFileName); 1500 if (configStatus) return configStatus; 1501 1502 // Check extended config time 1503 const extendedConfigStatus = forEach(project.options.configFile!.extendedSourceFiles || emptyArray, configFile => checkConfigFileUpToDateStatus(state, configFile, oldestOutputFileTime, oldestOutputFileName)); 1504 if (extendedConfigStatus) return extendedConfigStatus; 1505 } 1506 1507 if (!state.buildInfoChecked.has(resolvedPath)) { 1508 state.buildInfoChecked.set(resolvedPath, true); 1509 const buildInfoPath = getTsBuildInfoEmitOutputFilePath(project.options); 1510 if (buildInfoPath) { 1511 const value = state.readFileWithCache(buildInfoPath); 1512 const buildInfo = value && getBuildInfo(value); 1513 if (buildInfo && (buildInfo.bundle || buildInfo.program) && buildInfo.version !== version) { 1514 return { 1515 type: UpToDateStatusType.TsVersionOutputOfDate, 1516 version: buildInfo.version 1517 }; 1518 } 1519 } 1520 } 1521 1522 if (usesPrepend && pseudoUpToDate) { 1523 return { 1524 type: UpToDateStatusType.OutOfDateWithPrepend, 1525 outOfDateOutputFileName: oldestOutputFileName, 1526 newerProjectName: upstreamChangedProject! 1527 }; 1528 } 1529 1530 // Up to date 1531 return { 1532 type: pseudoUpToDate ? UpToDateStatusType.UpToDateWithUpstreamTypes : UpToDateStatusType.UpToDate, 1533 newestDeclarationFileContentChangedTime, 1534 newestInputFileTime, 1535 newestOutputFileTime, 1536 newestInputFileName, 1537 newestOutputFileName, 1538 oldestOutputFileName 1539 }; 1540 } 1541 1542 function getUpToDateStatus(state: SolutionBuilderState, project: ParsedCommandLine | undefined, resolvedPath: ResolvedConfigFilePath): UpToDateStatus { 1543 if (project === undefined) { 1544 return { type: UpToDateStatusType.Unbuildable, reason: "File deleted mid-build" }; 1545 } 1546 1547 const prior = state.projectStatus.get(resolvedPath); 1548 if (prior !== undefined) { 1549 return prior; 1550 } 1551 1552 const actual = getUpToDateStatusWorker(state, project, resolvedPath); 1553 state.projectStatus.set(resolvedPath, actual); 1554 return actual; 1555 } 1556 1557 function updateOutputTimestampsWorker(state: SolutionBuilderState, proj: ParsedCommandLine, priorNewestUpdateTime: Date, verboseMessage: DiagnosticMessage, skipOutputs?: ESMap<Path, string>) { 1558 const { host } = state; 1559 const outputs = getAllProjectOutputs(proj, !host.useCaseSensitiveFileNames()); 1560 if (!skipOutputs || outputs.length !== skipOutputs.size) { 1561 let reportVerbose = !!state.options.verbose; 1562 const now = host.now ? host.now() : new Date(); 1563 for (const file of outputs) { 1564 if (skipOutputs && skipOutputs.has(toPath(state, file))) { 1565 continue; 1566 } 1567 1568 if (reportVerbose) { 1569 reportVerbose = false; 1570 reportStatus(state, verboseMessage, proj.options.configFilePath!); 1571 } 1572 1573 if (isDeclarationFile(file)) { 1574 priorNewestUpdateTime = newer(priorNewestUpdateTime, host.getModifiedTime(file) || missingFileModifiedTime); 1575 } 1576 1577 host.setModifiedTime(file, now); 1578 } 1579 } 1580 1581 return priorNewestUpdateTime; 1582 } 1583 1584 function updateOutputTimestamps(state: SolutionBuilderState, proj: ParsedCommandLine, resolvedPath: ResolvedConfigFilePath) { 1585 if (state.options.dry) { 1586 return reportStatus(state, Diagnostics.A_non_dry_build_would_update_timestamps_for_output_of_project_0, proj.options.configFilePath!); 1587 } 1588 const priorNewestUpdateTime = updateOutputTimestampsWorker(state, proj, minimumDate, Diagnostics.Updating_output_timestamps_of_project_0); 1589 state.projectStatus.set(resolvedPath, { 1590 type: UpToDateStatusType.UpToDate, 1591 newestDeclarationFileContentChangedTime: priorNewestUpdateTime, 1592 oldestOutputFileName: getFirstProjectOutput(proj, !state.host.useCaseSensitiveFileNames()) 1593 }); 1594 } 1595 1596 function queueReferencingProjects( 1597 state: SolutionBuilderState, 1598 project: ResolvedConfigFileName, 1599 projectPath: ResolvedConfigFilePath, 1600 projectIndex: number, 1601 config: ParsedCommandLine, 1602 buildOrder: readonly ResolvedConfigFileName[], 1603 buildResult: BuildResultFlags 1604 ) { 1605 // Queue only if there are no errors 1606 if (buildResult & BuildResultFlags.AnyErrors) return; 1607 // Only composite projects can be referenced by other projects 1608 if (!config.options.composite) return; 1609 // Always use build order to queue projects 1610 for (let index = projectIndex + 1; index < buildOrder.length; index++) { 1611 const nextProject = buildOrder[index]; 1612 const nextProjectPath = toResolvedConfigFilePath(state, nextProject); 1613 if (state.projectPendingBuild.has(nextProjectPath)) continue; 1614 1615 const nextProjectConfig = parseConfigFile(state, nextProject, nextProjectPath); 1616 if (!nextProjectConfig || !nextProjectConfig.projectReferences) continue; 1617 for (const ref of nextProjectConfig.projectReferences) { 1618 const resolvedRefPath = resolveProjectName(state, ref.path); 1619 if (toResolvedConfigFilePath(state, resolvedRefPath) !== projectPath) continue; 1620 // If the project is referenced with prepend, always build downstream projects, 1621 // If declaration output is changed, build the project 1622 // otherwise mark the project UpToDateWithUpstreamTypes so it updates output time stamps 1623 const status = state.projectStatus.get(nextProjectPath); 1624 if (status) { 1625 switch (status.type) { 1626 case UpToDateStatusType.UpToDate: 1627 if (buildResult & BuildResultFlags.DeclarationOutputUnchanged) { 1628 if (ref.prepend) { 1629 state.projectStatus.set(nextProjectPath, { 1630 type: UpToDateStatusType.OutOfDateWithPrepend, 1631 outOfDateOutputFileName: status.oldestOutputFileName, 1632 newerProjectName: project 1633 }); 1634 } 1635 else { 1636 status.type = UpToDateStatusType.UpToDateWithUpstreamTypes; 1637 } 1638 break; 1639 } 1640 // falls through 1641 1642 case UpToDateStatusType.UpToDateWithUpstreamTypes: 1643 case UpToDateStatusType.OutOfDateWithPrepend: 1644 if (!(buildResult & BuildResultFlags.DeclarationOutputUnchanged)) { 1645 state.projectStatus.set(nextProjectPath, { 1646 type: UpToDateStatusType.OutOfDateWithUpstream, 1647 outOfDateOutputFileName: status.type === UpToDateStatusType.OutOfDateWithPrepend ? status.outOfDateOutputFileName : status.oldestOutputFileName, 1648 newerProjectName: project 1649 }); 1650 } 1651 break; 1652 1653 case UpToDateStatusType.UpstreamBlocked: 1654 if (toResolvedConfigFilePath(state, resolveProjectName(state, status.upstreamProjectName)) === projectPath) { 1655 clearProjectStatus(state, nextProjectPath); 1656 } 1657 break; 1658 } 1659 } 1660 addProjToQueue(state, nextProjectPath, ConfigFileProgramReloadLevel.None); 1661 break; 1662 } 1663 } 1664 } 1665 1666 function build(state: SolutionBuilderState, project?: string, cancellationToken?: CancellationToken, onlyReferences?: boolean): ExitStatus { 1667 const buildOrder = getBuildOrderFor(state, project, onlyReferences); 1668 if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; 1669 1670 setupInitialBuild(state, cancellationToken); 1671 1672 let reportQueue = true; 1673 let successfulProjects = 0; 1674 while (true) { 1675 const invalidatedProject = getNextInvalidatedProject(state, buildOrder, reportQueue); 1676 if (!invalidatedProject) break; 1677 reportQueue = false; 1678 invalidatedProject.done(cancellationToken); 1679 if (!state.diagnostics.has(invalidatedProject.projectPath)) successfulProjects++; 1680 } 1681 1682 disableCache(state); 1683 reportErrorSummary(state, buildOrder); 1684 startWatching(state, buildOrder); 1685 1686 return isCircularBuildOrder(buildOrder) 1687 ? ExitStatus.ProjectReferenceCycle_OutputsSkipped 1688 : !buildOrder.some(p => state.diagnostics.has(toResolvedConfigFilePath(state, p))) 1689 ? ExitStatus.Success 1690 : successfulProjects 1691 ? ExitStatus.DiagnosticsPresent_OutputsGenerated 1692 : ExitStatus.DiagnosticsPresent_OutputsSkipped; 1693 } 1694 1695 function clean(state: SolutionBuilderState, project?: string, onlyReferences?: boolean) { 1696 const buildOrder = getBuildOrderFor(state, project, onlyReferences); 1697 if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; 1698 1699 if (isCircularBuildOrder(buildOrder)) { 1700 reportErrors(state, buildOrder.circularDiagnostics); 1701 return ExitStatus.ProjectReferenceCycle_OutputsSkipped; 1702 } 1703 1704 const { options, host } = state; 1705 const filesToDelete = options.dry ? [] as string[] : undefined; 1706 for (const proj of buildOrder) { 1707 const resolvedPath = toResolvedConfigFilePath(state, proj); 1708 const parsed = parseConfigFile(state, proj, resolvedPath); 1709 if (parsed === undefined) { 1710 // File has gone missing; fine to ignore here 1711 reportParseConfigFileDiagnostic(state, resolvedPath); 1712 continue; 1713 } 1714 const outputs = getAllProjectOutputs(parsed, !host.useCaseSensitiveFileNames()); 1715 for (const output of outputs) { 1716 if (host.fileExists(output)) { 1717 if (filesToDelete) { 1718 filesToDelete.push(output); 1719 } 1720 else { 1721 host.deleteFile(output); 1722 invalidateProject(state, resolvedPath, ConfigFileProgramReloadLevel.None); 1723 } 1724 } 1725 } 1726 } 1727 1728 if (filesToDelete) { 1729 reportStatus(state, Diagnostics.A_non_dry_build_would_delete_the_following_files_Colon_0, filesToDelete.map(f => `\r\n * ${f}`).join("")); 1730 } 1731 1732 return ExitStatus.Success; 1733 } 1734 1735 function invalidateProject(state: SolutionBuilderState, resolved: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { 1736 // If host implements getParsedCommandLine, we cant get list of files from parseConfigFileHost 1737 if (state.host.getParsedCommandLine && reloadLevel === ConfigFileProgramReloadLevel.Partial) { 1738 reloadLevel = ConfigFileProgramReloadLevel.Full; 1739 } 1740 if (reloadLevel === ConfigFileProgramReloadLevel.Full) { 1741 state.configFileCache.delete(resolved); 1742 state.buildOrder = undefined; 1743 } 1744 state.needsSummary = true; 1745 clearProjectStatus(state, resolved); 1746 addProjToQueue(state, resolved, reloadLevel); 1747 enableCache(state); 1748 } 1749 1750 function invalidateProjectAndScheduleBuilds(state: SolutionBuilderState, resolvedPath: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { 1751 state.reportFileChangeDetected = true; 1752 invalidateProject(state, resolvedPath, reloadLevel); 1753 scheduleBuildInvalidatedProject(state); 1754 } 1755 1756 function scheduleBuildInvalidatedProject(state: SolutionBuilderState) { 1757 const { hostWithWatch } = state; 1758 if (!hostWithWatch.setTimeout || !hostWithWatch.clearTimeout) { 1759 return; 1760 } 1761 if (state.timerToBuildInvalidatedProject) { 1762 hostWithWatch.clearTimeout(state.timerToBuildInvalidatedProject); 1763 } 1764 state.timerToBuildInvalidatedProject = hostWithWatch.setTimeout(buildNextInvalidatedProject, 250, state); 1765 } 1766 1767 function buildNextInvalidatedProject(state: SolutionBuilderState) { 1768 state.timerToBuildInvalidatedProject = undefined; 1769 if (state.reportFileChangeDetected) { 1770 state.reportFileChangeDetected = false; 1771 state.projectErrorsReported.clear(); 1772 reportWatchStatus(state, Diagnostics.File_change_detected_Starting_incremental_compilation); 1773 } 1774 const buildOrder = getBuildOrder(state); 1775 const invalidatedProject = getNextInvalidatedProject(state, buildOrder, /*reportQueue*/ false); 1776 if (invalidatedProject) { 1777 invalidatedProject.done(); 1778 if (state.projectPendingBuild.size) { 1779 // Schedule next project for build 1780 if (state.watch && !state.timerToBuildInvalidatedProject) { 1781 scheduleBuildInvalidatedProject(state); 1782 } 1783 return; 1784 } 1785 } 1786 disableCache(state); 1787 reportErrorSummary(state, buildOrder); 1788 } 1789 1790 function watchConfigFile(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine | undefined) { 1791 if (!state.watch || state.allWatchedConfigFiles.has(resolvedPath)) return; 1792 state.allWatchedConfigFiles.set(resolvedPath, state.watchFile( 1793 resolved, 1794 () => { 1795 invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.Full); 1796 }, 1797 PollingInterval.High, 1798 parsed?.watchOptions, 1799 WatchType.ConfigFile, 1800 resolved 1801 )); 1802 } 1803 1804 function watchExtendedConfigFiles(state: SolutionBuilderState, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine | undefined) { 1805 updateSharedExtendedConfigFileWatcher( 1806 resolvedPath, 1807 parsed, 1808 state.allWatchedExtendedConfigFiles, 1809 (extendedConfigFileName, extendedConfigFilePath) => state.watchFile( 1810 extendedConfigFileName, 1811 () => state.allWatchedExtendedConfigFiles.get(extendedConfigFilePath)?.projects.forEach(projectConfigFilePath => 1812 invalidateProjectAndScheduleBuilds(state, projectConfigFilePath, ConfigFileProgramReloadLevel.Full) 1813 ), 1814 PollingInterval.High, 1815 parsed?.watchOptions, 1816 WatchType.ExtendedConfigFile, 1817 ), 1818 fileName => toPath(state, fileName), 1819 ); 1820 } 1821 1822 function watchWildCardDirectories(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { 1823 if (!state.watch) return; 1824 updateWatchingWildcardDirectories( 1825 getOrCreateValueMapFromConfigFileMap(state.allWatchedWildcardDirectories, resolvedPath), 1826 new Map(getEntries(parsed.wildcardDirectories!)), 1827 (dir, flags) => state.watchDirectory( 1828 dir, 1829 fileOrDirectory => { 1830 if (isIgnoredFileFromWildCardWatching({ 1831 watchedDirPath: toPath(state, dir), 1832 fileOrDirectory, 1833 fileOrDirectoryPath: toPath(state, fileOrDirectory), 1834 configFileName: resolved, 1835 currentDirectory: state.currentDirectory, 1836 options: parsed.options, 1837 program: state.builderPrograms.get(resolvedPath), 1838 useCaseSensitiveFileNames: state.parseConfigFileHost.useCaseSensitiveFileNames, 1839 writeLog: s => state.writeLog(s) 1840 })) return; 1841 1842 invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.Partial); 1843 }, 1844 flags, 1845 parsed?.watchOptions, 1846 WatchType.WildcardDirectory, 1847 resolved 1848 ) 1849 ); 1850 } 1851 1852 function watchInputFiles(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { 1853 if (!state.watch) return; 1854 mutateMap( 1855 getOrCreateValueMapFromConfigFileMap(state.allWatchedInputFiles, resolvedPath), 1856 arrayToMap(parsed.fileNames, fileName => toPath(state, fileName)), 1857 { 1858 createNewValue: (_path, input) => state.watchFile( 1859 input, 1860 () => invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.None), 1861 PollingInterval.Low, 1862 parsed?.watchOptions, 1863 WatchType.SourceFile, 1864 resolved 1865 ), 1866 onDeleteValue: closeFileWatcher, 1867 } 1868 ); 1869 } 1870 1871 function startWatching(state: SolutionBuilderState, buildOrder: AnyBuildOrder) { 1872 if (!state.watchAllProjectsPending) return; 1873 state.watchAllProjectsPending = false; 1874 for (const resolved of getBuildOrderFromAnyBuildOrder(buildOrder)) { 1875 const resolvedPath = toResolvedConfigFilePath(state, resolved); 1876 const cfg = parseConfigFile(state, resolved, resolvedPath); 1877 // Watch this file 1878 watchConfigFile(state, resolved, resolvedPath, cfg); 1879 watchExtendedConfigFiles(state, resolvedPath, cfg); 1880 if (cfg) { 1881 // Update watchers for wildcard directories 1882 watchWildCardDirectories(state, resolved, resolvedPath, cfg); 1883 1884 // Watch input files 1885 watchInputFiles(state, resolved, resolvedPath, cfg); 1886 } 1887 } 1888 } 1889 1890 function stopWatching(state: SolutionBuilderState) { 1891 clearMap(state.allWatchedConfigFiles, closeFileWatcher); 1892 clearMap(state.allWatchedExtendedConfigFiles, watcher => { 1893 watcher.projects.clear(); 1894 watcher.close(); 1895 }); 1896 clearMap(state.allWatchedWildcardDirectories, watchedWildcardDirectories => clearMap(watchedWildcardDirectories, closeFileWatcherOf)); 1897 clearMap(state.allWatchedInputFiles, watchedWildcardDirectories => clearMap(watchedWildcardDirectories, closeFileWatcher)); 1898 } 1899 1900 /** 1901 * A SolutionBuilder has an immutable set of rootNames that are the "entry point" projects, but 1902 * can dynamically add/remove other projects based on changes on the rootNames' references 1903 */ 1904 function createSolutionBuilderWorker<T extends BuilderProgram>(watch: false, host: SolutionBuilderHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder<T>; 1905 function createSolutionBuilderWorker<T extends BuilderProgram>(watch: true, host: SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions, baseWatchOptions?: WatchOptions): SolutionBuilder<T>; 1906 function createSolutionBuilderWorker<T extends BuilderProgram>(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], options: BuildOptions, baseWatchOptions?: WatchOptions): SolutionBuilder<T> { 1907 const state = createSolutionBuilderState(watch, hostOrHostWithWatch, rootNames, options, baseWatchOptions); 1908 return { 1909 build: (project, cancellationToken) => build(state, project, cancellationToken), 1910 clean: project => clean(state, project), 1911 buildReferences: (project, cancellationToken) => build(state, project, cancellationToken, /*onlyReferences*/ true), 1912 cleanReferences: project => clean(state, project, /*onlyReferences*/ true), 1913 getNextInvalidatedProject: cancellationToken => { 1914 setupInitialBuild(state, cancellationToken); 1915 return getNextInvalidatedProject(state, getBuildOrder(state), /*reportQueue*/ false); 1916 }, 1917 getBuildOrder: () => getBuildOrder(state), 1918 getUpToDateStatusOfProject: project => { 1919 const configFileName = resolveProjectName(state, project); 1920 const configFilePath = toResolvedConfigFilePath(state, configFileName); 1921 return getUpToDateStatus(state, parseConfigFile(state, configFileName, configFilePath), configFilePath); 1922 }, 1923 invalidateProject: (configFilePath, reloadLevel) => invalidateProject(state, configFilePath, reloadLevel || ConfigFileProgramReloadLevel.None), 1924 buildNextInvalidatedProject: () => buildNextInvalidatedProject(state), 1925 getAllParsedConfigs: () => arrayFrom(mapDefinedIterator( 1926 state.configFileCache.values(), 1927 config => isParsedCommandLine(config) ? config : undefined 1928 )), 1929 close: () => stopWatching(state), 1930 }; 1931 } 1932 1933 function relName(state: SolutionBuilderState, path: string): string { 1934 return convertToRelativePath(path, state.currentDirectory, f => state.getCanonicalFileName(f)); 1935 } 1936 1937 function reportStatus(state: SolutionBuilderState, message: DiagnosticMessage, ...args: string[]) { 1938 state.host.reportSolutionBuilderStatus(createCompilerDiagnostic(message, ...args)); 1939 } 1940 1941 function reportWatchStatus(state: SolutionBuilderState, message: DiagnosticMessage, ...args: (string | number | undefined)[]) { 1942 state.hostWithWatch.onWatchStatusChange?.(createCompilerDiagnostic(message, ...args), state.host.getNewLine(), state.baseCompilerOptions); 1943 } 1944 1945 function reportErrors({ host }: SolutionBuilderState, errors: readonly Diagnostic[]) { 1946 errors.forEach(err => host.reportDiagnostic(err)); 1947 } 1948 1949 function reportAndStoreErrors(state: SolutionBuilderState, proj: ResolvedConfigFilePath, errors: readonly Diagnostic[]) { 1950 reportErrors(state, errors); 1951 state.projectErrorsReported.set(proj, true); 1952 if (errors.length) { 1953 state.diagnostics.set(proj, errors); 1954 } 1955 } 1956 1957 function reportParseConfigFileDiagnostic(state: SolutionBuilderState, proj: ResolvedConfigFilePath) { 1958 reportAndStoreErrors(state, proj, [state.configFileCache.get(proj) as Diagnostic]); 1959 } 1960 1961 function reportErrorSummary(state: SolutionBuilderState, buildOrder: AnyBuildOrder) { 1962 if (!state.needsSummary) return; 1963 state.needsSummary = false; 1964 const canReportSummary = state.watch || !!state.host.reportErrorSummary; 1965 const { diagnostics } = state; 1966 let totalErrors = 0; 1967 if (isCircularBuildOrder(buildOrder)) { 1968 reportBuildQueue(state, buildOrder.buildOrder); 1969 reportErrors(state, buildOrder.circularDiagnostics); 1970 if (canReportSummary) totalErrors += getErrorCountForSummary(buildOrder.circularDiagnostics); 1971 } 1972 else { 1973 // Report errors from the other projects 1974 buildOrder.forEach(project => { 1975 const projectPath = toResolvedConfigFilePath(state, project); 1976 if (!state.projectErrorsReported.has(projectPath)) { 1977 reportErrors(state, diagnostics.get(projectPath) || emptyArray); 1978 } 1979 }); 1980 if (canReportSummary) diagnostics.forEach(singleProjectErrors => totalErrors += getErrorCountForSummary(singleProjectErrors)); 1981 } 1982 1983 if (state.watch) { 1984 reportWatchStatus(state, getWatchErrorSummaryDiagnosticMessage(totalErrors), totalErrors); 1985 } 1986 else if (state.host.reportErrorSummary) { 1987 state.host.reportErrorSummary(totalErrors); 1988 } 1989 } 1990 1991 /** 1992 * Report the build ordering inferred from the current project graph if we're in verbose mode 1993 */ 1994 function reportBuildQueue(state: SolutionBuilderState, buildQueue: readonly ResolvedConfigFileName[]) { 1995 if (state.options.verbose) { 1996 reportStatus(state, Diagnostics.Projects_in_this_build_Colon_0, buildQueue.map(s => "\r\n * " + relName(state, s)).join("")); 1997 } 1998 } 1999 2000 function reportUpToDateStatus(state: SolutionBuilderState, configFileName: string, status: UpToDateStatus) { 2001 switch (status.type) { 2002 case UpToDateStatusType.OutOfDateWithSelf: 2003 return reportStatus( 2004 state, 2005 Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, 2006 relName(state, configFileName), 2007 relName(state, status.outOfDateOutputFileName), 2008 relName(state, status.newerInputFileName) 2009 ); 2010 case UpToDateStatusType.OutOfDateWithUpstream: 2011 return reportStatus( 2012 state, 2013 Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, 2014 relName(state, configFileName), 2015 relName(state, status.outOfDateOutputFileName), 2016 relName(state, status.newerProjectName) 2017 ); 2018 case UpToDateStatusType.OutputMissing: 2019 return reportStatus( 2020 state, 2021 Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, 2022 relName(state, configFileName), 2023 relName(state, status.missingOutputFileName) 2024 ); 2025 case UpToDateStatusType.UpToDate: 2026 if (status.newestInputFileTime !== undefined) { 2027 return reportStatus( 2028 state, 2029 Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, 2030 relName(state, configFileName), 2031 relName(state, status.newestInputFileName || ""), 2032 relName(state, status.oldestOutputFileName || "") 2033 ); 2034 } 2035 // Don't report anything for "up to date because it was already built" -- too verbose 2036 break; 2037 case UpToDateStatusType.OutOfDateWithPrepend: 2038 return reportStatus( 2039 state, 2040 Diagnostics.Project_0_is_out_of_date_because_output_of_its_dependency_1_has_changed, 2041 relName(state, configFileName), 2042 relName(state, status.newerProjectName) 2043 ); 2044 case UpToDateStatusType.UpToDateWithUpstreamTypes: 2045 return reportStatus( 2046 state, 2047 Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, 2048 relName(state, configFileName) 2049 ); 2050 case UpToDateStatusType.UpstreamOutOfDate: 2051 return reportStatus( 2052 state, 2053 Diagnostics.Project_0_is_out_of_date_because_its_dependency_1_is_out_of_date, 2054 relName(state, configFileName), 2055 relName(state, status.upstreamProjectName) 2056 ); 2057 case UpToDateStatusType.UpstreamBlocked: 2058 return reportStatus( 2059 state, 2060 status.upstreamProjectBlocked ? 2061 Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_was_not_built : 2062 Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors, 2063 relName(state, configFileName), 2064 relName(state, status.upstreamProjectName) 2065 ); 2066 case UpToDateStatusType.Unbuildable: 2067 return reportStatus( 2068 state, 2069 Diagnostics.Failed_to_parse_file_0_Colon_1, 2070 relName(state, configFileName), 2071 status.reason 2072 ); 2073 case UpToDateStatusType.TsVersionOutputOfDate: 2074 return reportStatus( 2075 state, 2076 Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, 2077 relName(state, configFileName), 2078 status.version, 2079 version 2080 ); 2081 case UpToDateStatusType.ContainerOnly: 2082 // Don't report status on "solution" projects 2083 // falls through 2084 case UpToDateStatusType.ComputingUpstream: 2085 // Should never leak from getUptoDateStatusWorker 2086 break; 2087 default: 2088 assertType<never>(status); 2089 } 2090 } 2091 2092 /** 2093 * Report the up-to-date status of a project if we're in verbose mode 2094 */ 2095 function verboseReportProjectStatus(state: SolutionBuilderState, configFileName: string, status: UpToDateStatus) { 2096 if (state.options.verbose) { 2097 reportUpToDateStatus(state, configFileName, status); 2098 } 2099 } 2100} 2101