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