• 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, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[];
9        getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): CachedResolvedModuleWithFailedLookupLocations | undefined;
10        resolveTypeReferenceDirectives(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"]): (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        createHasInvalidatedResolutions(customHasInvalidatedResolutions: HasInvalidatedResolutions): HasInvalidatedResolutions;
18        hasChangedAutomaticTypeDirectiveNames(): boolean;
19        isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean;
20
21
22        startCachingPerDirectoryResolution(): void;
23        finishCachingPerDirectoryResolution(newProgram: Program | undefined, oldProgram: Program | undefined): void;
24
25        updateTypeRootsWatch(): void;
26        closeTypeRootsWatch(): void;
27
28        getModuleResolutionCache(): ModuleResolutionCache;
29
30        clear(): void;
31    }
32
33    interface ResolutionWithFailedLookupLocations {
34        readonly failedLookupLocations: string[];
35        readonly affectingLocations: string[];
36        isInvalidated?: boolean;
37        refCount?: number;
38        // Files that have this resolution using
39        files?: Path[];
40    }
41
42    interface ResolutionWithResolvedFileName {
43        resolvedFileName: string | undefined;
44        packagetId?: PackageId;
45    }
46
47    interface CachedResolvedModuleWithFailedLookupLocations extends ResolvedModuleWithFailedLookupLocations, ResolutionWithFailedLookupLocations {
48    }
49
50    interface CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations extends ResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolutionWithFailedLookupLocations {
51    }
52
53    export interface ResolutionCacheHost extends MinimalResolutionCacheHost {
54        toPath(fileName: string): Path;
55        getCanonicalFileName: GetCanonicalFileName;
56        getCompilationSettings(): CompilerOptions;
57        watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher;
58        watchAffectingFileLocation(file: string, cb: FileWatcherCallback): FileWatcher;
59        onInvalidatedResolution(): void;
60        watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher;
61        onChangedAutomaticTypeDirectiveNames(): void;
62        scheduleInvalidateResolutionsOfFailedLookupLocations(): void;
63        getCachedDirectoryStructureHost(): CachedDirectoryStructureHost | undefined;
64        projectName?: string;
65        getGlobalCache?(): string | undefined;
66        globalCacheResolutionModuleName?(externalModuleName: string): string;
67        writeLog(s: string): void;
68        getCurrentProgram(): Program | undefined;
69        fileIsOpen(filePath: Path): boolean;
70        onDiscoveredSymlink?(): void;
71    }
72
73    interface FileWatcherOfAffectingLocation {
74        /** watcher for the lookup */
75        watcher: FileWatcher;
76        resolutions: number;
77        files: number;
78        paths: Set<string>;
79    }
80
81    interface DirectoryWatchesOfFailedLookup {
82        /** watcher for the lookup */
83        watcher: FileWatcher;
84        /** ref count keeping this watch alive */
85        refCount: number;
86        /** is the directory watched being non recursive */
87        nonRecursive?: boolean;
88    }
89
90    interface DirectoryOfFailedLookupWatch {
91        dir: string;
92        dirPath: Path;
93        nonRecursive?: boolean;
94    }
95
96    export function removeIgnoredPath(path: Path): Path | undefined {
97        // Consider whole staging folder as if node_modules or oh_modules changed.
98        if (endsWith(path, "/node_modules/.staging") || endsWith(path, "/oh_modules/.staging")) {
99            return removeSuffix(path, "/.staging") as Path;
100        }
101
102        return some(ignoredPaths, searchPath => stringContains(path, searchPath)) ?
103            undefined :
104            path;
105    }
106
107    /**
108     * Filter out paths like
109     * "/", "/user", "/user/username", "/user/username/folderAtRoot",
110     * "c:/", "c:/users", "c:/users/username", "c:/users/username/folderAtRoot", "c:/folderAtRoot"
111     * @param dirPath
112     */
113    export function canWatchDirectoryOrFile(dirPath: Path) {
114        const rootLength = getRootLength(dirPath);
115        if (dirPath.length === rootLength) {
116            // Ignore "/", "c:/"
117            return false;
118        }
119
120        let nextDirectorySeparator = dirPath.indexOf(directorySeparator, rootLength);
121        if (nextDirectorySeparator === -1) {
122            // ignore "/user", "c:/users" or "c:/folderAtRoot"
123            return false;
124        }
125
126        let pathPartForUserCheck = dirPath.substring(rootLength, nextDirectorySeparator + 1);
127        const isNonDirectorySeparatorRoot = rootLength > 1 || dirPath.charCodeAt(0) !== CharacterCodes.slash;
128        if (isNonDirectorySeparatorRoot &&
129            dirPath.search(/[a-zA-Z]:/) !== 0 && // Non dos style paths
130            pathPartForUserCheck.search(/[a-zA-Z]\$\//) === 0) { // Dos style nextPart
131            nextDirectorySeparator = dirPath.indexOf(directorySeparator, nextDirectorySeparator + 1);
132            if (nextDirectorySeparator === -1) {
133                // ignore "//vda1cs4850/c$/folderAtRoot"
134                return false;
135            }
136
137            pathPartForUserCheck = dirPath.substring(rootLength + pathPartForUserCheck.length, nextDirectorySeparator + 1);
138        }
139
140        if (isNonDirectorySeparatorRoot &&
141            pathPartForUserCheck.search(/users\//i) !== 0) {
142            // Paths like c:/folderAtRoot/subFolder are allowed
143            return true;
144        }
145
146        for (let searchIndex = nextDirectorySeparator + 1, searchLevels = 2; searchLevels > 0; searchLevels--) {
147            searchIndex = dirPath.indexOf(directorySeparator, searchIndex) + 1;
148            if (searchIndex === 0) {
149                // Folder isnt at expected minimum levels
150                return false;
151            }
152        }
153        return true;
154    }
155
156    type GetResolutionWithResolvedFileName<T extends ResolutionWithFailedLookupLocations = ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName = ResolutionWithResolvedFileName> =
157        (resolution: T) => R | undefined;
158
159    export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string | undefined, logChangesWhenResolvingModule: boolean): ResolutionCache {
160        let filesWithChangedSetOfUnresolvedImports: Path[] | undefined;
161        let filesWithInvalidatedResolutions: Set<Path> | undefined;
162        let filesWithInvalidatedNonRelativeUnresolvedImports: ReadonlyESMap<Path, readonly string[]> | undefined;
163        const nonRelativeExternalModuleResolutions = createMultiMap<ResolutionWithFailedLookupLocations>();
164
165        const resolutionsWithFailedLookups: ResolutionWithFailedLookupLocations[] = [];
166        const resolutionsWithOnlyAffectingLocations: ResolutionWithFailedLookupLocations[] = [];
167        const resolvedFileToResolution = createMultiMap<ResolutionWithFailedLookupLocations>();
168        const impliedFormatPackageJsons = new Map<Path, readonly string[]>();
169
170        let hasChangedAutomaticTypeDirectiveNames = false;
171        let affectingPathChecksForFile: Set<string> | undefined;
172        let affectingPathChecks: Set<string> | undefined;
173        let failedLookupChecks: Set<Path> | undefined;
174        let startsWithPathChecks: Set<Path> | undefined;
175        let isInDirectoryChecks: Set<Path> | undefined;
176
177        const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory!()); // TODO: GH#18217
178        const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost();
179
180        // The resolvedModuleNames and resolvedTypeReferenceDirectives are the cache of resolutions per file.
181        // The key in the map is source file's path.
182        // The values are Map of resolutions with key being name lookedup.
183        const resolvedModuleNames = new Map<Path, ModeAwareCache<CachedResolvedModuleWithFailedLookupLocations>>();
184        const perDirectoryResolvedModuleNames: CacheWithRedirects<ModeAwareCache<CachedResolvedModuleWithFailedLookupLocations>> = createCacheWithRedirects();
185        const nonRelativeModuleNameCache: CacheWithRedirects<PerModuleNameCache> = createCacheWithRedirects();
186        const moduleResolutionCache = createModuleResolutionCache(
187            getCurrentDirectory(),
188            resolutionHost.getCanonicalFileName,
189            /*options*/ undefined,
190            perDirectoryResolvedModuleNames,
191            nonRelativeModuleNameCache,
192        );
193
194        const resolvedTypeReferenceDirectives = new Map<Path, ModeAwareCache<CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
195        const perDirectoryResolvedTypeReferenceDirectives: CacheWithRedirects<ModeAwareCache<CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations>> = createCacheWithRedirects();
196        const typeReferenceDirectiveResolutionCache = createTypeReferenceDirectiveResolutionCache(
197            getCurrentDirectory(),
198            resolutionHost.getCanonicalFileName,
199            /*options*/ undefined,
200            moduleResolutionCache.getPackageJsonInfoCache(),
201            perDirectoryResolvedTypeReferenceDirectives
202        );
203
204        /**
205         * These are the extensions that failed lookup files will have by default,
206         * any other extension of failed lookup will be store that path in custom failed lookup path
207         * This helps in not having to comb through all resolutions when files are added/removed
208         * Note that .d.ts file also has .d.ts extension hence will be part of default extensions
209         */
210        const failedLookupDefaultExtensions = resolutionHost.getCompilationSettings().ets
211            ? [Extension.Ets, Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json]
212            : [Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json, Extension.Ets];
213        const customFailedLookupPaths = new Map<string, number>();
214
215        const directoryWatchesOfFailedLookups = new Map<string, DirectoryWatchesOfFailedLookup>();
216        const fileWatchesOfAffectingLocations = new Map<string, FileWatcherOfAffectingLocation>();
217        const rootDir = rootDirForResolution && removeTrailingDirectorySeparator(getNormalizedAbsolutePath(rootDirForResolution, getCurrentDirectory()));
218        const rootPath = (rootDir && resolutionHost.toPath(rootDir)) as Path; // TODO: GH#18217
219        const rootSplitLength = rootPath !== undefined ? rootPath.split(directorySeparator).length : 0;
220
221        // TypeRoot watches for the types that get added as part of getAutomaticTypeDirectiveNames
222        const typeRootsWatches = new Map<string, FileWatcher>();
223
224        return {
225            getModuleResolutionCache: () => moduleResolutionCache,
226            startRecordingFilesWithChangedResolutions,
227            finishRecordingFilesWithChangedResolutions,
228            // perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update
229            // (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution)
230            startCachingPerDirectoryResolution,
231            finishCachingPerDirectoryResolution,
232            resolveModuleNames,
233            getResolvedModuleWithFailedLookupLocationsFromCache,
234            resolveTypeReferenceDirectives,
235            removeResolutionsFromProjectReferenceRedirects,
236            removeResolutionsOfFile,
237            hasChangedAutomaticTypeDirectiveNames: () => hasChangedAutomaticTypeDirectiveNames,
238            invalidateResolutionOfFile,
239            invalidateResolutionsOfFailedLookupLocations,
240            setFilesWithInvalidatedNonRelativeUnresolvedImports,
241            createHasInvalidatedResolutions,
242            isFileWithInvalidatedNonRelativeUnresolvedImports,
243            updateTypeRootsWatch,
244            closeTypeRootsWatch,
245            clear
246        };
247
248        function getResolvedModule(resolution: CachedResolvedModuleWithFailedLookupLocations) {
249            return resolution.resolvedModule;
250        }
251
252        function getResolvedTypeReferenceDirective(resolution: CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations) {
253            return resolution.resolvedTypeReferenceDirective;
254        }
255
256        function isInDirectoryPath(dir: Path | undefined, file: Path) {
257            if (dir === undefined || file.length <= dir.length) {
258                return false;
259            }
260            return startsWith(file, dir) && file[dir.length] === directorySeparator;
261        }
262
263        function clear() {
264            clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf);
265            clearMap(fileWatchesOfAffectingLocations, closeFileWatcherOf);
266            customFailedLookupPaths.clear();
267            nonRelativeExternalModuleResolutions.clear();
268            closeTypeRootsWatch();
269            resolvedModuleNames.clear();
270            resolvedTypeReferenceDirectives.clear();
271            resolvedFileToResolution.clear();
272            resolutionsWithFailedLookups.length = 0;
273            resolutionsWithOnlyAffectingLocations.length = 0;
274            failedLookupChecks = undefined;
275            startsWithPathChecks = undefined;
276            isInDirectoryChecks = undefined;
277            affectingPathChecks = undefined;
278            affectingPathChecksForFile = undefined;
279            moduleResolutionCache.clear();
280            typeReferenceDirectiveResolutionCache.clear();
281            impliedFormatPackageJsons.clear();
282            hasChangedAutomaticTypeDirectiveNames = false;
283        }
284
285        function startRecordingFilesWithChangedResolutions() {
286            filesWithChangedSetOfUnresolvedImports = [];
287        }
288
289        function finishRecordingFilesWithChangedResolutions() {
290            const collected = filesWithChangedSetOfUnresolvedImports;
291            filesWithChangedSetOfUnresolvedImports = undefined;
292            return collected;
293        }
294
295        function isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean {
296            if (!filesWithInvalidatedNonRelativeUnresolvedImports) {
297                return false;
298            }
299
300            // Invalidated if file has unresolved imports
301            const value = filesWithInvalidatedNonRelativeUnresolvedImports.get(path);
302            return !!value && !!value.length;
303        }
304
305        function createHasInvalidatedResolutions(customHasInvalidatedResolutions: HasInvalidatedResolutions): HasInvalidatedResolutions {
306            // Ensure pending resolutions are applied
307            invalidateResolutionsOfFailedLookupLocations();
308            const collected = filesWithInvalidatedResolutions;
309            filesWithInvalidatedResolutions = undefined;
310            return path => customHasInvalidatedResolutions(path) ||
311                !!collected?.has(path) ||
312                isFileWithInvalidatedNonRelativeUnresolvedImports(path);
313        }
314
315        function startCachingPerDirectoryResolution() {
316            moduleResolutionCache.clearAllExceptPackageJsonInfoCache();
317            typeReferenceDirectiveResolutionCache.clearAllExceptPackageJsonInfoCache();
318            // perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update
319            // (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution)
320            nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions);
321            nonRelativeExternalModuleResolutions.clear();
322        }
323
324        function finishCachingPerDirectoryResolution(newProgram: Program | undefined, oldProgram: Program | undefined) {
325            filesWithInvalidatedNonRelativeUnresolvedImports = undefined;
326            nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions);
327            nonRelativeExternalModuleResolutions.clear();
328            // Update file watches
329            if (newProgram !== oldProgram) {
330                newProgram?.getSourceFiles().forEach(newFile => {
331                    const expected = isExternalOrCommonJsModule(newFile) ? newFile.packageJsonLocations?.length ?? 0 : 0;
332                    const existing = impliedFormatPackageJsons.get(newFile.path) ?? emptyArray;
333                    for (let i = existing.length; i < expected; i++) {
334                        createFileWatcherOfAffectingLocation(newFile.packageJsonLocations![i], /*forResolution*/ false);
335                    }
336                    if (existing.length > expected) {
337                        for (let i = expected; i < existing.length; i++) {
338                            fileWatchesOfAffectingLocations.get(existing[i])!.files--;
339                        }
340                    }
341                    if (expected) impliedFormatPackageJsons.set(newFile.path, newFile.packageJsonLocations!);
342                    else impliedFormatPackageJsons.delete(newFile.path);
343                });
344                impliedFormatPackageJsons.forEach((existing, path) => {
345                    if (!newProgram?.getSourceFileByPath(path)) {
346                        existing.forEach(location => fileWatchesOfAffectingLocations.get(location)!.files--);
347                        impliedFormatPackageJsons.delete(path);
348                    }
349                });
350            }
351            directoryWatchesOfFailedLookups.forEach((watcher, path) => {
352                if (watcher.refCount === 0) {
353                    directoryWatchesOfFailedLookups.delete(path);
354                    watcher.watcher.close();
355                }
356            });
357            fileWatchesOfAffectingLocations.forEach((watcher, path) => {
358                if (watcher.files === 0 && watcher.resolutions === 0) {
359                    fileWatchesOfAffectingLocations.delete(path);
360                    watcher.watcher.close();
361                }
362            });
363
364            hasChangedAutomaticTypeDirectiveNames = false;
365        }
366
367        function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, _containingSourceFile?: never, mode?: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): CachedResolvedModuleWithFailedLookupLocations {
368            const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host, moduleResolutionCache, redirectedReference, mode);
369            // return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts
370            if (!resolutionHost.getGlobalCache) {
371                return primaryResult;
372            }
373
374            // otherwise try to load typings from @types
375            const globalCache = resolutionHost.getGlobalCache();
376            if (globalCache !== undefined && !isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTS(primaryResult.resolvedModule.extension))) {
377                // create different collection of failed lookup locations for second pass
378                // if it will fail and we've already found something during the first pass - we don't want to pollute its results
379                const { resolvedModule, failedLookupLocations, affectingLocations } = loadModuleFromGlobalCache(
380                    Debug.checkDefined(resolutionHost.globalCacheResolutionModuleName)(moduleName),
381                    resolutionHost.projectName,
382                    compilerOptions,
383                    host,
384                    globalCache,
385                    moduleResolutionCache,
386                );
387                if (resolvedModule) {
388                    // Modify existing resolution so its saved in the directory cache as well
389                    (primaryResult.resolvedModule as any) = resolvedModule;
390                    primaryResult.failedLookupLocations.push(...failedLookupLocations);
391                    primaryResult.affectingLocations.push(...affectingLocations);
392                    return primaryResult;
393                }
394            }
395
396            // Default return the result from the first pass
397            return primaryResult;
398        }
399
400        function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, _containingSourceFile?: SourceFile, resolutionMode?: SourceFile["impliedNodeFormat"] | undefined): CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations {
401            return ts.resolveTypeReferenceDirective(typeReferenceDirectiveName, containingFile, options, host, redirectedReference, typeReferenceDirectiveResolutionCache, resolutionMode);
402        }
403
404        interface ResolveNamesWithLocalCacheInput<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName> {
405            names: readonly string[] | readonly FileReference[];
406            containingFile: string;
407            redirectedReference: ResolvedProjectReference | undefined;
408            cache: ESMap<Path, ModeAwareCache<T>>;
409            perDirectoryCacheWithRedirects: CacheWithRedirects<ModeAwareCache<T>>;
410            loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) => T;
411            getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>;
412            shouldRetryResolution: (t: T) => boolean;
413            reusedNames?: readonly string[];
414            logChanges?: boolean;
415            containingSourceFile?: SourceFile;
416            containingSourceFileMode?: SourceFile["impliedNodeFormat"];
417        }
418        function resolveNamesWithLocalCache<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>({
419            names, containingFile, redirectedReference,
420            cache, perDirectoryCacheWithRedirects,
421            loader, getResolutionWithResolvedFileName,
422            shouldRetryResolution, reusedNames, logChanges, containingSourceFile, containingSourceFileMode
423        }: ResolveNamesWithLocalCacheInput<T, R>): (R | undefined)[] {
424            const path = resolutionHost.toPath(containingFile);
425            const resolutionsInFile = cache.get(path) || cache.set(path, createModeAwareCache()).get(path)!;
426            const dirPath = getDirectoryPath(path);
427            const perDirectoryCache = perDirectoryCacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference);
428            let perDirectoryResolution = perDirectoryCache.get(dirPath);
429            if (!perDirectoryResolution) {
430                perDirectoryResolution = createModeAwareCache();
431                perDirectoryCache.set(dirPath, perDirectoryResolution);
432            }
433            const resolvedModules: (R | undefined)[] = [];
434            const compilerOptions = resolutionHost.getCompilationSettings();
435            const hasInvalidatedNonRelativeUnresolvedImport = logChanges && isFileWithInvalidatedNonRelativeUnresolvedImports(path);
436
437            // All the resolutions in this file are invalidated if this file wasn't resolved using same redirect
438            const program = resolutionHost.getCurrentProgram();
439            const oldRedirect = program && program.getResolvedProjectReferenceToRedirect(containingFile);
440            const unmatchedRedirects = oldRedirect ?
441                !redirectedReference || redirectedReference.sourceFile.path !== oldRedirect.sourceFile.path :
442                !!redirectedReference;
443
444            const seenNamesInFile = createModeAwareCache<true>();
445            let i = 0;
446            for (const entry of names) {
447                const name = isString(entry) ? entry : entry.fileName.toLowerCase();
448                // Imports supply a `containingSourceFile` but no `containingSourceFileMode` - it would be redundant
449                // they require calculating the mode for a given import from it's position in the resolution table, since a given
450                // import's syntax may override the file's default mode.
451                // Type references instead supply a `containingSourceFileMode` and a non-string entry which contains
452                // a default file mode override if applicable.
453                const mode = !isString(entry) ? getModeForFileReference(entry, containingSourceFileMode) :
454                    containingSourceFile ? getModeForResolutionAtIndex(containingSourceFile, i) : undefined;
455                i++;
456                let resolution = resolutionsInFile.get(name, mode);
457                // Resolution is valid if it is present and not invalidated
458                if (!seenNamesInFile.has(name, mode) &&
459                    unmatchedRedirects || !resolution || resolution.isInvalidated ||
460                    // If the name is unresolved import that was invalidated, recalculate
461                    (hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && shouldRetryResolution(resolution))) {
462                    const existingResolution = resolution;
463                    const resolutionInDirectory = perDirectoryResolution.get(name, mode);
464                    if (resolutionInDirectory) {
465                        resolution = resolutionInDirectory;
466                        const host = resolutionHost.getCompilerHost?.() || resolutionHost;
467                        if (isTraceEnabled(compilerOptions, host)) {
468                            const resolved = getResolutionWithResolvedFileName(resolution);
469                            trace(
470                                host,
471                                loader === resolveModuleName as unknown ?
472                                    resolved?.resolvedFileName ?
473                                        resolved.packagetId ?
474                                            Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3_with_Package_ID_4:
475                                            Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3:
476                                        Diagnostics.Reusing_resolution_of_module_0_from_1_found_in_cache_from_location_2_it_was_not_resolved :
477                                    resolved?.resolvedFileName ?
478                                        resolved.packagetId ?
479                                            Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3_with_Package_ID_4 :
480                                            Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_successfully_resolved_to_3 :
481                                        Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_found_in_cache_from_location_2_it_was_not_resolved,
482                                name,
483                                containingFile,
484                                getDirectoryPath(containingFile),
485                                resolved?.resolvedFileName,
486                                resolved?.packagetId && packageIdToString(resolved.packagetId)
487                            );
488                        }
489                    }
490                    else {
491                        resolution = loader(name, containingFile, compilerOptions, resolutionHost.getCompilerHost?.() || resolutionHost, redirectedReference, containingSourceFile, mode);
492                        perDirectoryResolution.set(name, mode, resolution);
493                        if (resolutionHost.onDiscoveredSymlink && resolutionIsSymlink(resolution)) {
494                            resolutionHost.onDiscoveredSymlink();
495                        }
496                    }
497                    resolutionsInFile.set(name, mode, resolution);
498                    watchFailedLookupLocationsOfExternalModuleResolutions(name, resolution, path, getResolutionWithResolvedFileName);
499                    if (existingResolution) {
500                        stopWatchFailedLookupLocationOfResolution(existingResolution, path, getResolutionWithResolvedFileName);
501                    }
502
503                    if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) {
504                        filesWithChangedSetOfUnresolvedImports.push(path);
505                        // reset log changes to avoid recording the same file multiple times
506                        logChanges = false;
507                    }
508                }
509                else {
510                    const host = resolutionHost.getCompilerHost?.() || resolutionHost;
511                    if (isTraceEnabled(compilerOptions, host) && !seenNamesInFile.has(name, mode)) {
512                        const resolved = getResolutionWithResolvedFileName(resolution);
513                        trace(
514                            host,
515                            loader === resolveModuleName as unknown ?
516                                resolved?.resolvedFileName ?
517                                    resolved.packagetId ?
518                                        Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 :
519                                        Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2 :
520                                    Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_not_resolved :
521                                resolved?.resolvedFileName ?
522                                    resolved.packagetId ?
523                                        Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 :
524                                        Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2 :
525                                    Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_not_resolved,
526                            name,
527                            containingFile,
528                            resolved?.resolvedFileName,
529                            resolved?.packagetId && packageIdToString(resolved.packagetId)
530                        );
531                    }
532                }
533                Debug.assert(resolution !== undefined && !resolution.isInvalidated);
534                seenNamesInFile.set(name, mode, true);
535                resolvedModules.push(getResolutionWithResolvedFileName(resolution));
536            }
537
538            // Stop watching and remove the unused name
539            resolutionsInFile.forEach((resolution, name, mode) => {
540                if (!seenNamesInFile.has(name, mode) && !contains(reusedNames, name)) {
541                    stopWatchFailedLookupLocationOfResolution(resolution, path, getResolutionWithResolvedFileName);
542                    resolutionsInFile.delete(name, mode);
543                }
544            });
545
546            return resolvedModules;
547
548            function resolutionIsEqualTo(oldResolution: T | undefined, newResolution: T | undefined): boolean {
549                if (oldResolution === newResolution) {
550                    return true;
551                }
552                if (!oldResolution || !newResolution) {
553                    return false;
554                }
555                const oldResult = getResolutionWithResolvedFileName(oldResolution);
556                const newResult = getResolutionWithResolvedFileName(newResolution);
557                if (oldResult === newResult) {
558                    return true;
559                }
560                if (!oldResult || !newResult) {
561                    return false;
562                }
563                return oldResult.resolvedFileName === newResult.resolvedFileName;
564            }
565        }
566
567        function resolveTypeReferenceDirectives(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"]): (ResolvedTypeReferenceDirective | undefined)[] {
568            return resolveNamesWithLocalCache<CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolvedTypeReferenceDirective>({
569                names: typeDirectiveNames,
570                containingFile,
571                redirectedReference,
572                cache: resolvedTypeReferenceDirectives,
573                perDirectoryCacheWithRedirects: perDirectoryResolvedTypeReferenceDirectives,
574                loader: resolveTypeReferenceDirective,
575                getResolutionWithResolvedFileName: getResolvedTypeReferenceDirective,
576                shouldRetryResolution: resolution => resolution.resolvedTypeReferenceDirective === undefined,
577                containingSourceFileMode: containingFileMode
578            });
579        }
580
581        function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[] {
582            return resolveNamesWithLocalCache<CachedResolvedModuleWithFailedLookupLocations, ResolvedModuleFull>({
583                names: moduleNames,
584                containingFile,
585                redirectedReference,
586                cache: resolvedModuleNames,
587                perDirectoryCacheWithRedirects: perDirectoryResolvedModuleNames,
588                loader: resolveModuleName,
589                getResolutionWithResolvedFileName: getResolvedModule,
590                shouldRetryResolution: resolution => !resolution.resolvedModule || !resolutionExtensionIsTSOrJson(resolution.resolvedModule.extension),
591                reusedNames,
592                logChanges: logChangesWhenResolvingModule,
593                containingSourceFile,
594            });
595        }
596
597        function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): CachedResolvedModuleWithFailedLookupLocations | undefined {
598            const cache = resolvedModuleNames.get(resolutionHost.toPath(containingFile));
599            if (!cache) return undefined;
600            return cache.get(moduleName, resolutionMode);
601        }
602
603        function isNodeModulesAtTypesDirectory(dirPath: Path) {
604            return endsWith(dirPath, "/node_modules/@types");
605        }
606
607        function getDirectoryToWatchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path): DirectoryOfFailedLookupWatch | undefined {
608            if (isInDirectoryPath(rootPath, failedLookupLocationPath)) {
609                // Ensure failed look up is normalized path
610                failedLookupLocation = isRootedDiskPath(failedLookupLocation) ? normalizePath(failedLookupLocation) : getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory());
611                const failedLookupPathSplit = failedLookupLocationPath.split(directorySeparator);
612                const failedLookupSplit = failedLookupLocation.split(directorySeparator);
613                Debug.assert(failedLookupSplit.length === failedLookupPathSplit.length, `FailedLookup: ${failedLookupLocation} failedLookupLocationPath: ${failedLookupLocationPath}`);
614                if (failedLookupPathSplit.length > rootSplitLength + 1) {
615                    // Instead of watching root, watch directory in root to avoid watching excluded directories not needed for module resolution
616                    return {
617                        dir: failedLookupSplit.slice(0, rootSplitLength + 1).join(directorySeparator),
618                        dirPath: failedLookupPathSplit.slice(0, rootSplitLength + 1).join(directorySeparator) as Path
619                    };
620                }
621                else {
622                    // Always watch root directory non recursively
623                    return {
624                        dir: rootDir!,
625                        dirPath: rootPath,
626                        nonRecursive: false
627                    };
628                }
629            }
630
631            return getDirectoryToWatchFromFailedLookupLocationDirectory(
632                getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory())),
633                getDirectoryPath(failedLookupLocationPath)
634            );
635        }
636
637        function getDirectoryToWatchFromFailedLookupLocationDirectory(dir: string, dirPath: Path): DirectoryOfFailedLookupWatch | undefined {
638            // If directory path contains node module, get the most parent node_modules or oh_modules directory for watching
639            const isOHModules: boolean = isOhpm(resolutionHost.getCompilationSettings().packageManagerType);
640            while (isOHModules ? pathContainsOHModules(dirPath) : pathContainsNodeModules(dirPath)) {
641                dir = getDirectoryPath(dir);
642                dirPath = getDirectoryPath(dirPath);
643            }
644
645            // If the directory is node_modules or oh_modules use it to watch, always watch it recursively
646            if (isTargetModulesDerectory(dirPath)) {
647                return canWatchDirectoryOrFile(getDirectoryPath(dirPath)) ? { dir, dirPath } : undefined;
648            }
649
650            let nonRecursive = true;
651            // Use some ancestor of the root directory
652            let subDirectoryPath: Path | undefined, subDirectory: string | undefined;
653            if (rootPath !== undefined) {
654                while (!isInDirectoryPath(dirPath, rootPath)) {
655                    const parentPath = getDirectoryPath(dirPath);
656                    if (parentPath === dirPath) {
657                        break;
658                    }
659                    nonRecursive = false;
660                    subDirectoryPath = dirPath;
661                    subDirectory = dir;
662                    dirPath = parentPath;
663                    dir = getDirectoryPath(dir);
664                }
665            }
666
667            return canWatchDirectoryOrFile(dirPath) ? { dir: subDirectory || dir, dirPath: subDirectoryPath || dirPath, nonRecursive } : undefined;
668        }
669
670        function isPathWithDefaultFailedLookupExtension(path: Path) {
671            return fileExtensionIsOneOf(path, failedLookupDefaultExtensions);
672        }
673
674        function watchFailedLookupLocationsOfExternalModuleResolutions<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>(
675            name: string,
676            resolution: T,
677            filePath: Path,
678            getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>,
679        ) {
680            if (resolution.refCount) {
681                resolution.refCount++;
682                Debug.assertIsDefined(resolution.files);
683            }
684            else {
685                resolution.refCount = 1;
686                Debug.assert(length(resolution.files) === 0); // This resolution shouldnt be referenced by any file yet
687                if (isExternalModuleNameRelative(name)) {
688                    watchFailedLookupLocationOfResolution(resolution);
689                }
690                else {
691                    nonRelativeExternalModuleResolutions.add(name, resolution);
692                }
693                const resolved = getResolutionWithResolvedFileName(resolution);
694                if (resolved && resolved.resolvedFileName) {
695                    resolvedFileToResolution.add(resolutionHost.toPath(resolved.resolvedFileName), resolution);
696                }
697            }
698            (resolution.files || (resolution.files = [])).push(filePath);
699        }
700
701        function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) {
702            Debug.assert(!!resolution.refCount);
703
704            const { failedLookupLocations, affectingLocations } = resolution;
705            if (!failedLookupLocations.length && !affectingLocations.length) return;
706            if (failedLookupLocations.length) resolutionsWithFailedLookups.push(resolution);
707
708            let setAtRoot = false;
709            for (const failedLookupLocation of failedLookupLocations) {
710                const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
711                const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
712                if (toWatch) {
713                    const { dir, dirPath, nonRecursive } = toWatch;
714                    // If the failed lookup location path is not one of the supported extensions,
715                    // store it in the custom path
716                    if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) {
717                        const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0;
718                        customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1);
719                    }
720                    if (dirPath === rootPath) {
721                        Debug.assert(!nonRecursive);
722                        setAtRoot = true;
723                    }
724                    else {
725                        setDirectoryWatcher(dir, dirPath, nonRecursive);
726                    }
727                }
728            }
729
730            if (setAtRoot) {
731                // This is always non recursive
732                setDirectoryWatcher(rootDir!, rootPath, /*nonRecursive*/ true); // TODO: GH#18217
733            }
734            watchAffectingLocationsOfResolution(resolution, !failedLookupLocations.length);
735        }
736
737        function watchAffectingLocationsOfResolution(resolution: ResolutionWithFailedLookupLocations, addToResolutionsWithOnlyAffectingLocations: boolean) {
738            Debug.assert(!!resolution.refCount);
739            const { affectingLocations } = resolution;
740            if (!affectingLocations.length) return;
741            if (addToResolutionsWithOnlyAffectingLocations) resolutionsWithOnlyAffectingLocations.push(resolution);
742            // Watch package json
743            for (const affectingLocation of affectingLocations) {
744                createFileWatcherOfAffectingLocation(affectingLocation, /*forResolution*/ true);
745            }
746        }
747
748        function createFileWatcherOfAffectingLocation(affectingLocation: string, forResolution: boolean) {
749            const fileWatcher = fileWatchesOfAffectingLocations.get(affectingLocation);
750            if (fileWatcher) {
751                if (forResolution) fileWatcher.resolutions++;
752                else fileWatcher.files++;
753                return;
754            }
755            let locationToWatch = affectingLocation;
756            if (resolutionHost.realpath) {
757                locationToWatch = resolutionHost.realpath(affectingLocation);
758                if (affectingLocation !== locationToWatch) {
759                    const fileWatcher = fileWatchesOfAffectingLocations.get(locationToWatch);
760                    if (fileWatcher) {
761                        if (forResolution) fileWatcher.resolutions++;
762                        else fileWatcher.files++;
763                        fileWatcher.paths.add(affectingLocation);
764                        fileWatchesOfAffectingLocations.set(affectingLocation, fileWatcher);
765                        return;
766                    }
767                }
768            }
769            const paths = new Set<string>();
770            paths.add(locationToWatch);
771            let actualWatcher = canWatchDirectoryOrFile(resolutionHost.toPath(locationToWatch)) ?
772                resolutionHost.watchAffectingFileLocation(locationToWatch, (fileName, eventKind) => {
773                    cachedDirectoryStructureHost?.addOrDeleteFile(fileName, resolutionHost.toPath(locationToWatch), eventKind);
774                    const packageJsonMap = moduleResolutionCache.getPackageJsonInfoCache().getInternalMap();
775                    paths.forEach(path => {
776                        if (watcher.resolutions) (affectingPathChecks ??= new Set()).add(path);
777                        if (watcher.files) (affectingPathChecksForFile ??= new Set()).add(path);
778                        packageJsonMap?.delete(resolutionHost.toPath(path));
779                    });
780                    resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations();
781                }) : noopFileWatcher;
782            const watcher: FileWatcherOfAffectingLocation = {
783                watcher: actualWatcher !== noopFileWatcher ? {
784                    close: () => {
785                        actualWatcher.close();
786                        // Ensure when watching symlinked package.json, we can close the actual file watcher only once
787                        actualWatcher = noopFileWatcher;
788                    }
789                } : actualWatcher,
790                resolutions: forResolution ? 1 : 0,
791                files: forResolution ? 0 : 1,
792                paths,
793            };
794            fileWatchesOfAffectingLocations.set(locationToWatch, watcher);
795            if (affectingLocation !== locationToWatch) {
796                fileWatchesOfAffectingLocations.set(affectingLocation, watcher);
797                paths.add(affectingLocation);
798            }
799        }
800
801        function watchFailedLookupLocationOfNonRelativeModuleResolutions(resolutions: ResolutionWithFailedLookupLocations[], name: string) {
802            const program = resolutionHost.getCurrentProgram();
803            if (!program || !program.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(name)) {
804                resolutions.forEach(watchFailedLookupLocationOfResolution);
805            }
806            else {
807                resolutions.forEach(resolution => watchAffectingLocationsOfResolution(resolution, /*addToResolutionWithOnlyAffectingLocations*/ true));
808            }
809        }
810
811        function setDirectoryWatcher(dir: string, dirPath: Path, nonRecursive?: boolean) {
812            const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
813            if (dirWatcher) {
814                Debug.assert(!!nonRecursive === !!dirWatcher.nonRecursive);
815                dirWatcher.refCount++;
816            }
817            else {
818                directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath, nonRecursive), refCount: 1, nonRecursive });
819            }
820        }
821
822        function stopWatchFailedLookupLocationOfResolution<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>(
823            resolution: T,
824            filePath: Path,
825            getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>,
826        ) {
827            unorderedRemoveItem(Debug.checkDefined(resolution.files), filePath);
828            resolution.refCount!--;
829            if (resolution.refCount) {
830                return;
831            }
832            const resolved = getResolutionWithResolvedFileName(resolution);
833            if (resolved && resolved.resolvedFileName) {
834                resolvedFileToResolution.remove(resolutionHost.toPath(resolved.resolvedFileName), resolution);
835            }
836
837            const { failedLookupLocations, affectingLocations } = resolution;
838            if (unorderedRemoveItem(resolutionsWithFailedLookups, resolution)) {
839                let removeAtRoot = false;
840                for (const failedLookupLocation of failedLookupLocations) {
841                    const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
842                    const toWatch = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
843                    if (toWatch) {
844                        const { dirPath } = toWatch;
845                        const refCount = customFailedLookupPaths.get(failedLookupLocationPath);
846                        if (refCount) {
847                            if (refCount === 1) {
848                                customFailedLookupPaths.delete(failedLookupLocationPath);
849                            }
850                            else {
851                                Debug.assert(refCount > 1);
852                                customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1);
853                            }
854                        }
855
856                        if (dirPath === rootPath) {
857                            removeAtRoot = true;
858                        }
859                        else {
860                            removeDirectoryWatcher(dirPath);
861                        }
862                    }
863                }
864                if (removeAtRoot) {
865                    removeDirectoryWatcher(rootPath);
866                }
867            }
868            else if (affectingLocations.length) {
869                unorderedRemoveItem(resolutionsWithOnlyAffectingLocations, resolution);
870            }
871
872            for (const affectingLocation of affectingLocations) {
873                const watcher = fileWatchesOfAffectingLocations.get(affectingLocation)!;
874                watcher.resolutions--;
875            }
876        }
877
878        function removeDirectoryWatcher(dirPath: string) {
879            const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath)!;
880            // Do not close the watcher yet since it might be needed by other failed lookup locations.
881            dirWatcher.refCount--;
882        }
883
884        function createDirectoryWatcher(directory: string, dirPath: Path, nonRecursive: boolean | undefined) {
885            return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrDirectory => {
886                const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory);
887                if (cachedDirectoryStructureHost) {
888                    // Since the file existence changed, update the sourceFiles cache
889                    cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
890                }
891
892                scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath);
893            }, nonRecursive ? WatchDirectoryFlags.None : WatchDirectoryFlags.Recursive);
894        }
895
896        function removeResolutionsOfFileFromCache<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>(
897            cache: ESMap<string, ModeAwareCache<T>>,
898            filePath: Path,
899            getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>,
900        ) {
901            // Deleted file, stop watching failed lookups for all the resolutions in the file
902            const resolutions = cache.get(filePath);
903            if (resolutions) {
904                resolutions.forEach(resolution => stopWatchFailedLookupLocationOfResolution(resolution, filePath, getResolutionWithResolvedFileName));
905                cache.delete(filePath);
906            }
907        }
908
909        function removeResolutionsFromProjectReferenceRedirects(filePath: Path) {
910            if (!fileExtensionIs(filePath, Extension.Json)) return;
911
912            const program = resolutionHost.getCurrentProgram();
913            if (!program) return;
914
915            // If this file is input file for the referenced project, get it
916            const resolvedProjectReference = program.getResolvedProjectReferenceByPath(filePath);
917            if (!resolvedProjectReference) return;
918
919            // filePath is for the projectReference and the containing file is from this project reference, invalidate the resolution
920            resolvedProjectReference.commandLine.fileNames.forEach(f => removeResolutionsOfFile(resolutionHost.toPath(f)));
921        }
922
923        function removeResolutionsOfFile(filePath: Path) {
924            removeResolutionsOfFileFromCache(resolvedModuleNames, filePath, getResolvedModule);
925            removeResolutionsOfFileFromCache(resolvedTypeReferenceDirectives, filePath, getResolvedTypeReferenceDirective);
926        }
927
928        function invalidateResolutions(resolutions: ResolutionWithFailedLookupLocations[] | undefined, canInvalidate: (resolution: ResolutionWithFailedLookupLocations) => boolean) {
929            if (!resolutions) return false;
930            let invalidated = false;
931            for (const resolution of resolutions) {
932                if (resolution.isInvalidated || !canInvalidate(resolution)) continue;
933                resolution.isInvalidated = invalidated = true;
934                for (const containingFilePath of Debug.checkDefined(resolution.files)) {
935                    (filesWithInvalidatedResolutions ??= new Set()).add(containingFilePath);
936                    // When its a file with inferred types resolution, invalidate type reference directive resolution
937                    hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames || endsWith(containingFilePath, inferredTypesContainingFile);
938                }
939            }
940            return invalidated;
941        }
942
943        function invalidateResolutionOfFile(filePath: Path) {
944            removeResolutionsOfFile(filePath);
945            // Resolution is invalidated if the resulting file name is same as the deleted file path
946            const prevHasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames;
947            if (invalidateResolutions(resolvedFileToResolution.get(filePath), returnTrue) &&
948                hasChangedAutomaticTypeDirectiveNames &&
949                !prevHasChangedAutomaticTypeDirectiveNames) {
950                resolutionHost.onChangedAutomaticTypeDirectiveNames();
951            }
952        }
953
954        function setFilesWithInvalidatedNonRelativeUnresolvedImports(filesMap: ReadonlyESMap<Path, readonly string[]>) {
955            Debug.assert(filesWithInvalidatedNonRelativeUnresolvedImports === filesMap || filesWithInvalidatedNonRelativeUnresolvedImports === undefined);
956            filesWithInvalidatedNonRelativeUnresolvedImports = filesMap;
957        }
958
959        function scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) {
960            if (isCreatingWatchedDirectory) {
961                // Watching directory is created
962                // Invalidate any resolution has failed lookup in this directory
963                (isInDirectoryChecks ||= new Set()).add(fileOrDirectoryPath);
964            }
965            else {
966                // If something to do with folder/file starting with "." in node_modules folder, skip it
967                const updatedPath = removeIgnoredPath(fileOrDirectoryPath);
968                if (!updatedPath) return false;
969                fileOrDirectoryPath = updatedPath;
970
971                // prevent saving an open file from over-eagerly triggering invalidation
972                if (resolutionHost.fileIsOpen(fileOrDirectoryPath)) {
973                    return false;
974                }
975
976                // Some file or directory in the watching directory is created
977                // Return early if it does not have any of the watching extension or not the custom failed lookup path
978                const dirOfFileOrDirectory = getDirectoryPath(fileOrDirectoryPath);
979                const isOHModules = isOhpm(resolutionHost.getCompilationSettings().packageManagerType);
980                if (isNodeModulesAtTypesDirectory(fileOrDirectoryPath) || (isOHModules && isOHModulesAtTypesDirectory(fileOrDirectoryPath)) ||
981                    isNodeModulesDirectory(fileOrDirectoryPath) || isNodeModulesAtTypesDirectory(dirOfFileOrDirectory) || (isOHModules &&
982                    isOHModulesAtTypesDirectory(dirOfFileOrDirectory)) || isNodeModulesDirectory(dirOfFileOrDirectory)) {
983                    // Invalidate any resolution from this directory
984                    (failedLookupChecks ||= new Set()).add(fileOrDirectoryPath);
985                    (startsWithPathChecks ||= new Set()).add(fileOrDirectoryPath);
986                }
987                else {
988                    if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) {
989                        return false;
990                    }
991                    // Ignore emits from the program
992                    if (isEmittedFileOfProgram(resolutionHost.getCurrentProgram(), fileOrDirectoryPath)) {
993                        return false;
994                    }
995                    // Resolution need to be invalidated if failed lookup location is same as the file or directory getting created
996                    (failedLookupChecks ||= new Set()).add(fileOrDirectoryPath);
997
998                    // If the invalidated file is from a node_modules package, invalidate everything else
999                    // in the package since we might not get notifications for other files in the package.
1000                    // This hardens our logic against unreliable file watchers.
1001                    const packagePath = parseModuleFromPath(fileOrDirectoryPath);
1002                    if (packagePath) (startsWithPathChecks ||= new Set()).add(packagePath as Path);
1003                }
1004            }
1005            resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations();
1006        }
1007
1008        function invalidateResolutionsOfFailedLookupLocations() {
1009            let invalidated = false;
1010            if (affectingPathChecksForFile) {
1011                resolutionHost.getCurrentProgram()?.getSourceFiles().forEach(f => {
1012                    if (some(f.packageJsonLocations, location => affectingPathChecksForFile!.has(location))) {
1013                        (filesWithInvalidatedResolutions ??= new Set()).add(f.path);
1014                        invalidated = true;
1015                    }
1016                });
1017                affectingPathChecksForFile = undefined;
1018            }
1019
1020            if (!failedLookupChecks && !startsWithPathChecks && !isInDirectoryChecks && !affectingPathChecks) {
1021                return invalidated;
1022            }
1023
1024            invalidated = invalidateResolutions(resolutionsWithFailedLookups, canInvalidateFailedLookupResolution) || invalidated;
1025            const packageJsonMap = moduleResolutionCache.getPackageJsonInfoCache().getInternalMap();
1026            if (packageJsonMap && (failedLookupChecks || startsWithPathChecks || isInDirectoryChecks)) {
1027                packageJsonMap.forEach((_value, path) => isInvalidatedFailedLookup(path) ? packageJsonMap.delete(path) : undefined);
1028            }
1029            failedLookupChecks = undefined;
1030            startsWithPathChecks = undefined;
1031            isInDirectoryChecks = undefined;
1032            invalidated = invalidateResolutions(resolutionsWithOnlyAffectingLocations, canInvalidatedFailedLookupResolutionWithAffectingLocation) || invalidated;
1033            affectingPathChecks = undefined;
1034            return invalidated;
1035        }
1036
1037        function canInvalidateFailedLookupResolution(resolution: ResolutionWithFailedLookupLocations) {
1038            if (canInvalidatedFailedLookupResolutionWithAffectingLocation(resolution)) return true;
1039            if (!failedLookupChecks && !startsWithPathChecks && !isInDirectoryChecks) return false;
1040            return resolution.failedLookupLocations.some(location => isInvalidatedFailedLookup(resolutionHost.toPath(location)));
1041        }
1042
1043        function isInvalidatedFailedLookup(locationPath: Path) {
1044            return failedLookupChecks?.has(locationPath) ||
1045                firstDefinedIterator(startsWithPathChecks?.keys() || emptyIterator, fileOrDirectoryPath => startsWith(locationPath, fileOrDirectoryPath) ? true : undefined) ||
1046                firstDefinedIterator(isInDirectoryChecks?.keys() || emptyIterator, fileOrDirectoryPath => isInDirectoryPath(fileOrDirectoryPath, locationPath) ? true : undefined);
1047        }
1048
1049        function canInvalidatedFailedLookupResolutionWithAffectingLocation(resolution: ResolutionWithFailedLookupLocations) {
1050            return !!affectingPathChecks && resolution.affectingLocations.some(location => affectingPathChecks!.has(location));
1051        }
1052
1053        function closeTypeRootsWatch() {
1054            clearMap(typeRootsWatches, closeFileWatcher);
1055        }
1056
1057        function getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot: string, typeRootPath: Path): Path | undefined {
1058            if (isInDirectoryPath(rootPath, typeRootPath)) {
1059                return rootPath;
1060            }
1061            const toWatch = getDirectoryToWatchFromFailedLookupLocationDirectory(typeRoot, typeRootPath);
1062            return toWatch && directoryWatchesOfFailedLookups.has(toWatch.dirPath) ? toWatch.dirPath : undefined;
1063        }
1064
1065        function createTypeRootsWatch(typeRootPath: Path, typeRoot: string): FileWatcher {
1066            // Create new watch and recursive info
1067            return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => {
1068                const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory);
1069                if (cachedDirectoryStructureHost) {
1070                    // Since the file existence changed, update the sourceFiles cache
1071                    cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
1072                }
1073
1074                // For now just recompile
1075                // We could potentially store more data here about whether it was/would be really be used or not
1076                // and with that determine to trigger compilation but for now this is enough
1077                hasChangedAutomaticTypeDirectiveNames = true;
1078                resolutionHost.onChangedAutomaticTypeDirectiveNames();
1079
1080                // Since directory watchers invoked are flaky, the failed lookup location events might not be triggered
1081                // So handle to failed lookup locations here as well to ensure we are invalidating resolutions
1082                const dirPath = getDirectoryToWatchFailedLookupLocationFromTypeRoot(typeRoot, typeRootPath);
1083                if (dirPath) {
1084                    scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath);
1085                }
1086            }, WatchDirectoryFlags.Recursive);
1087        }
1088
1089        /**
1090         * Watches the types that would get added as part of getAutomaticTypeDirectiveNames
1091         * To be called when compiler options change
1092         */
1093        function updateTypeRootsWatch() {
1094            const options = resolutionHost.getCompilationSettings();
1095            if (options.types) {
1096                // No need to do any watch since resolution cache is going to handle the failed lookups
1097                // for the types added by this
1098                closeTypeRootsWatch();
1099                return;
1100            }
1101
1102            // we need to assume the directories exist to ensure that we can get all the type root directories that get included
1103            // But filter directories that are at root level to say directory doesnt exist, so that we arent watching them
1104            const typeRoots = getEffectiveTypeRoots(options, { directoryExists: directoryExistsForTypeRootWatch, getCurrentDirectory });
1105            if (typeRoots) {
1106                mutateMap(
1107                    typeRootsWatches,
1108                    arrayToMap(typeRoots, tr => resolutionHost.toPath(tr)),
1109                    {
1110                        createNewValue: createTypeRootsWatch,
1111                        onDeleteValue: closeFileWatcher
1112                    }
1113                );
1114            }
1115            else {
1116                closeTypeRootsWatch();
1117            }
1118        }
1119
1120        /**
1121         * Use this function to return if directory exists to get type roots to watch
1122         * If we return directory exists then only the paths will be added to type roots
1123         * Hence return true for all directories except root directories which are filtered from watching
1124         */
1125        function directoryExistsForTypeRootWatch(nodeTypesDirectory: string) {
1126            const dir = getDirectoryPath(getDirectoryPath(nodeTypesDirectory));
1127            const dirPath = resolutionHost.toPath(dir);
1128            return dirPath === rootPath || canWatchDirectoryOrFile(dirPath);
1129        }
1130    }
1131
1132    function resolutionIsSymlink(resolution: ResolutionWithFailedLookupLocations) {
1133        return !!(
1134            (resolution as ResolvedModuleWithFailedLookupLocations).resolvedModule?.originalPath ||
1135            (resolution as ResolvedTypeReferenceDirectiveWithFailedLookupLocations).resolvedTypeReferenceDirective?.originalPath
1136        );
1137    }
1138}
1139