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