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