• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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