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