• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts {
2    export interface ReadBuildProgramHost {
3        useCaseSensitiveFileNames(): boolean;
4        getCurrentDirectory(): string;
5        readFile(fileName: string): string | undefined;
6    }
7    export function readBuilderProgram(compilerOptions: CompilerOptions, host: ReadBuildProgramHost) {
8        if (outFile(compilerOptions)) return undefined;
9        const buildInfoPath = getTsBuildInfoEmitOutputFilePath(compilerOptions);
10        if (!buildInfoPath) return undefined;
11        const content = host.readFile(buildInfoPath);
12        if (!content) return undefined;
13        const buildInfo = getBuildInfo(content);
14        if (buildInfo.version !== version) return undefined;
15        if (!buildInfo.program) return undefined;
16        return createBuildProgramUsingProgramBuildInfo(buildInfo.program, buildInfoPath, host);
17    }
18
19    export function createIncrementalCompilerHost(options: CompilerOptions, system = sys): CompilerHost {
20        const host = createCompilerHostWorker(options, /*setParentNodes*/ undefined, system);
21        host.createHash = maybeBind(system, system.createHash);
22        setGetSourceFileAsHashVersioned(host, system);
23        changeCompilerHostLikeToUseCache(host, fileName => toPath(fileName, host.getCurrentDirectory(), host.getCanonicalFileName));
24        return host;
25    }
26
27    export interface IncrementalProgramOptions<T extends BuilderProgram> {
28        rootNames: readonly string[];
29        options: CompilerOptions;
30        configFileParsingDiagnostics?: readonly Diagnostic[];
31        projectReferences?: readonly ProjectReference[];
32        host?: CompilerHost;
33        createProgram?: CreateProgram<T>;
34    }
35
36    export function createIncrementalProgram<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>({
37        rootNames, options, configFileParsingDiagnostics, projectReferences, host, createProgram
38    }: IncrementalProgramOptions<T>): T {
39        host = host || createIncrementalCompilerHost(options);
40        createProgram = createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram<T>;
41        const oldProgram = readBuilderProgram(options, host) as any as T;
42        return createProgram(rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences);
43    }
44
45    export type WatchStatusReporter = (diagnostic: Diagnostic, newLine: string, options: CompilerOptions, errorCount?: number) => void;
46    /** Create the program with rootNames and options, if they are undefined, oldProgram and new configFile diagnostics create new program */
47    export type CreateProgram<T extends BuilderProgram> = (rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[] | undefined) => T;
48
49    /** Host that has watch functionality used in --watch mode */
50    export interface WatchHost {
51        /** If provided, called with Diagnostic message that informs about change in watch status */
52        onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions, errorCount?: number): void;
53
54        /** Used to watch changes in source files, missing files needed to update the program or config file */
55        watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: CompilerOptions): FileWatcher;
56        /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */
57        watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: CompilerOptions): FileWatcher;
58        /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */
59        setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
60        /** If provided, will be used to reset existing delayed compilation */
61        clearTimeout?(timeoutId: any): void;
62    }
63    export interface ProgramHost<T extends BuilderProgram> {
64        /**
65         * Used to create the program when need for program creation or recreation detected
66         */
67        createProgram: CreateProgram<T>;
68
69        // Sub set of compiler host methods to read and generate new program
70        useCaseSensitiveFileNames(): boolean;
71        getNewLine(): string;
72        getCurrentDirectory(): string;
73        getDefaultLibFileName(options: CompilerOptions): string;
74        getDefaultLibLocation?(): string;
75        createHash?(data: string): string;
76
77        /**
78         * Use to check file presence for source files and
79         * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well
80         */
81        fileExists(path: string): boolean;
82        /**
83         * Use to read file text for source files and
84         * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well
85         */
86        readFile(path: string, encoding?: string): string | undefined;
87
88        /** If provided, used for module resolution as well as to handle directory structure */
89        directoryExists?(path: string): boolean;
90        /** If provided, used in resolutions as well as handling directory structure */
91        getDirectories?(path: string): string[];
92        /** If provided, used to cache and handle directory structure modifications */
93        readDirectory?(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[];
94
95        /** Symbol links resolution */
96        realpath?(path: string): string;
97        /** If provided would be used to write log about compilation */
98        trace?(s: string): void;
99        /** If provided is used to get the environment variable */
100        getEnvironmentVariable?(name: string): string | undefined;
101
102        /** If provided, used to resolve the module names, otherwise typescript's default module resolution */
103        resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedModule | undefined)[];
104        /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */
105        resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedTypeReferenceDirective | undefined)[];
106    }
107    /** Internal interface used to wire emit through same host */
108
109    /*@internal*/
110    export interface ProgramHost<T extends BuilderProgram> {
111        // TODO: GH#18217 Optional methods are frequently asserted
112        createDirectory?(path: string): void;
113        writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void;
114    }
115
116    export interface WatchCompilerHost<T extends BuilderProgram> extends ProgramHost<T>, WatchHost {
117        /** Instead of using output d.ts file from project reference, use its source file */
118        useSourceOfProjectReferenceRedirect?(): boolean;
119
120        /** If provided, callback to invoke after every new program creation */
121        afterProgramCreate?(program: T): void;
122    }
123
124    /**
125     * Host to create watch with root files and options
126     */
127    export interface WatchCompilerHostOfFilesAndCompilerOptions<T extends BuilderProgram> extends WatchCompilerHost<T> {
128        /** root files to use to generate program */
129        rootFiles: string[];
130
131        /** Compiler options */
132        options: CompilerOptions;
133
134        watchOptions?: WatchOptions;
135
136        /** Project References */
137        projectReferences?: readonly ProjectReference[];
138    }
139
140    /**
141     * Host to create watch with config file
142     */
143    export interface WatchCompilerHostOfConfigFile<T extends BuilderProgram> extends WatchCompilerHost<T>, ConfigFileDiagnosticsReporter {
144        /** Name of the config file to compile */
145        configFileName: string;
146
147        /** Options to extend */
148        optionsToExtend?: CompilerOptions;
149
150        watchOptionsToExtend?: WatchOptions;
151
152        extraFileExtensions?: readonly FileExtensionInfo[]
153
154        /**
155         * Used to generate source file names from the config file and its include, exclude, files rules
156         * and also to cache the directory stucture
157         */
158        readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[];
159    }
160
161    /**
162     * Host to create watch with config file that is already parsed (from tsc)
163     */
164    /*@internal*/
165    export interface WatchCompilerHostOfConfigFile<T extends BuilderProgram> extends WatchCompilerHost<T> {
166        configFileParsingResult?: ParsedCommandLine;
167    }
168
169    export interface Watch<T> {
170        /** Synchronize with host and get updated program */
171        getProgram(): T;
172        /** Gets the existing program without synchronizing with changes on host */
173        /*@internal*/
174        getCurrentProgram(): T;
175        /** Closes the watch */
176        close(): void;
177    }
178
179    /**
180     * Creates the watch what generates program using the config file
181     */
182    export interface WatchOfConfigFile<T> extends Watch<T> {
183    }
184
185    /**
186     * Creates the watch that generates program using the root files and compiler options
187     */
188    export interface WatchOfFilesAndCompilerOptions<T> extends Watch<T> {
189        /** Updates the root files in the program, only if this is not config file compilation */
190        updateRootFileNames(fileNames: string[]): void;
191    }
192
193    /**
194     * Create the watch compiler host for either configFile or fileNames and its options
195     */
196    export function createWatchCompilerHost<T extends BuilderProgram>(configFileName: string, optionsToExtend: CompilerOptions | undefined, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, watchOptionsToExtend?: WatchOptions, extraFileExtensions?: readonly FileExtensionInfo[]): WatchCompilerHostOfConfigFile<T>;
197    export function createWatchCompilerHost<T extends BuilderProgram>(rootFiles: string[], options: CompilerOptions, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferences?: readonly ProjectReference[], watchOptions?: WatchOptions): WatchCompilerHostOfFilesAndCompilerOptions<T>;
198    export function createWatchCompilerHost<T extends BuilderProgram>(rootFilesOrConfigFileName: string | string[], options: CompilerOptions | undefined, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferencesOrWatchOptionsToExtend?: readonly ProjectReference[] | WatchOptions, watchOptionsOrExtraFileExtensions?: WatchOptions | readonly FileExtensionInfo[]): WatchCompilerHostOfFilesAndCompilerOptions<T> | WatchCompilerHostOfConfigFile<T> {
199        if (isArray(rootFilesOrConfigFileName)) {
200            return createWatchCompilerHostOfFilesAndCompilerOptions({
201                rootFiles: rootFilesOrConfigFileName,
202                options: options!,
203                watchOptions: watchOptionsOrExtraFileExtensions as WatchOptions,
204                projectReferences: projectReferencesOrWatchOptionsToExtend as readonly ProjectReference[],
205                system,
206                createProgram,
207                reportDiagnostic,
208                reportWatchStatus,
209            });
210        }
211        else {
212            return createWatchCompilerHostOfConfigFile({
213                configFileName: rootFilesOrConfigFileName,
214                optionsToExtend: options,
215                watchOptionsToExtend: projectReferencesOrWatchOptionsToExtend as WatchOptions,
216                extraFileExtensions: watchOptionsOrExtraFileExtensions as readonly FileExtensionInfo[],
217                system,
218                createProgram,
219                reportDiagnostic,
220                reportWatchStatus,
221            });
222        }
223    }
224
225    /**
226     * Creates the watch from the host for root files and compiler options
227     */
228    export function createWatchProgram<T extends BuilderProgram>(host: WatchCompilerHostOfFilesAndCompilerOptions<T>): WatchOfFilesAndCompilerOptions<T>;
229    /**
230     * Creates the watch from the host for config file
231     */
232    export function createWatchProgram<T extends BuilderProgram>(host: WatchCompilerHostOfConfigFile<T>): WatchOfConfigFile<T>;
233    export function createWatchProgram<T extends BuilderProgram>(host: WatchCompilerHostOfFilesAndCompilerOptions<T> & WatchCompilerHostOfConfigFile<T>): WatchOfFilesAndCompilerOptions<T> | WatchOfConfigFile<T> {
234        interface FilePresentOnHost {
235            version: string;
236            sourceFile: SourceFile;
237            fileWatcher: FileWatcher;
238        }
239        type FileMissingOnHost = false;
240        interface FilePresenceUnknownOnHost {
241            version: false;
242            fileWatcher?: FileWatcher;
243        }
244        type FileMayBePresentOnHost = FilePresentOnHost | FilePresenceUnknownOnHost;
245        type HostFileInfo = FilePresentOnHost | FileMissingOnHost | FilePresenceUnknownOnHost;
246
247        let builderProgram: T;
248        let reloadLevel: ConfigFileProgramReloadLevel;                      // level to indicate if the program needs to be reloaded from config file/just filenames etc
249        let extendedConfigFilesMap: ESMap<Path, FileWatcher>;               // Map of file watchers for the extended config files
250        let missingFilesMap: ESMap<Path, FileWatcher>;                        // Map of file watchers for the missing files
251        let watchedWildcardDirectories: ESMap<string, WildcardDirectoryWatcher>; // map of watchers for the wild card directories in the config file
252        let timerToUpdateProgram: any;                                      // timer callback to recompile the program
253        let timerToInvalidateFailedLookupResolutions: any;                  // timer callback to invalidate resolutions for changes in failed lookup locations
254
255
256        const sourceFilesCache = new Map<string, HostFileInfo>();                 // Cache that stores the source file and version info
257        let missingFilePathsRequestedForRelease: Path[] | undefined;        // These paths are held temporarily so that we can remove the entry from source file cache if the file is not tracked by missing files
258        let hasChangedCompilerOptions = false;                              // True if the compiler options have changed between compilations
259
260        const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames();
261        const currentDirectory = host.getCurrentDirectory();
262        const { configFileName, optionsToExtend: optionsToExtendForConfigFile = {}, watchOptionsToExtend, extraFileExtensions, createProgram } = host;
263        let { rootFiles: rootFileNames, options: compilerOptions, watchOptions, projectReferences } = host;
264        let wildcardDirectories: MapLike<WatchDirectoryFlags> | undefined;
265        let configFileParsingDiagnostics: Diagnostic[] | undefined;
266        let canConfigFileJsonReportNoInputFiles = false;
267        let hasChangedConfigFileParsingErrors = false;
268
269        const cachedDirectoryStructureHost = configFileName === undefined ? undefined : createCachedDirectoryStructureHost(host, currentDirectory, useCaseSensitiveFileNames);
270        const directoryStructureHost: DirectoryStructureHost = cachedDirectoryStructureHost || host;
271        const parseConfigFileHost = parseConfigHostFromCompilerHostLike(host, directoryStructureHost);
272
273        // From tsc we want to get already parsed result and hence check for rootFileNames
274        let newLine = updateNewLine();
275        if (configFileName && host.configFileParsingResult) {
276            setConfigFileParsingResult(host.configFileParsingResult);
277            newLine = updateNewLine();
278        }
279        reportWatchDiagnostic(Diagnostics.Starting_compilation_in_watch_mode);
280        if (configFileName && !host.configFileParsingResult) {
281            newLine = getNewLineCharacter(optionsToExtendForConfigFile, () => host.getNewLine());
282            Debug.assert(!rootFileNames);
283            parseConfigFile();
284            newLine = updateNewLine();
285        }
286
287        const { watchFile, watchDirectory, writeLog } = createWatchFactory(host, compilerOptions);
288        const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
289
290        writeLog(`Current directory: ${currentDirectory} CaseSensitiveFileNames: ${useCaseSensitiveFileNames}`);
291        let configFileWatcher: FileWatcher | undefined;
292        if (configFileName) {
293            configFileWatcher = watchFile(configFileName, scheduleProgramReload, PollingInterval.High, watchOptions, WatchType.ConfigFile);
294        }
295
296        const compilerHost = createCompilerHostFromProgramHost(host, () => compilerOptions, directoryStructureHost) as CompilerHost & ResolutionCacheHost;
297        setGetSourceFileAsHashVersioned(compilerHost, host);
298        // Members for CompilerHost
299        const getNewSourceFile = compilerHost.getSourceFile;
300        compilerHost.getSourceFile = (fileName, ...args) => getVersionedSourceFileByPath(fileName, toPath(fileName), ...args);
301        compilerHost.getSourceFileByPath = getVersionedSourceFileByPath;
302        compilerHost.getNewLine = () => newLine;
303        compilerHost.fileExists = fileExists;
304        compilerHost.onReleaseOldSourceFile = onReleaseOldSourceFile;
305        // Members for ResolutionCacheHost
306        compilerHost.toPath = toPath;
307        compilerHost.getCompilationSettings = () => compilerOptions;
308        compilerHost.useSourceOfProjectReferenceRedirect = maybeBind(host, host.useSourceOfProjectReferenceRedirect);
309        compilerHost.watchDirectoryOfFailedLookupLocation = (dir, cb, flags) => watchDirectory(dir, cb, flags, watchOptions, WatchType.FailedLookupLocations);
310        compilerHost.watchTypeRootsDirectory = (dir, cb, flags) => watchDirectory(dir, cb, flags, watchOptions, WatchType.TypeRoots);
311        compilerHost.getCachedDirectoryStructureHost = () => cachedDirectoryStructureHost;
312        compilerHost.scheduleInvalidateResolutionsOfFailedLookupLocations = scheduleInvalidateResolutionsOfFailedLookupLocations;
313        compilerHost.onInvalidatedResolution = scheduleProgramUpdate;
314        compilerHost.onChangedAutomaticTypeDirectiveNames = scheduleProgramUpdate;
315        compilerHost.fileIsOpen = returnFalse;
316        compilerHost.getCurrentProgram = getCurrentProgram;
317        compilerHost.writeLog = writeLog;
318
319        // Cache for the module resolution
320        const resolutionCache = createResolutionCache(compilerHost,
321            configFileName ?
322                getDirectoryPath(getNormalizedAbsolutePath(configFileName, currentDirectory)) :
323                currentDirectory,
324            /*logChangesWhenResolvingModule*/ false
325        );
326        // Resolve module using host module resolution strategy if provided otherwise use resolution cache to resolve module names
327        compilerHost.resolveModuleNames = host.resolveModuleNames ?
328            ((...args) => host.resolveModuleNames!(...args)) :
329            ((moduleNames, containingFile, reusedNames, redirectedReference) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, redirectedReference));
330        compilerHost.resolveTypeReferenceDirectives = host.resolveTypeReferenceDirectives ?
331            ((...args) => host.resolveTypeReferenceDirectives!(...args)) :
332            ((typeDirectiveNames, containingFile, redirectedReference) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile, redirectedReference));
333        const userProvidedResolution = !!host.resolveModuleNames || !!host.resolveTypeReferenceDirectives;
334
335        builderProgram = readBuilderProgram(compilerOptions, compilerHost) as any as T;
336        synchronizeProgram();
337
338        // Update the wild card directory watch
339        watchConfigFileWildCardDirectories();
340
341        // Update extended config file watch
342        watchExtendedConfigFiles();
343
344        return configFileName ?
345            { getCurrentProgram: getCurrentBuilderProgram, getProgram: updateProgram, close } :
346            { getCurrentProgram: getCurrentBuilderProgram, getProgram: updateProgram, updateRootFileNames, close };
347
348        function close() {
349            clearInvalidateResolutionsOfFailedLookupLocations();
350            resolutionCache.clear();
351            clearMap(sourceFilesCache, value => {
352                if (value && value.fileWatcher) {
353                    value.fileWatcher.close();
354                    value.fileWatcher = undefined;
355                }
356            });
357            if (configFileWatcher) {
358                configFileWatcher.close();
359                configFileWatcher = undefined;
360            }
361            if (extendedConfigFilesMap) {
362                clearMap(extendedConfigFilesMap, closeFileWatcher);
363                extendedConfigFilesMap = undefined!;
364            }
365            if (watchedWildcardDirectories) {
366                clearMap(watchedWildcardDirectories, closeFileWatcherOf);
367                watchedWildcardDirectories = undefined!;
368            }
369            if (missingFilesMap) {
370                clearMap(missingFilesMap, closeFileWatcher);
371                missingFilesMap = undefined!;
372            }
373        }
374
375        function getCurrentBuilderProgram() {
376            return builderProgram;
377        }
378
379        function getCurrentProgram() {
380            return builderProgram && builderProgram.getProgramOrUndefined();
381        }
382
383        function synchronizeProgram() {
384            writeLog(`Synchronizing program`);
385            clearInvalidateResolutionsOfFailedLookupLocations();
386
387            const program = getCurrentBuilderProgram();
388            if (hasChangedCompilerOptions) {
389                newLine = updateNewLine();
390                if (program && changesAffectModuleResolution(program.getCompilerOptions(), compilerOptions)) {
391                    resolutionCache.clear();
392                }
393            }
394
395            // All resolutions are invalid if user provided resolutions
396            const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(userProvidedResolution);
397            if (isProgramUptoDate(getCurrentProgram(), rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames, projectReferences)) {
398                if (hasChangedConfigFileParsingErrors) {
399                    builderProgram = createProgram(/*rootNames*/ undefined, /*options*/ undefined, compilerHost, builderProgram, configFileParsingDiagnostics, projectReferences);
400                    hasChangedConfigFileParsingErrors = false;
401                }
402            }
403            else {
404                createNewProgram(hasInvalidatedResolution);
405            }
406
407            if (host.afterProgramCreate && program !== builderProgram) {
408                host.afterProgramCreate(builderProgram);
409            }
410
411            return builderProgram;
412        }
413
414        function createNewProgram(hasInvalidatedResolution: HasInvalidatedResolution) {
415            // Compile the program
416            writeLog("CreatingProgramWith::");
417            writeLog(`  roots: ${JSON.stringify(rootFileNames)}`);
418            writeLog(`  options: ${JSON.stringify(compilerOptions)}`);
419
420            const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !getCurrentProgram();
421            hasChangedCompilerOptions = false;
422            hasChangedConfigFileParsingErrors = false;
423            resolutionCache.startCachingPerDirectoryResolution();
424            compilerHost.hasInvalidatedResolution = hasInvalidatedResolution;
425            compilerHost.hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames;
426            builderProgram = createProgram(rootFileNames, compilerOptions, compilerHost, builderProgram, configFileParsingDiagnostics, projectReferences);
427            resolutionCache.finishCachingPerDirectoryResolution();
428
429            // Update watches
430            updateMissingFilePathsWatch(builderProgram.getProgram(), missingFilesMap || (missingFilesMap = new Map()), watchMissingFilePath);
431            if (needsUpdateInTypeRootWatch) {
432                resolutionCache.updateTypeRootsWatch();
433            }
434
435            if (missingFilePathsRequestedForRelease) {
436                // These are the paths that program creater told us as not in use any more but were missing on the disk.
437                // We didnt remove the entry for them from sourceFiles cache so that we dont have to do File IO,
438                // if there is already watcher for it (for missing files)
439                // At this point our watches were updated, hence now we know that these paths are not tracked and need to be removed
440                // so that at later time we have correct result of their presence
441                for (const missingFilePath of missingFilePathsRequestedForRelease) {
442                    if (!missingFilesMap.has(missingFilePath)) {
443                        sourceFilesCache.delete(missingFilePath);
444                    }
445                }
446                missingFilePathsRequestedForRelease = undefined;
447            }
448        }
449
450        function updateRootFileNames(files: string[]) {
451            Debug.assert(!configFileName, "Cannot update root file names with config file watch mode");
452            rootFileNames = files;
453            scheduleProgramUpdate();
454        }
455
456        function updateNewLine() {
457            return getNewLineCharacter(compilerOptions || optionsToExtendForConfigFile, () => host.getNewLine());
458        }
459
460        function toPath(fileName: string) {
461            return ts.toPath(fileName, currentDirectory, getCanonicalFileName);
462        }
463
464        function isFileMissingOnHost(hostSourceFile: HostFileInfo | undefined): hostSourceFile is FileMissingOnHost {
465            return typeof hostSourceFile === "boolean";
466        }
467
468        function isFilePresenceUnknownOnHost(hostSourceFile: FileMayBePresentOnHost): hostSourceFile is FilePresenceUnknownOnHost {
469            return typeof (hostSourceFile as FilePresenceUnknownOnHost).version === "boolean";
470        }
471
472        function fileExists(fileName: string) {
473            const path = toPath(fileName);
474            // If file is missing on host from cache, we can definitely say file doesnt exist
475            // otherwise we need to ensure from the disk
476            if (isFileMissingOnHost(sourceFilesCache.get(path))) {
477                return false;
478            }
479
480            return directoryStructureHost.fileExists(fileName);
481        }
482
483        function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined {
484            const hostSourceFile = sourceFilesCache.get(path);
485            // No source file on the host
486            if (isFileMissingOnHost(hostSourceFile)) {
487                return undefined;
488            }
489
490            // Create new source file if requested or the versions dont match
491            if (hostSourceFile === undefined || shouldCreateNewSourceFile || isFilePresenceUnknownOnHost(hostSourceFile)) {
492                const sourceFile = getNewSourceFile(fileName, languageVersion, onError);
493                if (hostSourceFile) {
494                    if (sourceFile) {
495                        // Set the source file and create file watcher now that file was present on the disk
496                        (hostSourceFile as FilePresentOnHost).sourceFile = sourceFile;
497                        hostSourceFile.version = sourceFile.version;
498                        if (!hostSourceFile.fileWatcher) {
499                            hostSourceFile.fileWatcher = watchFilePath(path, fileName, onSourceFileChange, PollingInterval.Low, watchOptions, WatchType.SourceFile);
500                        }
501                    }
502                    else {
503                        // There is no source file on host any more, close the watch, missing file paths will track it
504                        if (hostSourceFile.fileWatcher) {
505                            hostSourceFile.fileWatcher.close();
506                        }
507                        sourceFilesCache.set(path, false);
508                    }
509                }
510                else {
511                    if (sourceFile) {
512                        const fileWatcher = watchFilePath(path, fileName, onSourceFileChange, PollingInterval.Low, watchOptions, WatchType.SourceFile);
513                        sourceFilesCache.set(path, { sourceFile, version: sourceFile.version, fileWatcher });
514                    }
515                    else {
516                        sourceFilesCache.set(path, false);
517                    }
518                }
519                return sourceFile;
520            }
521            return hostSourceFile.sourceFile;
522        }
523
524        function nextSourceFileVersion(path: Path) {
525            const hostSourceFile = sourceFilesCache.get(path);
526            if (hostSourceFile !== undefined) {
527                if (isFileMissingOnHost(hostSourceFile)) {
528                    // The next version, lets set it as presence unknown file
529                    sourceFilesCache.set(path, { version: false });
530                }
531                else {
532                    (hostSourceFile as FilePresenceUnknownOnHost).version = false;
533                }
534            }
535        }
536
537        function getSourceVersion(path: Path): string | undefined {
538            const hostSourceFile = sourceFilesCache.get(path);
539            return !hostSourceFile || !hostSourceFile.version ? undefined : hostSourceFile.version;
540        }
541
542        function onReleaseOldSourceFile(oldSourceFile: SourceFile, _oldOptions: CompilerOptions, hasSourceFileByPath: boolean) {
543            const hostSourceFileInfo = sourceFilesCache.get(oldSourceFile.resolvedPath);
544            // If this is the source file thats in the cache and new program doesnt need it,
545            // remove the cached entry.
546            // Note we arent deleting entry if file became missing in new program or
547            // there was version update and new source file was created.
548            if (hostSourceFileInfo !== undefined) {
549                // record the missing file paths so they can be removed later if watchers arent tracking them
550                if (isFileMissingOnHost(hostSourceFileInfo)) {
551                    (missingFilePathsRequestedForRelease || (missingFilePathsRequestedForRelease = [])).push(oldSourceFile.path);
552                }
553                else if ((hostSourceFileInfo as FilePresentOnHost).sourceFile === oldSourceFile) {
554                    if (hostSourceFileInfo.fileWatcher) {
555                        hostSourceFileInfo.fileWatcher.close();
556                    }
557                    sourceFilesCache.delete(oldSourceFile.resolvedPath);
558                    if (!hasSourceFileByPath) {
559                        resolutionCache.removeResolutionsOfFile(oldSourceFile.path);
560                    }
561                }
562            }
563        }
564
565        function reportWatchDiagnostic(message: DiagnosticMessage) {
566            if (host.onWatchStatusChange) {
567                host.onWatchStatusChange(createCompilerDiagnostic(message), newLine, compilerOptions || optionsToExtendForConfigFile);
568            }
569        }
570
571        function hasChangedAutomaticTypeDirectiveNames() {
572            return resolutionCache.hasChangedAutomaticTypeDirectiveNames();
573        }
574
575        function clearInvalidateResolutionsOfFailedLookupLocations() {
576            if (!timerToInvalidateFailedLookupResolutions) return false;
577            host.clearTimeout!(timerToInvalidateFailedLookupResolutions);
578            timerToInvalidateFailedLookupResolutions = undefined;
579            return true;
580        }
581
582        function scheduleInvalidateResolutionsOfFailedLookupLocations() {
583            if (!host.setTimeout || !host.clearTimeout) {
584                return resolutionCache.invalidateResolutionsOfFailedLookupLocations();
585            }
586            const pending = clearInvalidateResolutionsOfFailedLookupLocations();
587            writeLog(`Scheduling invalidateFailedLookup${pending ? ", Cancelled earlier one" : ""}`);
588            timerToInvalidateFailedLookupResolutions = host.setTimeout(invalidateResolutionsOfFailedLookup, 250);
589        }
590
591        function invalidateResolutionsOfFailedLookup() {
592            timerToInvalidateFailedLookupResolutions = undefined;
593            if (resolutionCache.invalidateResolutionsOfFailedLookupLocations()) {
594                scheduleProgramUpdate();
595            }
596        }
597
598        // Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch
599        // operations (such as saving all modified files in an editor) a chance to complete before we kick
600        // off a new compilation.
601        function scheduleProgramUpdate() {
602            if (!host.setTimeout || !host.clearTimeout) {
603                return;
604            }
605
606            if (timerToUpdateProgram) {
607                host.clearTimeout(timerToUpdateProgram);
608            }
609            writeLog("Scheduling update");
610            timerToUpdateProgram = host.setTimeout(updateProgramWithWatchStatus, 250);
611        }
612
613        function scheduleProgramReload() {
614            Debug.assert(!!configFileName);
615            reloadLevel = ConfigFileProgramReloadLevel.Full;
616            scheduleProgramUpdate();
617        }
618
619        function updateProgramWithWatchStatus() {
620            timerToUpdateProgram = undefined;
621            reportWatchDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation);
622            updateProgram();
623        }
624
625        function updateProgram() {
626            switch (reloadLevel) {
627                case ConfigFileProgramReloadLevel.Partial:
628                    perfLogger.logStartUpdateProgram("PartialConfigReload");
629                    reloadFileNamesFromConfigFile();
630                    break;
631                case ConfigFileProgramReloadLevel.Full:
632                    perfLogger.logStartUpdateProgram("FullConfigReload");
633                    reloadConfigFile();
634                    break;
635                default:
636                    perfLogger.logStartUpdateProgram("SynchronizeProgram");
637                    synchronizeProgram();
638                    break;
639            }
640            perfLogger.logStopUpdateProgram("Done");
641            return getCurrentBuilderProgram();
642        }
643
644        function reloadFileNamesFromConfigFile() {
645            writeLog("Reloading new file names and options");
646            rootFileNames = getFileNamesFromConfigSpecs(compilerOptions.configFile!.configFileSpecs!, getNormalizedAbsolutePath(getDirectoryPath(configFileName), currentDirectory), compilerOptions, parseConfigFileHost, extraFileExtensions);
647            if (updateErrorForNoInputFiles(rootFileNames, getNormalizedAbsolutePath(configFileName, currentDirectory), compilerOptions.configFile!.configFileSpecs!, configFileParsingDiagnostics!, canConfigFileJsonReportNoInputFiles)) {
648                hasChangedConfigFileParsingErrors = true;
649            }
650
651            // Update the program
652            synchronizeProgram();
653        }
654
655        function reloadConfigFile() {
656            writeLog(`Reloading config file: ${configFileName}`);
657            reloadLevel = ConfigFileProgramReloadLevel.None;
658
659            if (cachedDirectoryStructureHost) {
660                cachedDirectoryStructureHost.clearCache();
661            }
662            parseConfigFile();
663            hasChangedCompilerOptions = true;
664            synchronizeProgram();
665
666            // Update the wild card directory watch
667            watchConfigFileWildCardDirectories();
668
669            // Update extended config file watch
670            watchExtendedConfigFiles();
671        }
672
673        function parseConfigFile() {
674            setConfigFileParsingResult(getParsedCommandLineOfConfigFile(configFileName, optionsToExtendForConfigFile, parseConfigFileHost, /*extendedConfigCache*/ undefined, watchOptionsToExtend, extraFileExtensions)!); // TODO: GH#18217
675        }
676
677        function setConfigFileParsingResult(configFileParseResult: ParsedCommandLine) {
678            rootFileNames = configFileParseResult.fileNames;
679            compilerOptions = configFileParseResult.options;
680            watchOptions = configFileParseResult.watchOptions;
681            projectReferences = configFileParseResult.projectReferences;
682            wildcardDirectories = configFileParseResult.wildcardDirectories;
683            configFileParsingDiagnostics = getConfigFileParsingDiagnostics(configFileParseResult).slice();
684            canConfigFileJsonReportNoInputFiles = canJsonReportNoInputFiles(configFileParseResult.raw);
685            hasChangedConfigFileParsingErrors = true;
686        }
687
688        function watchFilePath(
689            path: Path,
690            file: string,
691            callback: (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void,
692            pollingInterval: PollingInterval,
693            options: WatchOptions | undefined,
694            watchType: WatchType
695        ): FileWatcher {
696            return watchFile(file, (fileName, eventKind) => callback(fileName, eventKind, path), pollingInterval, options, watchType);
697        }
698
699        function onSourceFileChange(fileName: string, eventKind: FileWatcherEventKind, path: Path) {
700            updateCachedSystemWithFile(fileName, path, eventKind);
701
702            // Update the source file cache
703            if (eventKind === FileWatcherEventKind.Deleted && sourceFilesCache.has(path)) {
704                resolutionCache.invalidateResolutionOfFile(path);
705            }
706            resolutionCache.removeResolutionsFromProjectReferenceRedirects(path);
707            nextSourceFileVersion(path);
708
709            // Update the program
710            scheduleProgramUpdate();
711        }
712
713        function updateCachedSystemWithFile(fileName: string, path: Path, eventKind: FileWatcherEventKind) {
714            if (cachedDirectoryStructureHost) {
715                cachedDirectoryStructureHost.addOrDeleteFile(fileName, path, eventKind);
716            }
717        }
718
719        function watchMissingFilePath(missingFilePath: Path) {
720            return watchFilePath(missingFilePath, missingFilePath, onMissingFileChange, PollingInterval.Medium, watchOptions, WatchType.MissingFile);
721        }
722
723        function onMissingFileChange(fileName: string, eventKind: FileWatcherEventKind, missingFilePath: Path) {
724            updateCachedSystemWithFile(fileName, missingFilePath, eventKind);
725
726            if (eventKind === FileWatcherEventKind.Created && missingFilesMap.has(missingFilePath)) {
727                missingFilesMap.get(missingFilePath)!.close();
728                missingFilesMap.delete(missingFilePath);
729
730                // Delete the entry in the source files cache so that new source file is created
731                nextSourceFileVersion(missingFilePath);
732
733                // When a missing file is created, we should update the graph.
734                scheduleProgramUpdate();
735            }
736        }
737
738        function watchConfigFileWildCardDirectories() {
739            if (wildcardDirectories) {
740                updateWatchingWildcardDirectories(
741                    watchedWildcardDirectories || (watchedWildcardDirectories = new Map()),
742                    new Map(getEntries(wildcardDirectories)),
743                    watchWildcardDirectory
744                );
745            }
746            else if (watchedWildcardDirectories) {
747                clearMap(watchedWildcardDirectories, closeFileWatcherOf);
748            }
749        }
750
751        function watchWildcardDirectory(directory: string, flags: WatchDirectoryFlags) {
752            return watchDirectory(
753                directory,
754                fileOrDirectory => {
755                    Debug.assert(!!configFileName);
756
757                    const fileOrDirectoryPath = toPath(fileOrDirectory);
758
759                    // Since the file existence changed, update the sourceFiles cache
760                    if (cachedDirectoryStructureHost) {
761                        cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
762                    }
763                    nextSourceFileVersion(fileOrDirectoryPath);
764
765                    if (isIgnoredFileFromWildCardWatching({
766                        watchedDirPath: toPath(directory),
767                        fileOrDirectory,
768                        fileOrDirectoryPath,
769                        configFileName,
770                        extraFileExtensions,
771                        options: compilerOptions,
772                        program: getCurrentBuilderProgram(),
773                        currentDirectory,
774                        useCaseSensitiveFileNames,
775                        writeLog
776                    })) return;
777
778                    // Reload is pending, do the reload
779                    if (reloadLevel !== ConfigFileProgramReloadLevel.Full) {
780                        reloadLevel = ConfigFileProgramReloadLevel.Partial;
781
782                        // Schedule Update the program
783                        scheduleProgramUpdate();
784                    }
785                },
786                flags,
787                watchOptions,
788                WatchType.WildcardDirectory
789            );
790        }
791
792        function watchExtendedConfigFiles() {
793            // Update the extended config files watcher
794            mutateMap(
795                extendedConfigFilesMap ||= new Map(),
796                arrayToMap(compilerOptions.configFile?.extendedSourceFiles || emptyArray, toPath),
797                {
798                    // Watch the extended config files
799                    createNewValue: watchExtendedConfigFile,
800                    // Config files that are no longer extended should no longer be watched.
801                    onDeleteValue: closeFileWatcher
802                }
803            );
804        }
805
806        function watchExtendedConfigFile(extendedConfigFile: Path) {
807            return watchFile(extendedConfigFile, scheduleProgramReload, PollingInterval.High, watchOptions, WatchType.ExtendedConfigFile);
808        }
809    }
810}
811