• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*@internal*/
2namespace ts {
3    /** This is the cache of module/typedirectives resolution that can be retained across program */
4    export interface ResolutionCache {
5        startRecordingFilesWithChangedResolutions(): void;
6        finishRecordingFilesWithChangedResolutions(): Path[] | undefined;
7
8        resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference): (ResolvedModuleFull | undefined)[];
9        getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): CachedResolvedModuleWithFailedLookupLocations | undefined;
10        resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[];
11
12        invalidateResolutionsOfFailedLookupLocations(): boolean;
13        invalidateResolutionOfFile(filePath: Path): void;
14        removeResolutionsOfFile(filePath: Path): void;
15        removeResolutionsFromProjectReferenceRedirects(filePath: Path): void;
16        setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: ESMap<Path, readonly string[]>): void;
17        createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution;
18        hasChangedAutomaticTypeDirectiveNames(): boolean;
19        isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean;
20
21
22        startCachingPerDirectoryResolution(): void;
23        finishCachingPerDirectoryResolution(): void;
24
25        updateTypeRootsWatch(): void;
26        closeTypeRootsWatch(): void;
27
28        clear(): void;
29    }
30
31    interface ResolutionWithFailedLookupLocations {
32        readonly failedLookupLocations: string[];
33        isInvalidated?: boolean;
34        refCount?: number;
35        // Files that have this resolution using
36        files?: Path[];
37    }
38
39    interface ResolutionWithResolvedFileName {
40        resolvedFileName: string | undefined;
41    }
42
43    interface CachedResolvedModuleWithFailedLookupLocations extends ResolvedModuleWithFailedLookupLocations, ResolutionWithFailedLookupLocations {
44    }
45
46    interface CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations extends ResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolutionWithFailedLookupLocations {
47    }
48
49    export interface ResolutionCacheHost extends ModuleResolutionHost {
50        toPath(fileName: string): Path;
51        getCanonicalFileName: GetCanonicalFileName;
52        getCompilationSettings(): CompilerOptions;
53        watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher;
54        onInvalidatedResolution(): void;
55        watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher;
56        onChangedAutomaticTypeDirectiveNames(): void;
57        scheduleInvalidateResolutionsOfFailedLookupLocations(): void;
58        getCachedDirectoryStructureHost(): CachedDirectoryStructureHost | undefined;
59        projectName?: string;
60        getGlobalCache?(): string | undefined;
61        globalCacheResolutionModuleName?(externalModuleName: string): string;
62        writeLog(s: string): void;
63        getCurrentProgram(): Program | undefined;
64        fileIsOpen(filePath: Path): boolean;
65        getCompilerHost?(): CompilerHost | undefined;
66    }
67
68    interface DirectoryWatchesOfFailedLookup {
69        /** watcher for the directory of failed lookup */
70        watcher: FileWatcher;
71        /** ref count keeping this directory watch alive */
72        refCount: number;
73        /** is the directory watched being non recursive */
74        nonRecursive?: boolean;
75    }
76
77    interface DirectoryOfFailedLookupWatch {
78        dir: string;
79        dirPath: Path;
80        nonRecursive?: boolean;
81    }
82
83    export function removeIgnoredPath(path: Path): Path | undefined {
84        // Consider whole staging folder as if node_modules or oh_modules changed.
85        if (endsWith(path, "/node_modules/.staging") || endsWith(path, "/oh_modules/.staging")) {
86            return removeSuffix(path, "/.staging") as Path;
87        }
88
89        return some(ignoredPaths, searchPath => stringContains(path, searchPath)) ?
90            undefined :
91            path;
92    }
93
94    /**
95     * Filter out paths like
96     * "/", "/user", "/user/username", "/user/username/folderAtRoot",
97     * "c:/", "c:/users", "c:/users/username", "c:/users/username/folderAtRoot", "c:/folderAtRoot"
98     * @param dirPath
99     */
100    export function canWatchDirectory(dirPath: Path) {
101        const rootLength = getRootLength(dirPath);
102        if (dirPath.length === rootLength) {
103            // Ignore "/", "c:/"
104            return false;
105        }
106
107        let nextDirectorySeparator = dirPath.indexOf(directorySeparator, rootLength);
108        if (nextDirectorySeparator === -1) {
109            // ignore "/user", "c:/users" or "c:/folderAtRoot"
110            return false;
111        }
112
113        let pathPartForUserCheck = dirPath.substring(rootLength, nextDirectorySeparator + 1);
114        const isNonDirectorySeparatorRoot = rootLength > 1 || dirPath.charCodeAt(0) !== CharacterCodes.slash;
115        if (isNonDirectorySeparatorRoot &&
116            dirPath.search(/[a-zA-Z]:/) !== 0 && // Non dos style paths
117            pathPartForUserCheck.search(/[a-zA-z]\$\//) === 0) { // Dos style nextPart
118            nextDirectorySeparator = dirPath.indexOf(directorySeparator, nextDirectorySeparator + 1);
119            if (nextDirectorySeparator === -1) {
120                // ignore "//vda1cs4850/c$/folderAtRoot"
121                return false;
122            }
123
124            pathPartForUserCheck = dirPath.substring(rootLength + pathPartForUserCheck.length, nextDirectorySeparator + 1);
125        }
126
127        if (isNonDirectorySeparatorRoot &&
128            pathPartForUserCheck.search(/users\//i) !== 0) {
129            // Paths like c:/folderAtRoot/subFolder are allowed
130            return true;
131        }
132
133        for (let searchIndex = nextDirectorySeparator + 1, searchLevels = 2; searchLevels > 0; searchLevels--) {
134            searchIndex = dirPath.indexOf(directorySeparator, searchIndex) + 1;
135            if (searchIndex === 0) {
136                // Folder isnt at expected minimum levels
137                return false;
138            }
139        }
140        return true;
141    }
142
143    type GetResolutionWithResolvedFileName<T extends ResolutionWithFailedLookupLocations = ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName = ResolutionWithResolvedFileName> =
144        (resolution: T) => R | undefined;
145
146    export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string | undefined, logChangesWhenResolvingModule: boolean): ResolutionCache {
147        let filesWithChangedSetOfUnresolvedImports: Path[] | undefined;
148        let filesWithInvalidatedResolutions: Set<Path> | undefined;
149        let filesWithInvalidatedNonRelativeUnresolvedImports: ReadonlyESMap<Path, readonly string[]> | undefined;
150        const nonRelativeExternalModuleResolutions = createMultiMap<ResolutionWithFailedLookupLocations>();
151
152        const resolutionsWithFailedLookups: ResolutionWithFailedLookupLocations[] = [];
153        const resolvedFileToResolution = createMultiMap<ResolutionWithFailedLookupLocations>();
154
155        let hasChangedAutomaticTypeDirectiveNames = false;
156        const failedLookupChecks: Path[] = [];
157        const startsWithPathChecks: Path[] = [];
158        const isInDirectoryChecks: Path[] = [];
159
160        const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory!()); // TODO: GH#18217
161        const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost();
162
163        // The resolvedModuleNames and resolvedTypeReferenceDirectives are the cache of resolutions per file.
164        // The key in the map is source file's path.
165        // The values are Map of resolutions with key being name lookedup.
166        const resolvedModuleNames = new Map<Path, ESMap<string, CachedResolvedModuleWithFailedLookupLocations>>();
167        const perDirectoryResolvedModuleNames: CacheWithRedirects<ESMap<string, CachedResolvedModuleWithFailedLookupLocations>> = createCacheWithRedirects();
168        const nonRelativeModuleNameCache: CacheWithRedirects<PerModuleNameCache> = createCacheWithRedirects();
169        const moduleResolutionCache = createModuleResolutionCacheWithMaps(
170            perDirectoryResolvedModuleNames,
171            nonRelativeModuleNameCache,
172            getCurrentDirectory(),
173            resolutionHost.getCanonicalFileName
174        );
175
176        const resolvedTypeReferenceDirectives = new Map<Path, ESMap<string, CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
177        const perDirectoryResolvedTypeReferenceDirectives: CacheWithRedirects<ESMap<string, CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations>> = createCacheWithRedirects();
178
179        /**
180         * These are the extensions that failed lookup files will have by default,
181         * any other extension of failed lookup will be store that path in custom failed lookup path
182         * This helps in not having to comb through all resolutions when files are added/removed
183         * Note that .d.ts file also has .d.ts extension hence will be part of default extensions
184         */
185        const failedLookupDefaultExtensions = resolutionHost.getCompilationSettings().ets
186            ? [Extension.Ets, Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json]
187            : [Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json, Extension.Ets];
188        const customFailedLookupPaths = new Map<string, number>();
189
190        const directoryWatchesOfFailedLookups = new Map<string, DirectoryWatchesOfFailedLookup>();
191        const rootDir = rootDirForResolution && removeTrailingDirectorySeparator(getNormalizedAbsolutePath(rootDirForResolution, getCurrentDirectory()));
192        const rootPath = (rootDir && resolutionHost.toPath(rootDir)) as Path; // TODO: GH#18217
193        const rootSplitLength = rootPath !== undefined ? rootPath.split(directorySeparator).length : 0;
194
195        // TypeRoot watches for the types that get added as part of getAutomaticTypeDirectiveNames
196        const typeRootsWatches = new Map<string, FileWatcher>();
197
198        return {
199            startRecordingFilesWithChangedResolutions,
200            finishRecordingFilesWithChangedResolutions,
201            // perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update
202            // (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution)
203            startCachingPerDirectoryResolution: clearPerDirectoryResolutions,
204            finishCachingPerDirectoryResolution,
205            resolveModuleNames,
206            getResolvedModuleWithFailedLookupLocationsFromCache,
207            resolveTypeReferenceDirectives,
208            removeResolutionsFromProjectReferenceRedirects,
209            removeResolutionsOfFile,
210            hasChangedAutomaticTypeDirectiveNames: () => hasChangedAutomaticTypeDirectiveNames,
211            invalidateResolutionOfFile,
212            invalidateResolutionsOfFailedLookupLocations,
213            setFilesWithInvalidatedNonRelativeUnresolvedImports,
214            createHasInvalidatedResolution,
215            isFileWithInvalidatedNonRelativeUnresolvedImports,
216            updateTypeRootsWatch,
217            closeTypeRootsWatch,
218            clear
219        };
220
221        function getResolvedModule(resolution: CachedResolvedModuleWithFailedLookupLocations) {
222            return resolution.resolvedModule;
223        }
224
225        function getResolvedTypeReferenceDirective(resolution: CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations) {
226            return resolution.resolvedTypeReferenceDirective;
227        }
228
229        function isInDirectoryPath(dir: Path | undefined, file: Path) {
230            if (dir === undefined || file.length <= dir.length) {
231                return false;
232            }
233            return startsWith(file, dir) && file[dir.length] === directorySeparator;
234        }
235
236        function clear() {
237            clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf);
238            customFailedLookupPaths.clear();
239            nonRelativeExternalModuleResolutions.clear();
240            closeTypeRootsWatch();
241            resolvedModuleNames.clear();
242            resolvedTypeReferenceDirectives.clear();
243            resolvedFileToResolution.clear();
244            resolutionsWithFailedLookups.length = 0;
245            failedLookupChecks.length = 0;
246            startsWithPathChecks.length = 0;
247            isInDirectoryChecks.length = 0;
248            // perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update
249            // (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution)
250            clearPerDirectoryResolutions();
251            hasChangedAutomaticTypeDirectiveNames = false;
252        }
253
254        function startRecordingFilesWithChangedResolutions() {
255            filesWithChangedSetOfUnresolvedImports = [];
256        }
257
258        function finishRecordingFilesWithChangedResolutions() {
259            const collected = filesWithChangedSetOfUnresolvedImports;
260            filesWithChangedSetOfUnresolvedImports = undefined;
261            return collected;
262        }
263
264        function isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean {
265            if (!filesWithInvalidatedNonRelativeUnresolvedImports) {
266                return false;
267            }
268
269            // Invalidated if file has unresolved imports
270            const value = filesWithInvalidatedNonRelativeUnresolvedImports.get(path);
271            return !!value && !!value.length;
272        }
273
274        function createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution {
275            // Ensure pending resolutions are applied
276            invalidateResolutionsOfFailedLookupLocations();
277            if (forceAllFilesAsInvalidated) {
278                // Any file asked would have invalidated resolution
279                filesWithInvalidatedResolutions = undefined;
280                return returnTrue;
281            }
282            const collected = filesWithInvalidatedResolutions;
283            filesWithInvalidatedResolutions = undefined;
284            return path => (!!collected && collected.has(path)) ||
285                isFileWithInvalidatedNonRelativeUnresolvedImports(path);
286        }
287
288        function clearPerDirectoryResolutions() {
289            perDirectoryResolvedModuleNames.clear();
290            nonRelativeModuleNameCache.clear();
291            perDirectoryResolvedTypeReferenceDirectives.clear();
292            nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions);
293            nonRelativeExternalModuleResolutions.clear();
294        }
295
296        function finishCachingPerDirectoryResolution() {
297            filesWithInvalidatedNonRelativeUnresolvedImports = undefined;
298            clearPerDirectoryResolutions();
299            directoryWatchesOfFailedLookups.forEach((watcher, path) => {
300                if (watcher.refCount === 0) {
301                    directoryWatchesOfFailedLookups.delete(path);
302                    watcher.watcher.close();
303                }
304            });
305            hasChangedAutomaticTypeDirectiveNames = false;
306        }
307
308        function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference): CachedResolvedModuleWithFailedLookupLocations {
309            const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host, moduleResolutionCache, redirectedReference);
310            // return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts
311            if (!resolutionHost.getGlobalCache) {
312                return primaryResult;
313            }
314
315            // otherwise try to load typings from @types
316            const globalCache = resolutionHost.getGlobalCache();
317            if (globalCache !== undefined && !isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTS(primaryResult.resolvedModule.extension))) {
318                // create different collection of failed lookup locations for second pass
319                // if it will fail and we've already found something during the first pass - we don't want to pollute its results
320                const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache(
321                    Debug.checkDefined(resolutionHost.globalCacheResolutionModuleName)(moduleName),
322                    resolutionHost.projectName,
323                    compilerOptions,
324                    host,
325                    globalCache);
326                if (resolvedModule) {
327                    // Modify existing resolution so its saved in the directory cache as well
328                    (primaryResult.resolvedModule as any) = resolvedModule;
329                    primaryResult.failedLookupLocations.push(...failedLookupLocations);
330                    return primaryResult;
331                }
332            }
333
334            // Default return the result from the first pass
335            return primaryResult;
336        }
337
338        interface ResolveNamesWithLocalCacheInput<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName> {
339            names: readonly string[];
340            containingFile: string;
341            redirectedReference: ResolvedProjectReference | undefined;
342            cache: ESMap<Path, ESMap<string, T>>;
343            perDirectoryCacheWithRedirects: CacheWithRedirects<ESMap<string, T>>;
344            loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference) => T;
345            getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>;
346            shouldRetryResolution: (t: T) => boolean;
347            reusedNames?: readonly string[];
348            logChanges?: boolean;
349        }
350        function resolveNamesWithLocalCache<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>({
351            names, containingFile, redirectedReference,
352            cache, perDirectoryCacheWithRedirects,
353            loader, getResolutionWithResolvedFileName,
354            shouldRetryResolution, reusedNames, logChanges
355        }: ResolveNamesWithLocalCacheInput<T, R>): (R | undefined)[] {
356            const path = resolutionHost.toPath(containingFile);
357            const resolutionsInFile = cache.get(path) || cache.set(path, new Map()).get(path)!;
358            const dirPath = getDirectoryPath(path);
359            const perDirectoryCache = perDirectoryCacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference);
360            let perDirectoryResolution = perDirectoryCache.get(dirPath);
361            if (!perDirectoryResolution) {
362                perDirectoryResolution = new Map();
363                perDirectoryCache.set(dirPath, perDirectoryResolution);
364            }
365            const resolvedModules: (R | undefined)[] = [];
366            const compilerOptions = resolutionHost.getCompilationSettings();
367            const hasInvalidatedNonRelativeUnresolvedImport = logChanges && isFileWithInvalidatedNonRelativeUnresolvedImports(path);
368
369            // All the resolutions in this file are invalidated if this file wasn't resolved using same redirect
370            const program = resolutionHost.getCurrentProgram();
371            const oldRedirect = program && program.getResolvedProjectReferenceToRedirect(containingFile);
372            const unmatchedRedirects = oldRedirect ?
373                !redirectedReference || redirectedReference.sourceFile.path !== oldRedirect.sourceFile.path :
374                !!redirectedReference;
375
376            const seenNamesInFile = new Map<string, true>();
377            for (const name of names) {
378                let resolution = resolutionsInFile.get(name);
379                // Resolution is valid if it is present and not invalidated
380                if (!seenNamesInFile.has(name) &&
381                    unmatchedRedirects || !resolution || resolution.isInvalidated ||
382                    // If the name is unresolved import that was invalidated, recalculate
383                    (hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && shouldRetryResolution(resolution))) {
384                    const existingResolution = resolution;
385                    const resolutionInDirectory = perDirectoryResolution.get(name);
386                    if (resolutionInDirectory) {
387                        resolution = resolutionInDirectory;
388                    }
389                    else {
390                        resolution = loader(name, containingFile, compilerOptions, resolutionHost.getCompilerHost?.() || resolutionHost, redirectedReference);
391                        perDirectoryResolution.set(name, resolution);
392                    }
393                    resolutionsInFile.set(name, resolution);
394                    watchFailedLookupLocationsOfExternalModuleResolutions(name, resolution, path, getResolutionWithResolvedFileName);
395                    if (existingResolution) {
396                        stopWatchFailedLookupLocationOfResolution(existingResolution, path, getResolutionWithResolvedFileName);
397                    }
398
399                    if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) {
400                        filesWithChangedSetOfUnresolvedImports.push(path);
401                        // reset log changes to avoid recording the same file multiple times
402                        logChanges = false;
403                    }
404                }
405                Debug.assert(resolution !== undefined && !resolution.isInvalidated);
406                seenNamesInFile.set(name, true);
407                resolvedModules.push(getResolutionWithResolvedFileName(resolution));
408            }
409
410            // Stop watching and remove the unused name
411            resolutionsInFile.forEach((resolution, name) => {
412                if (!seenNamesInFile.has(name) && !contains(reusedNames, name)) {
413                    stopWatchFailedLookupLocationOfResolution(resolution, path, getResolutionWithResolvedFileName);
414                    resolutionsInFile.delete(name);
415                }
416            });
417
418            return resolvedModules;
419
420            function resolutionIsEqualTo(oldResolution: T | undefined, newResolution: T | undefined): boolean {
421                if (oldResolution === newResolution) {
422                    return true;
423                }
424                if (!oldResolution || !newResolution) {
425                    return false;
426                }
427                const oldResult = getResolutionWithResolvedFileName(oldResolution);
428                const newResult = getResolutionWithResolvedFileName(newResolution);
429                if (oldResult === newResult) {
430                    return true;
431                }
432                if (!oldResult || !newResult) {
433                    return false;
434                }
435                return oldResult.resolvedFileName === newResult.resolvedFileName;
436            }
437        }
438
439        function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[] {
440            return resolveNamesWithLocalCache<CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolvedTypeReferenceDirective>({
441                names: typeDirectiveNames,
442                containingFile,
443                redirectedReference,
444                cache: resolvedTypeReferenceDirectives,
445                perDirectoryCacheWithRedirects: perDirectoryResolvedTypeReferenceDirectives,
446                loader: resolveTypeReferenceDirective,
447                getResolutionWithResolvedFileName: getResolvedTypeReferenceDirective,
448                shouldRetryResolution: resolution => resolution.resolvedTypeReferenceDirective === undefined,
449            });
450        }
451
452        function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference): (ResolvedModuleFull | undefined)[] {
453            return resolveNamesWithLocalCache<CachedResolvedModuleWithFailedLookupLocations, ResolvedModuleFull>({
454                names: moduleNames,
455                containingFile,
456                redirectedReference,
457                cache: resolvedModuleNames,
458                perDirectoryCacheWithRedirects: perDirectoryResolvedModuleNames,
459                loader: resolveModuleName,
460                getResolutionWithResolvedFileName: getResolvedModule,
461                shouldRetryResolution: resolution => !resolution.resolvedModule || !resolutionExtensionIsTSOrJson(resolution.resolvedModule.extension),
462                reusedNames,
463                logChanges: logChangesWhenResolvingModule,
464            });
465        }
466
467        function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): CachedResolvedModuleWithFailedLookupLocations | undefined {
468            const cache = resolvedModuleNames.get(resolutionHost.toPath(containingFile));
469            return cache && cache.get(moduleName);
470        }
471
472        function isNodeModulesAtTypesDirectory(dirPath: Path) {
473            return endsWith(dirPath, "/node_modules/@types");
474        }
475
476        function isOHModulesAtTypesDirectory(dirPath: Path) {
477            return endsWith(dirPath, "/oh_modules/@types");
478        }
479
480        function getDirectoryToWatchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path): DirectoryOfFailedLookupWatch | undefined {
481            if (isInDirectoryPath(rootPath, failedLookupLocationPath)) {
482                // Ensure failed look up is normalized path
483                failedLookupLocation = isRootedDiskPath(failedLookupLocation) ? normalizePath(failedLookupLocation) : getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory());
484                const failedLookupPathSplit = failedLookupLocationPath.split(directorySeparator);
485                const failedLookupSplit = failedLookupLocation.split(directorySeparator);
486                Debug.assert(failedLookupSplit.length === failedLookupPathSplit.length, `FailedLookup: ${failedLookupLocation} failedLookupLocationPath: ${failedLookupLocationPath}`);
487                if (failedLookupPathSplit.length > rootSplitLength + 1) {
488                    // Instead of watching root, watch directory in root to avoid watching excluded directories not needed for module resolution
489                    return {
490                        dir: failedLookupSplit.slice(0, rootSplitLength + 1).join(directorySeparator),
491                        dirPath: failedLookupPathSplit.slice(0, rootSplitLength + 1).join(directorySeparator) as Path
492                    };
493                }
494                else {
495                    // Always watch root directory non recursively
496                    return {
497                        dir: rootDir!,
498                        dirPath: rootPath,
499                        nonRecursive: false
500                    };
501                }
502            }
503
504            return getDirectoryToWatchFromFailedLookupLocationDirectory(
505                getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory())),
506                getDirectoryPath(failedLookupLocationPath)
507            );
508        }
509
510        function getDirectoryToWatchFromFailedLookupLocationDirectory(dir: string, dirPath: Path): DirectoryOfFailedLookupWatch | undefined {
511            // If directory path contains node module, get the most parent node_modules or oh_modules directory for watching
512            const isOHModules: boolean = isOhpm(resolutionHost.getCompilationSettings().packageManagerType);
513            while (isOHModules ? pathContainsOHModules(dirPath) : pathContainsNodeModules(dirPath)) {
514                dir = getDirectoryPath(dir);
515                dirPath = getDirectoryPath(dirPath);
516            }
517
518            // If the directory is node_modules or oh_modules use it to watch, always watch it recursively
519            if (isOHModules ? isOHModulesDirectory(dirPath) : isNodeModulesDirectory(dirPath)) {
520                return canWatchDirectory(getDirectoryPath(dirPath)) ? { dir, dirPath } : undefined;
521            }
522
523            let nonRecursive = true;
524            // Use some ancestor of the root directory
525            let subDirectoryPath: Path | undefined, subDirectory: string | undefined;
526            if (rootPath !== undefined) {
527                while (!isInDirectoryPath(dirPath, rootPath)) {
528                    const parentPath = getDirectoryPath(dirPath);
529                    if (parentPath === dirPath) {
530                        break;
531                    }
532                    nonRecursive = false;
533                    subDirectoryPath = dirPath;
534                    subDirectory = dir;
535                    dirPath = parentPath;
536                    dir = getDirectoryPath(dir);
537                }
538            }
539
540            return canWatchDirectory(dirPath) ? { dir: subDirectory || dir, dirPath: subDirectoryPath || dirPath, nonRecursive } : undefined;
541        }
542
543        function isPathWithDefaultFailedLookupExtension(path: Path) {
544            return fileExtensionIsOneOf(path, failedLookupDefaultExtensions);
545        }
546
547        function watchFailedLookupLocationsOfExternalModuleResolutions<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>(
548            name: string,
549            resolution: T,
550            filePath: Path,
551            getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>,
552        ) {
553            if (resolution.refCount) {
554                resolution.refCount++;
555                Debug.assertDefined(resolution.files);
556            }
557            else {
558                resolution.refCount = 1;
559                Debug.assert(length(resolution.files) === 0); // This resolution shouldnt be referenced by any file yet
560                if (isExternalModuleNameRelative(name)) {
561                    watchFailedLookupLocationOfResolution(resolution);
562                }
563                else {
564                    nonRelativeExternalModuleResolutions.add(name, resolution);
565                }
566                const resolved = getResolutionWithResolvedFileName(resolution);
567                if (resolved && resolved.resolvedFileName) {
568                    resolvedFileToResolution.add(resolutionHost.toPath(resolved.resolvedFileName), resolution);
569                }
570            }
571            (resolution.files || (resolution.files = [])).push(filePath);
572        }
573
574        function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) {
575            Debug.assert(!!resolution.refCount);
576
577            const { failedLookupLocations } = resolution;
578            if (!failedLookupLocations.length) return;
579            resolutionsWithFailedLookups.push(resolution);
580
581            let setAtRoot = false;
582            for (const failedLookupLocation of failedLookupLocations) {
583                const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
584                const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
585                if (toWatch) {
586                    const { dir, dirPath, nonRecursive } = toWatch;
587                    // If the failed lookup location path is not one of the supported extensions,
588                    // store it in the custom path
589                    if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) {
590                        const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0;
591                        customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1);
592                    }
593                    if (dirPath === rootPath) {
594                        Debug.assert(!nonRecursive);
595                        setAtRoot = true;
596                    }
597                    else {
598                        setDirectoryWatcher(dir, dirPath, nonRecursive);
599                    }
600                }
601            }
602
603            if (setAtRoot) {
604                // This is always non recursive
605                setDirectoryWatcher(rootDir!, rootPath, /*nonRecursive*/ true); // TODO: GH#18217
606            }
607        }
608
609        function watchFailedLookupLocationOfNonRelativeModuleResolutions(resolutions: ResolutionWithFailedLookupLocations[], name: string) {
610            const program = resolutionHost.getCurrentProgram();
611            if (!program || !program.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(name)) {
612                resolutions.forEach(watchFailedLookupLocationOfResolution);
613            }
614        }
615
616        function setDirectoryWatcher(dir: string, dirPath: Path, nonRecursive?: boolean) {
617            const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
618            if (dirWatcher) {
619                Debug.assert(!!nonRecursive === !!dirWatcher.nonRecursive);
620                dirWatcher.refCount++;
621            }
622            else {
623                directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath, nonRecursive), refCount: 1, nonRecursive });
624            }
625        }
626
627        function stopWatchFailedLookupLocationOfResolution<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>(
628            resolution: T,
629            filePath: Path,
630            getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>,
631        ) {
632            unorderedRemoveItem(Debug.assertDefined(resolution.files), filePath);
633            resolution.refCount!--;
634            if (resolution.refCount) {
635                return;
636            }
637            const resolved = getResolutionWithResolvedFileName(resolution);
638            if (resolved && resolved.resolvedFileName) {
639                resolvedFileToResolution.remove(resolutionHost.toPath(resolved.resolvedFileName), resolution);
640            }
641
642            if (!unorderedRemoveItem(resolutionsWithFailedLookups, resolution)) {
643                // If not watching failed lookups, it wont be there in resolutionsWithFailedLookups
644                return;
645            }
646
647            const { failedLookupLocations } = resolution;
648            let removeAtRoot = false;
649            for (const failedLookupLocation of failedLookupLocations) {
650                const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
651                const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
652                if (toWatch) {
653                    const { dirPath } = toWatch;
654                    const refCount = customFailedLookupPaths.get(failedLookupLocationPath);
655                    if (refCount) {
656                        if (refCount === 1) {
657                            customFailedLookupPaths.delete(failedLookupLocationPath);
658                        }
659                        else {
660                            Debug.assert(refCount > 1);
661                            customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1);
662                        }
663                    }
664
665                    if (dirPath === rootPath) {
666                        removeAtRoot = true;
667                    }
668                    else {
669                        removeDirectoryWatcher(dirPath);
670                    }
671                }
672            }
673            if (removeAtRoot) {
674                removeDirectoryWatcher(rootPath);
675            }
676        }
677
678        function removeDirectoryWatcher(dirPath: string) {
679            const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath)!;
680            // Do not close the watcher yet since it might be needed by other failed lookup locations.
681            dirWatcher.refCount--;
682        }
683
684        function createDirectoryWatcher(directory: string, dirPath: Path, nonRecursive: boolean | undefined) {
685            return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrDirectory => {
686                const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory);
687                if (cachedDirectoryStructureHost) {
688                    // Since the file existence changed, update the sourceFiles cache
689                    cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
690                }
691
692                scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath);
693            }, nonRecursive ? WatchDirectoryFlags.None : WatchDirectoryFlags.Recursive);
694        }
695
696        function removeResolutionsOfFileFromCache<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>(
697            cache: ESMap<string, ESMap<string, T>>,
698            filePath: Path,
699            getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>,
700        ) {
701            // Deleted file, stop watching failed lookups for all the resolutions in the file
702            const resolutions = cache.get(filePath);
703            if (resolutions) {
704                resolutions.forEach(resolution => stopWatchFailedLookupLocationOfResolution(resolution, filePath, getResolutionWithResolvedFileName));
705                cache.delete(filePath);
706            }
707        }
708
709        function removeResolutionsFromProjectReferenceRedirects(filePath: Path) {
710            if (!fileExtensionIs(filePath, Extension.Json)) { return; }
711
712            const program = resolutionHost.getCurrentProgram();
713            if (!program) { return; }
714
715            // If this file is input file for the referenced project, get it
716            const resolvedProjectReference = program.getResolvedProjectReferenceByPath(filePath);
717            if (!resolvedProjectReference) { return; }
718
719            // filePath is for the projectReference and the containing file is from this project reference, invalidate the resolution
720            resolvedProjectReference.commandLine.fileNames.forEach(f => removeResolutionsOfFile(resolutionHost.toPath(f)));
721        }
722
723        function removeResolutionsOfFile(filePath: Path) {
724            removeResolutionsOfFileFromCache(resolvedModuleNames, filePath, getResolvedModule);
725            removeResolutionsOfFileFromCache(resolvedTypeReferenceDirectives, filePath, getResolvedTypeReferenceDirective);
726        }
727
728        function invalidateResolutions(resolutions: ResolutionWithFailedLookupLocations[] | undefined, canInvalidate: (resolution: ResolutionWithFailedLookupLocations) => boolean) {
729            if (!resolutions) return false;
730            let invalidated = false;
731            for (const resolution of resolutions) {
732                if (resolution.isInvalidated || !canInvalidate(resolution)) continue;
733                resolution.isInvalidated = invalidated = true;
734                for (const containingFilePath of Debug.assertDefined(resolution.files)) {
735                    (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = new Set())).add(containingFilePath);
736                    // When its a file with inferred types resolution, invalidate type reference directive resolution
737                    hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames || endsWith(containingFilePath, inferredTypesContainingFile);
738                }
739            }
740            return invalidated;
741        }
742
743        function invalidateResolutionOfFile(filePath: Path) {
744            removeResolutionsOfFile(filePath);
745            // Resolution is invalidated if the resulting file name is same as the deleted file path
746            const prevHasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames;
747            if (invalidateResolutions(resolvedFileToResolution.get(filePath), returnTrue) &&
748                hasChangedAutomaticTypeDirectiveNames &&
749                !prevHasChangedAutomaticTypeDirectiveNames) {
750                resolutionHost.onChangedAutomaticTypeDirectiveNames();
751            }
752        }
753
754        function setFilesWithInvalidatedNonRelativeUnresolvedImports(filesMap: ReadonlyESMap<Path, readonly string[]>) {
755            Debug.assert(filesWithInvalidatedNonRelativeUnresolvedImports === filesMap || filesWithInvalidatedNonRelativeUnresolvedImports === undefined);
756            filesWithInvalidatedNonRelativeUnresolvedImports = filesMap;
757        }
758
759        function scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) {
760            if (isCreatingWatchedDirectory) {
761                // Watching directory is created
762                // Invalidate any resolution has failed lookup in this directory
763                isInDirectoryChecks.push(fileOrDirectoryPath);
764            }
765            else {
766                // If something to do with folder/file starting with "." in node_modules folder, skip it
767                const updatedPath = removeIgnoredPath(fileOrDirectoryPath);
768                if (!updatedPath) return false;
769                fileOrDirectoryPath = updatedPath;
770
771                // prevent saving an open file from over-eagerly triggering invalidation
772                if (resolutionHost.fileIsOpen(fileOrDirectoryPath)) {
773                    return false;
774                }
775
776                // Some file or directory in the watching directory is created
777                // Return early if it does not have any of the watching extension or not the custom failed lookup path
778                const dirOfFileOrDirectory = getDirectoryPath(fileOrDirectoryPath);
779                const isOHModules = isOhpm(resolutionHost.getCompilationSettings().packageManagerType);
780                if (isNodeModulesAtTypesDirectory(fileOrDirectoryPath) || (isOHModules && isOHModulesAtTypesDirectory(fileOrDirectoryPath)) ||
781                    isNodeModulesDirectory(fileOrDirectoryPath) || isNodeModulesAtTypesDirectory(dirOfFileOrDirectory) || (isOHModules &&
782                    isOHModulesAtTypesDirectory(dirOfFileOrDirectory)) || isNodeModulesDirectory(dirOfFileOrDirectory)) {
783                    // Invalidate any resolution from this directory
784                    failedLookupChecks.push(fileOrDirectoryPath);
785                    startsWithPathChecks.push(fileOrDirectoryPath);
786                }
787                else {
788                    if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) {
789                        return false;
790                    }
791                    // Ignore emits from the program
792                    if (isEmittedFileOfProgram(resolutionHost.getCurrentProgram(), fileOrDirectoryPath)) {
793                        return false;
794                    }
795                    // Resolution need to be invalidated if failed lookup location is same as the file or directory getting created
796                    failedLookupChecks.push(fileOrDirectoryPath);
797                }
798            }
799            resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations();
800        }
801
802        function invalidateResolutionsOfFailedLookupLocations() {
803            if (!failedLookupChecks.length && !startsWithPathChecks.length && !isInDirectoryChecks.length) {
804                return false;
805            }
806
807            const invalidated = invalidateResolutions(resolutionsWithFailedLookups, canInvalidateFailedLookupResolution);
808            failedLookupChecks.length = 0;
809            startsWithPathChecks.length = 0;
810            isInDirectoryChecks.length = 0;
811            return invalidated;
812        }
813
814        function canInvalidateFailedLookupResolution(resolution: ResolutionWithFailedLookupLocations) {
815            return resolution.failedLookupLocations.some(location => {
816                const locationPath = resolutionHost.toPath(location);
817                return contains(failedLookupChecks, locationPath) ||
818                    startsWithPathChecks.some(fileOrDirectoryPath => startsWith(locationPath, fileOrDirectoryPath)) ||
819                    isInDirectoryChecks.some(fileOrDirectoryPath => isInDirectoryPath(fileOrDirectoryPath, locationPath));
820            });
821        }
822
823        function closeTypeRootsWatch() {
824            clearMap(typeRootsWatches, closeFileWatcher);
825        }
826
827        function getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot: string, typeRootPath: Path): Path | undefined {
828            if (isInDirectoryPath(rootPath, typeRootPath)) {
829                return rootPath;
830            }
831            const toWatch = getDirectoryToWatchFromFailedLookupLocationDirectory(typeRoot, typeRootPath);
832            return toWatch && directoryWatchesOfFailedLookups.has(toWatch.dirPath) ? toWatch.dirPath : undefined;
833        }
834
835        function createTypeRootsWatch(typeRootPath: Path, typeRoot: string): FileWatcher {
836            // Create new watch and recursive info
837            return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => {
838                const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory);
839                if (cachedDirectoryStructureHost) {
840                    // Since the file existence changed, update the sourceFiles cache
841                    cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
842                }
843
844                // For now just recompile
845                // We could potentially store more data here about whether it was/would be really be used or not
846                // and with that determine to trigger compilation but for now this is enough
847                hasChangedAutomaticTypeDirectiveNames = true;
848                resolutionHost.onChangedAutomaticTypeDirectiveNames();
849
850                // Since directory watchers invoked are flaky, the failed lookup location events might not be triggered
851                // So handle to failed lookup locations here as well to ensure we are invalidating resolutions
852                const dirPath = getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot, typeRootPath);
853                if (dirPath) {
854                    scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath);
855                }
856            }, WatchDirectoryFlags.Recursive);
857        }
858
859        /**
860         * Watches the types that would get added as part of getAutomaticTypeDirectiveNames
861         * To be called when compiler options change
862         */
863        function updateTypeRootsWatch() {
864            const options = resolutionHost.getCompilationSettings();
865            if (options.types) {
866                // No need to do any watch since resolution cache is going to handle the failed lookups
867                // for the types added by this
868                closeTypeRootsWatch();
869                return;
870            }
871
872            // we need to assume the directories exist to ensure that we can get all the type root directories that get included
873            // But filter directories that are at root level to say directory doesnt exist, so that we arent watching them
874            const typeRoots = getEffectiveTypeRoots(options, { directoryExists: directoryExistsForTypeRootWatch, getCurrentDirectory });
875            if (typeRoots) {
876                mutateMap(
877                    typeRootsWatches,
878                    arrayToMap(typeRoots, tr => resolutionHost.toPath(tr)),
879                    {
880                        createNewValue: createTypeRootsWatch,
881                        onDeleteValue: closeFileWatcher
882                    }
883                );
884            }
885            else {
886                closeTypeRootsWatch();
887            }
888        }
889
890        /**
891         * Use this function to return if directory exists to get type roots to watch
892         * If we return directory exists then only the paths will be added to type roots
893         * Hence return true for all directories except root directories which are filtered from watching
894         */
895        function directoryExistsForTypeRootWatch(nodeTypesDirectory: string) {
896            const dir = getDirectoryPath(getDirectoryPath(nodeTypesDirectory));
897            const dirPath = resolutionHost.toPath(dir);
898            return dirPath === rootPath || canWatchDirectory(dirPath);
899        }
900    }
901}
902