import { clearMap, closeFileWatcherOf, CompilerOptions, createModuleResolutionCache, createTypeReferenceDirectiveResolutionCache, Debug, directorySeparator, endsWith, fileExtensionIs, FileWatcher, firstDefinedIterator, forEachKey, GetCanonicalFileName, getDirectoryPath, getNormalizedAbsolutePath, getOptionsForLibraryResolution, getPathComponents, getPathFromPathComponents, identity, ignoredPaths, isDiskPathRoot, isEmittedFileOfProgram, isNodeModulesDirectory, isResolvedWithGlobalCachePass, isResolvedWithGlobalCachePassButStillUnresolved, isRootedDiskPath, memoize, ModuleOrTypeReferenceResolutionCache, ModuleResolutionCache, noopFileWatcher, normalizePath, parseNodeModuleFromPath, Path, PathPathComponents, removeSuffix, ResolutionCache, ResolutionWithResolvedFileName, ResolvedModuleWithFailedLookupLocations, ResolvedProjectReference, ResolvedTypeReferenceDirectiveWithFailedLookupLocations, returnTrue, RootDirInfo, some, startsWith, tryAddToSet, TypeReferenceDirectiveResolutionCache, WatchDirectoryFlags, } from "./_namespaces/ts.js"; declare module "./_namespaces/ts.js" { /** @internal */ export interface ModuleOrTypeReferenceResolutionCache { sharedCache?: ModuleOrTypeReferenceResolutionCache; } } /** * This is the cache of module/typedirectives resolution that are shared across projects * * @internal */ export interface SharedResolutionCache { sharedCacheHost: SharedResolutionCacheHost; moduleResolutionCache: ModuleResolutionCache; typeReferenceDirectiveResolutionCache: TypeReferenceDirectiveResolutionCache; libraryResolutionCache: ModuleResolutionCache; resolvedFileToResolution: Map>; resolutionsWithFailedLookups: Set; packageJsonRefCount: Map; resolutionsWithOnlyAffectingLocations: Set; watchedResolutionInfoMap: Map; typeRootsWatches: Map; inUseResolutionCaches: RefCountCache; cacheToOptions: Map>; directoryWatchesOfFailedLookups: Map; nonRecursiveDirectoryWatchesOfFailedLookups: Map; fileWatchesOfAffectingLocations: Map; packageDirWatchers: Map; dirPathToSymlinkPackageRefCount: Map; clear(cache: ResolutionCache): void; startCachingPerDirectoryResolution(cache: ResolutionCache): void; finishCachingPerDirectoryResolution(): void; compactCaches(availableOptions: Set, cache: ResolutionCache): void; watchResolution( resolution: T, getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, redirectedReference: ResolvedProjectReference | undefined, ): void; releaseResolution( resolution: ResolutionWithFailedLookupLocations, moduleOrTypeRefCache: ModuleOrTypeReferenceResolutionCache, ): void; createFileWatcherOfAffectingLocation(affectingLocation: string): void; releaseFileWatcherOfAffectingLocation(location: string): void; closeFileWatcherOfAffectingLocation(path: string): void; addToPotentiallyUnwatchedPackageJsons(location: string): void; invalidateResolutionsOfFailedLookupLocations(): boolean; invalidateResolutionOfFile(filePath: Path): boolean; invalidateResolution( resolution: ResolutionWithFailedLookupLocations, canInvalidate: (resolution: ResolutionWithFailedLookupLocations) => boolean | undefined, ): void; createTypeRootsWatch(typeRoot: string, cache: ResolutionCache): FileWatcher; currentCache(): ResolutionCache | undefined; } /** @internal */ export interface WatchedResolutionInfo { isInvalidated?: boolean; // Files that have this resolution using caches: Set; watchedFailed?: number; watchedAffected?: number; dirWatches?: Set; nonRecursiveDirWatches?: Set; packageDirWatchers?: Map>; rootDirInfo?: RootDirInfo; } /** @internal */ export type ResolutionWithFailedLookupLocations = | ResolvedModuleWithFailedLookupLocations | ( & ResolvedTypeReferenceDirectiveWithFailedLookupLocations // Just so we can use this directly. These any ways are optional properties & Pick ); /** @internal */ export interface SharedResolutionCacheHost { getCurrentDirectory(): string; toPath(fileName: string): Path; getCanonicalFileName: GetCanonicalFileName; preferNonRecursiveWatch: boolean | undefined; fileIsOpen(filePath: Path): boolean; } /** @internal */ export type RefCountCache = Map; /** @internal */ export interface FileWatcherOfAffectingLocation { /** watcher for the lookup */ watcher: FileWatcher; resolutions: RefCountCache | undefined; files: RefCountCache | undefined; symlinks: Set | undefined; } /** @internal */ export interface DirectoryWatchesOfFailedLookup { /** watcher for the lookup */ watcher: FileWatcher; /** ref count keeping this watch alive */ refCount: RefCountCache; } /** @internal */ export interface DirPathToWatcherOfPackageDirWatcher { watcher: DirectoryWatchesOfFailedLookup; refCount: RefCountCache; } /** @internal */ export interface PackageDirWatcher { dirPathToWatcher: Map; isSymlink: boolean; } /** @internal */ export interface TypeRootWatch { watcher: FileWatcher; refCount: Set; } /** @internal */ export interface DirectoryOfFailedLookupWatch { dir: string; dirPath: Path; nonRecursive?: boolean; packageDir?: string; packageDirPath?: Path; } /** @internal */ export function removeIgnoredPath(path: Path): Path | undefined { // Consider whole staging folder as if node_modules changed. if (endsWith(path, "/node_modules/.staging")) { return removeSuffix(path, "/.staging") as Path; } return some(ignoredPaths, searchPath => path.includes(searchPath)) ? undefined : path; } function perceivedOsRootLengthForWatching(pathComponents: Readonly, length: number) { // Ignore "/", "c:/" if (length <= 1) return 1; let indexAfterOsRoot = 1; let isDosStyle = pathComponents[0].search(/[a-z]:/i) === 0; if ( pathComponents[0] !== directorySeparator && !isDosStyle && // Non dos style paths pathComponents[1].search(/[a-z]\$$/i) === 0 // Dos style nextPart ) { // ignore "//vda1cs4850/c$/folderAtRoot" if (length === 2) return 2; indexAfterOsRoot = 2; isDosStyle = true; } if ( isDosStyle && !pathComponents[indexAfterOsRoot].match(/^users$/i) ) { // Paths like c:/notUsers return indexAfterOsRoot; } if (pathComponents[indexAfterOsRoot].match(/^workspaces$/i)) { // Paths like: /workspaces as codespaces hoist the repos in /workspaces so we have to exempt these from "2" level from root rule return indexAfterOsRoot + 1; } // Paths like: c:/users/username or /home/username return indexAfterOsRoot + 2; } /** * Filter out paths like * "/", "/user", "/user/username", "/user/username/folderAtRoot", * "c:/", "c:/users", "c:/users/username", "c:/users/username/folderAtRoot", "c:/folderAtRoot" * @param dirPath * * @internal */ export function canWatchDirectoryOrFile(pathComponents: Readonly, length?: number): boolean { if (length === undefined) length = pathComponents.length; // Ignore "/", "c:/" // ignore "/user", "c:/users" or "c:/folderAtRoot" if (length <= 2) return false; const perceivedOsRootLength = perceivedOsRootLengthForWatching(pathComponents, length); return length > perceivedOsRootLength + 1; } /** @internal */ export function canWatchDirectoryOrFilePath(path: Path): boolean { return canWatchDirectoryOrFile(getPathComponents(path)); } /** @internal */ export function canWatchAtTypes(atTypes: Path): boolean { // Otherwise can watch directory only if we can watch the parent directory of node_modules/@types return canWatchAffectedPackageJsonOrNodeModulesOfAtTypes(getDirectoryPath(atTypes)); } function isInDirectoryPath(dirComponents: Readonly, fileOrDirComponents: Readonly) { if (fileOrDirComponents.length < dirComponents.length) return false; for (let i = 0; i < dirComponents.length; i++) { if (fileOrDirComponents[i] !== dirComponents[i]) return false; } return true; } function canWatchAffectedPackageJsonOrNodeModulesOfAtTypes(fileOrDirPath: Path) { return canWatchDirectoryOrFilePath(fileOrDirPath); } /** @internal */ export function canWatchAffectingLocation(filePath: Path): boolean { return canWatchAffectedPackageJsonOrNodeModulesOfAtTypes(filePath); } /** @internal */ export function getDirectoryToWatchFailedLookupLocation( failedLookupLocation: string, failedLookupLocationPath: Path, rootDir: string, rootPath: Path, rootPathComponents: Readonly, isRootWatchable: boolean, getCurrentDirectory: () => string | undefined, preferNonRecursiveWatch: boolean | undefined, ): DirectoryOfFailedLookupWatch | undefined { const failedLookupPathComponents: Readonly = getPathComponents(failedLookupLocationPath); // Ensure failed look up is normalized path failedLookupLocation = isRootedDiskPath(failedLookupLocation) ? normalizePath(failedLookupLocation) : getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory()); const failedLookupComponents: readonly string[] = getPathComponents(failedLookupLocation); const perceivedOsRootLength = perceivedOsRootLengthForWatching(failedLookupPathComponents, failedLookupPathComponents.length); if (failedLookupPathComponents.length <= perceivedOsRootLength + 1) return undefined; // If directory path contains node module, get the most parent node_modules directory for watching const nodeModulesIndex = failedLookupPathComponents.indexOf("node_modules" as Path); if (nodeModulesIndex !== -1 && nodeModulesIndex + 1 <= perceivedOsRootLength + 1) return undefined; // node_modules not at position where it can be watched const lastNodeModulesIndex = failedLookupPathComponents.lastIndexOf("node_modules" as Path); if (isRootWatchable && isInDirectoryPath(rootPathComponents, failedLookupPathComponents)) { if (failedLookupPathComponents.length > rootPathComponents.length + 1) { // Instead of watching root, watch directory in root to avoid watching excluded directories not needed for module resolution return getDirectoryOfFailedLookupWatch( failedLookupComponents, failedLookupPathComponents, Math.max(rootPathComponents.length + 1, perceivedOsRootLength + 1), lastNodeModulesIndex, ); } else { // Always watch root directory non recursively return { dir: rootDir, dirPath: rootPath, nonRecursive: true, }; } } return getDirectoryToWatchFromFailedLookupLocationDirectory( failedLookupComponents, failedLookupPathComponents, failedLookupPathComponents.length - 1, perceivedOsRootLength, nodeModulesIndex, rootPathComponents, lastNodeModulesIndex, preferNonRecursiveWatch, ); } function getDirectoryToWatchFromFailedLookupLocationDirectory( dirComponents: readonly string[], dirPathComponents: Readonly, dirPathComponentsLength: number, perceivedOsRootLength: number, nodeModulesIndex: number, rootPathComponents: Readonly, lastNodeModulesIndex: number, preferNonRecursiveWatch: boolean | undefined, ): DirectoryOfFailedLookupWatch | undefined { // If directory path contains node module, get the most parent node_modules directory for watching if (nodeModulesIndex !== -1) { // If the directory is node_modules use it to watch, always watch it recursively return getDirectoryOfFailedLookupWatch( dirComponents, dirPathComponents, nodeModulesIndex + 1, lastNodeModulesIndex, ); } // Use some ancestor of the root directory let nonRecursive = true; let length = dirPathComponentsLength; if (!preferNonRecursiveWatch) { for (let i = 0; i < dirPathComponentsLength; i++) { if (dirPathComponents[i] !== rootPathComponents[i]) { nonRecursive = false; length = Math.max(i + 1, perceivedOsRootLength + 1); break; } } } return getDirectoryOfFailedLookupWatch( dirComponents, dirPathComponents, length, lastNodeModulesIndex, nonRecursive, ); } function getDirectoryOfFailedLookupWatch( dirComponents: readonly string[], dirPathComponents: Readonly, length: number, lastNodeModulesIndex: number, nonRecursive?: boolean, ): DirectoryOfFailedLookupWatch { let packageDirLength; if (lastNodeModulesIndex !== -1 && lastNodeModulesIndex + 1 >= length && lastNodeModulesIndex + 2 < dirPathComponents.length) { if (!startsWith(dirPathComponents[lastNodeModulesIndex + 1], "@")) { packageDirLength = lastNodeModulesIndex + 2; } else if (lastNodeModulesIndex + 3 < dirPathComponents.length) { packageDirLength = lastNodeModulesIndex + 3; } } return { dir: getPathFromPathComponents(dirComponents, length), dirPath: getPathFromPathComponents(dirPathComponents, length), nonRecursive, packageDir: packageDirLength !== undefined ? getPathFromPathComponents(dirComponents, packageDirLength) : undefined, packageDirPath: packageDirLength !== undefined ? getPathFromPathComponents(dirPathComponents, packageDirLength) : undefined, }; } /** @internal */ export type GetResolutionWithResolvedFileName = (resolution: T) => R | undefined; function getModuleOrTypeRefResolved(resolution: ResolutionWithFailedLookupLocations) { return (resolution as ResolvedModuleWithFailedLookupLocations).resolvedModule ?? (resolution as ResolvedTypeReferenceDirectiveWithFailedLookupLocations).resolvedTypeReferenceDirective; } /** @internal */ export function createSharedResolutionCache(sharedCacheHost: SharedResolutionCacheHost): SharedResolutionCache { const resolutionsWithFailedLookups = new Set(); const resolutionsWithOnlyAffectingLocations = new Set(); const resolvedFileToResolution = new Map>(); const impliedFormatPackageJsons = new Map(); const watchedResolutionInfoMap = new Map(); const typeRootsWatches = new Map(); const inUseResolutionCaches = new Map(); const cacheToOptions = new Map>(); let affectingPathChecks: Set | undefined; let failedLookupChecks: Set | undefined; let startsWithPathChecks: Set | undefined; let isInDirectoryChecks: Set | undefined; let cachesNeedingGc: Set> | undefined; let potentiallyUnreferencedDirWatchers: Set | undefined; let considerNonWatchedResolutionAsInvalidated = false; const getCurrentDirectory = memoize(() => sharedCacheHost.getCurrentDirectory()); const moduleResolutionCache = createModuleResolutionCache( getCurrentDirectory(), sharedCacheHost.getCanonicalFileName, /*options*/ undefined, /*packageJsonInfoCache*/ undefined, /*optionsToRedirectsKey*/ undefined, getValidModuleResolution, ); const typeReferenceDirectiveResolutionCache: TypeReferenceDirectiveResolutionCache = createTypeReferenceDirectiveResolutionCache( getCurrentDirectory(), sharedCacheHost.getCanonicalFileName, /*options*/ undefined, moduleResolutionCache.getPackageJsonInfoCache(), moduleResolutionCache.optionsToRedirectsKey, getValidResolution, ); const libraryResolutionCache = createModuleResolutionCache( getCurrentDirectory(), sharedCacheHost.getCanonicalFileName, getOptionsForLibraryResolution(/*options*/ undefined), moduleResolutionCache.getPackageJsonInfoCache(), /*optionsToRedirectsKey*/ undefined, getValidResolution, ); const packageJsonRefCount = new Map(); let potentiallyUnwatchedPackageJsons: Set | undefined; const directoryWatchesOfFailedLookups = new Map(); const nonRecursiveDirectoryWatchesOfFailedLookups = new Map(); const fileWatchesOfAffectingLocations = new Map(); const isSymlinkCache = new Map(); const packageDirWatchers = new Map(); // Watching packageDir if symlink otherwise watching dirPath const dirPathToSymlinkPackageRefCount = new Map(); // Refcount for dirPath watches when watching symlinked packageDir let currentCache: ResolutionCache | undefined; return { sharedCacheHost, moduleResolutionCache, typeReferenceDirectiveResolutionCache, libraryResolutionCache, resolvedFileToResolution, resolutionsWithFailedLookups, resolutionsWithOnlyAffectingLocations, packageJsonRefCount, watchedResolutionInfoMap, typeRootsWatches, inUseResolutionCaches, cacheToOptions, directoryWatchesOfFailedLookups, nonRecursiveDirectoryWatchesOfFailedLookups, fileWatchesOfAffectingLocations, packageDirWatchers, dirPathToSymlinkPackageRefCount, clear, startCachingPerDirectoryResolution, finishCachingPerDirectoryResolution, compactCaches, watchResolution, releaseResolution, createFileWatcherOfAffectingLocation, releaseFileWatcherOfAffectingLocation, closeFileWatcherOfAffectingLocation, addToPotentiallyUnwatchedPackageJsons, invalidateResolutionsOfFailedLookupLocations, invalidateResolutionOfFile, invalidateResolution, createTypeRootsWatch, currentCache: () => currentCache, }; function clearForCache(cache: ResolutionCache) { let gcCaches = false; currentCache = cache; watchedResolutionInfoMap.forEach((watchedResolutionInfo, resolution) => { if (watchedResolutionInfo.caches.has(cache)) { gcCaches = releaseResolution(resolution, /*moduleOrTypeRefCache*/ undefined) || gcCaches; } }); fileWatchesOfAffectingLocations.forEach((watcher, location) => { if (watcher.files?.delete(cache)) closeFileWatcherOfAffectingLocation(location, watcher); }); // Skip handling typeRoots as they would be cleared before calling this method inUseResolutionCaches.delete(cache); if (gcCaches) { gcModuleOrTypeRefCache(moduleResolutionCache); gcModuleOrTypeRefCache(typeReferenceDirectiveResolutionCache); gcModuleOrTypeRefCache(libraryResolutionCache); } currentCache = undefined; if (cacheToOptions.delete(cache)) compactCachesWoker(); } function clear(cache: ResolutionCache) { if (!inUseResolutionCaches.has(cache)) return; if (inUseResolutionCaches.size !== 1) return clearForCache(cache); currentCache = undefined; inUseResolutionCaches.clear(); cacheToOptions.clear(); cachesNeedingGc = undefined; potentiallyUnwatchedPackageJsons = undefined; clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf); clearMap(nonRecursiveDirectoryWatchesOfFailedLookups, closeFileWatcherOf); clearMap(fileWatchesOfAffectingLocations, closeFileWatcherOf); clearMap(typeRootsWatches, closeFileWatcherOf); packageJsonRefCount.clear(); isSymlinkCache.clear(); packageDirWatchers.clear(); dirPathToSymlinkPackageRefCount.clear(); resolvedFileToResolution.clear(); resolutionsWithFailedLookups.clear(); resolutionsWithOnlyAffectingLocations.clear(); watchedResolutionInfoMap.clear(); failedLookupChecks = undefined; startsWithPathChecks = undefined; isInDirectoryChecks = undefined; affectingPathChecks = undefined; moduleResolutionCache.clear(); typeReferenceDirectiveResolutionCache.clear(); libraryResolutionCache.clear(); impliedFormatPackageJsons.clear(); } function startCachingPerDirectoryResolution(cache: ResolutionCache) { currentCache = cache; moduleResolutionCache.isReadonly = undefined; typeReferenceDirectiveResolutionCache.isReadonly = undefined; libraryResolutionCache.isReadonly = undefined; moduleResolutionCache.getPackageJsonInfoCache().isReadonly = undefined; isSymlinkCache.clear(); } function finishCachingPerDirectoryResolution() { // These are only dir watchers that were potentially removed because packageDir symlink status changed while watching resolutions potentiallyUnreferencedDirWatchers?.forEach(path => closeDirectoryWatchesOfFailedLookup( getDirectoryWatchesOfFailedLookup(path, /*nonRecursive*/ false), path, /*nonRecursive*/ false, ) ); potentiallyUnreferencedDirWatchers = undefined; cachesNeedingGc?.forEach(gcModuleOrTypeRefCache); cachesNeedingGc = undefined; potentiallyUnwatchedPackageJsons?.forEach(releasePotentiallyUnwatchedPackageJson); potentiallyUnwatchedPackageJsons = undefined; moduleResolutionCache.isReadonly = true; typeReferenceDirectiveResolutionCache.isReadonly = true; libraryResolutionCache.isReadonly = true; moduleResolutionCache.getPackageJsonInfoCache().isReadonly = true; isSymlinkCache.clear(); currentCache = undefined; } function compactCaches(availableOptions: Set, cache: ResolutionCache) { if (availableOptions.size) cacheToOptions.set(cache, availableOptions); else cacheToOptions.delete(cache); compactCachesWoker(); } function compactCachesWoker() { let availableOptions: Set; if (cacheToOptions.size === 1) availableOptions = firstDefinedIterator(cacheToOptions.values(), identity)!; else { availableOptions = new Set(); cacheToOptions.forEach(setOfOptions => setOfOptions.forEach(options => availableOptions.add(options))); } moduleResolutionCache.compact(availableOptions, /*skipOptionsToRedirectsKeyCleanup*/ true); typeReferenceDirectiveResolutionCache.compact(availableOptions); libraryResolutionCache.compact(); } function gcModuleOrTypeRefCache( cache: ModuleOrTypeReferenceResolutionCache, ) { considerNonWatchedResolutionAsInvalidated = true; cache.gc(watchedResolutionInfoMap); considerNonWatchedResolutionAsInvalidated = false; } function getValidResolution(resolution: T | undefined) { return isInvalidatedResolution(resolution) ? undefined : resolution; } function getValidModuleResolution(resolution: ResolvedModuleWithFailedLookupLocations | undefined, forSet?: boolean) { resolution = getValidResolution(resolution); return resolution && ( forSet ? resolution.globalCacheResolution && isInvalidatedResolution(resolution.globalCacheResolution.globalResult) ? undefined : resolution : currentCache?.resolutionHost.getGlobalTypingsCacheLocation?.() && resolution?.globalCacheResolution?.globalResult ? currentCache.getValidResolution(resolution.globalCacheResolution.globalResult) : resolution ); } function isInvalidatedResolution(resolution: ResolutionWithFailedLookupLocations | undefined) { return !resolution || (considerNonWatchedResolutionAsInvalidated && !watchedResolutionInfoMap.has(resolution)) || watchedResolutionInfoMap.get(resolution)?.isInvalidated; } function isNodeModulesAtTypesDirectory(dirPath: Path) { return endsWith(dirPath, "/node_modules/@types"); } function watchResolution( resolution: T, getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, redirectedReference: ResolvedProjectReference | undefined, ) { let watchedResolutionInfo = watchedResolutionInfoMap.get(resolution); if (!watchedResolutionInfo?.caches.has(currentCache!)) addRefCountToCacheMap(inUseResolutionCaches, currentCache!); if (!watchedResolutionInfo?.caches.size) { if (!watchedResolutionInfo) { watchedResolutionInfoMap.set(resolution, watchedResolutionInfo = { caches: new Set() }); if (isResolvedWithGlobalCachePass(resolution) && !watchedResolutionInfoMap.has(resolution.globalCacheResolution.primary)) { watchedResolutionInfoMap.set(resolution.globalCacheResolution.primary, { caches: new Set() }); } } if (isResolvedWithGlobalCachePassButStillUnresolved(resolution)) { // Add to potentially unreferenced resolutions resolution.globalCacheResolution.globalResolution.failedLookupLocations?.forEach( addToPotentiallyUnwatchedPackageJsonsIfPackageJson, ); if (resolution.globalCacheResolution.globalResolution.alternateResult) addToPotentiallyUnwatchedPackageJsonsIfPackageJson(resolution.globalCacheResolution.globalResolution.alternateResult); resolution.globalCacheResolution.globalResolution.affectingLocations?.forEach(addToPotentiallyUnwatchedPackageJsons); } const resolved = getResolutionWithResolvedFileName(resolution); if (resolved && resolved.resolvedFileName) { const key = sharedCacheHost.toPath(resolved.resolvedFileName); let resolutions = resolvedFileToResolution.get(key); if (!resolutions) resolvedFileToResolution.set(key, resolutions = new Set()); resolutions.add(resolution); } } watchFailedLookupLocationOfResolution(resolution, redirectedReference, watchedResolutionInfo); watchAffectingLocationsOfResolution(resolution, watchedResolutionInfo); watchedResolutionInfo.caches.add(currentCache!); // If this resolution is primary if (isResolvedWithGlobalCachePass(resolution)) { // Update to global cache pass resolution watchPrimaryOrGlobalResultFailedAndAffectedLookups(resolution.globalCacheResolution.primary); } else if (resolution.globalCacheResolution?.globalResult) { // Update to global cache pass resolution watchPrimaryOrGlobalResultFailedAndAffectedLookups(resolution.globalCacheResolution.globalResult); } } function watchPrimaryOrGlobalResultFailedAndAffectedLookups(primaryOrGlobalResult: ResolvedModuleWithFailedLookupLocations) { const watchInfo = watchedResolutionInfoMap.get(primaryOrGlobalResult); if (!watchInfo?.caches.size) return; const first = firstDefinedIterator(watchInfo.caches, identity)!; const savedCurrentCache = currentCache; currentCache = first; watchFailedLookupLocationOfResolution(primaryOrGlobalResult, /*redirectedReference*/ undefined, watchInfo); watchAffectingLocationsOfResolution(primaryOrGlobalResult, watchInfo); currentCache = savedCurrentCache; } function watchFailedLookupLocation( failedLookupLocation: string, resolution: ResolutionWithFailedLookupLocations, redirectedReference: ResolvedProjectReference | undefined, watchedResolutionInfo: WatchedResolutionInfo, ) { watchedResolutionInfo.rootDirInfo ??= currentCache!.getRootDirInfoForResolution(redirectedReference, resolution); const failedLookupLocationPath = sharedCacheHost.toPath(failedLookupLocation); if (endsWith(failedLookupLocationPath, "/package.json")) addRefToPackageJson(failedLookupLocationPath); const toWatch = getDirectoryToWatchFailedLookupLocation( failedLookupLocation, failedLookupLocationPath, watchedResolutionInfo.rootDirInfo.rootDir, watchedResolutionInfo.rootDirInfo.rootPath, watchedResolutionInfo.rootDirInfo.rootPathComponents, watchedResolutionInfo.rootDirInfo.canWatch, getCurrentDirectory, sharedCacheHost.preferNonRecursiveWatch, ); if (!toWatch) return; const { dir, dirPath, nonRecursive, packageDir, packageDirPath } = toWatch; if (!packageDirPath || !currentCache!.resolutionHost.realpath) { if (!(!nonRecursive ? watchedResolutionInfo.dirWatches : watchedResolutionInfo.nonRecursiveDirWatches)?.has(dirPath)) { const dirWatcher = createOrAddRefToDirectoryWatchOfFailedLookups(dir, dirPath, nonRecursive, currentCache!); (!nonRecursive ? watchedResolutionInfo.dirWatches ??= new Set() : watchedResolutionInfo.nonRecursiveDirWatches ??= new Set()).add(dirPath); addRefCountToMapForCaches(dirWatcher.refCount, watchedResolutionInfo.caches, currentCache); } } else { Debug.assert(!nonRecursive); const forDirPath = watchedResolutionInfo.packageDirWatchers?.get(packageDirPath); if (!forDirPath?.has(dirPath)) { const packageDirWatcher = createDirectoryWatcherForPackageDir(dir, dirPath, packageDir!, packageDirPath); if (forDirPath) forDirPath.add(dirPath); else (watchedResolutionInfo.packageDirWatchers ??= new Map()).set(packageDirPath, new Set([dirPath])); forRefCountCaches(watchedResolutionInfo.caches, cache => addRefToDirPathWatcherOfPackageDir(packageDirWatcher, dirPath, cache), currentCache); } } } function addRefToPackageJson(path: Path) { addRefCountToCacheMap(packageJsonRefCount, path); } function releasePackageJsonCachePath(path: Path) { moduleResolutionCache.getPackageJsonInfoCache().getInternalMap()?.delete(path); packageJsonRefCount.delete(path); } function addToPotentiallyUnwatchedPackageJsons(location: string) { (potentiallyUnwatchedPackageJsons ??= new Set()).add(sharedCacheHost.toPath(location)); } function addToPotentiallyUnwatchedPackageJsonsIfPackageJson(location: string) { if (endsWith(location, "/package.json")) addToPotentiallyUnwatchedPackageJsons(location); } function releasePotentiallyUnwatchedPackageJson(path: Path) { if (!packageJsonRefCount.has(path)) moduleResolutionCache.getPackageJsonInfoCache().getInternalMap()?.delete(path); } function releasePackageJson(path: Path) { const existing = packageJsonRefCount.get(path)!; if (existing !== 1) packageJsonRefCount.set(path, existing - 1); else releasePackageJsonCachePath(path); } function releaseIfPackageJson(failedLookupLocation: string) { if (endsWith(failedLookupLocation, "/package.json")) releasePackageJson(sharedCacheHost.toPath(failedLookupLocation)); } function watchFailedLookupLocationOfResolution( resolution: ResolutionWithFailedLookupLocations, redirectedReference: ResolvedProjectReference | undefined, watchedResolutionInfo: WatchedResolutionInfo, ) { // Existing watches need to be ref counted for this cache if (!watchedResolutionInfo.caches.has(currentCache!)) { watchedResolutionInfo.dirWatches?.forEach(dirPath => addRefCountToCacheMap(getDirectoryWatchesOfFailedLookup(dirPath, /*nonRecursive*/ false)!.refCount, currentCache!)); watchedResolutionInfo.nonRecursiveDirWatches?.forEach(dirPath => addRefCountToCacheMap(getDirectoryWatchesOfFailedLookup(dirPath, /*nonRecursive*/ true)!.refCount, currentCache!)); watchedResolutionInfo.packageDirWatchers?.forEach((dirPaths, packageDirPath) => { const packageDirWatcher = packageDirWatchers.get(packageDirPath)!; dirPaths.forEach(dirPath => addRefToDirPathWatcherOfPackageDir(packageDirWatcher, dirPath, currentCache!)); }); } // There have to be failed lookup locations if there is alternateResult so storing failedLookupLocation length is good enough, // alternateResult doesnt change later only failed lookup locations get added on if (watchedResolutionInfo.watchedFailed === resolution.failedLookupLocations?.length) return; if (!watchedResolutionInfo.watchedFailed) { resolutionsWithFailedLookups.add(resolution); if (watchedResolutionInfo.watchedAffected) resolutionsWithOnlyAffectingLocations.delete(resolution); } for (let i = watchedResolutionInfo.watchedFailed || 0; i < resolution.failedLookupLocations!.length; i++) { watchFailedLookupLocation( resolution.failedLookupLocations![i], resolution, redirectedReference, watchedResolutionInfo, ); } if (!watchedResolutionInfo.watchedFailed && resolution.alternateResult) { watchFailedLookupLocation( resolution.alternateResult, resolution, redirectedReference, watchedResolutionInfo, ); } watchedResolutionInfo.watchedFailed = resolution.failedLookupLocations?.length; } function createDirectoryWatcherForPackageDir( dir: string, dirPath: Path, packageDir: string, packageDirPath: Path, ) { // Check if this is symlink: let isSymlink = isSymlinkCache.get(packageDirPath); let packageDirWatcher = packageDirWatchers.get(packageDirPath); if (isSymlink === undefined) { const realPath = currentCache!.resolutionHost.realpath!(packageDir); isSymlink = realPath !== packageDir && sharedCacheHost.toPath(realPath) !== packageDirPath; isSymlinkCache.set(packageDirPath, isSymlink); if (!packageDirWatcher) { packageDirWatchers.set( packageDirPath, packageDirWatcher = { dirPathToWatcher: new Map(), isSymlink, }, ); } else if (packageDirWatcher.isSymlink !== isSymlink) { // Handle the change packageDirWatcher.dirPathToWatcher.forEach((watcher, dirPath, map) => { // Do not close the watcher yet since it might be needed by other failed lookup locations. releaseRefCountFromMapForCaches(watcher.watcher.refCount, watcher.refCount); (potentiallyUnreferencedDirWatchers ??= new Set()).add(packageDirWatcher!.isSymlink ? packageDirPath : dirPath); const firstCache = firstDefinedIterator(watcher.refCount.keys(), identity); if (firstCache) { watcher.watcher = createDirPathToWatcher(firstCache); addRefCountToMapForCaches(watcher.watcher.refCount, watcher.refCount, firstCache); } else { // Unused, remove it map.delete(dirPath); } }); packageDirWatcher.isSymlink = isSymlink; } } else { Debug.assertIsDefined(packageDirWatcher); Debug.assert(isSymlink === packageDirWatcher.isSymlink); } if (packageDirWatcher.dirPathToWatcher.has(dirPath)) { addRefToDirPathWatcherOfPackageDir(packageDirWatcher, dirPath, currentCache!); } else { packageDirWatcher.dirPathToWatcher.set(dirPath, { watcher: createDirPathToWatcher(currentCache!), refCount: new Map([[currentCache!, 1]]), }); if (isSymlink) addRefCountToCacheMap(dirPathToSymlinkPackageRefCount, dirPath); } return packageDirWatcher; function createDirPathToWatcher(cache: ResolutionCache) { return isSymlink ? createOrAddRefToDirectoryWatchOfFailedLookups(packageDir, packageDirPath, /*nonRecursive*/ false, cache) : createOrAddRefToDirectoryWatchOfFailedLookups(dir, dirPath, /*nonRecursive*/ false, cache); } } function addRefToDirPathWatcherOfPackageDir(packageDirWatcher: PackageDirWatcher, dirPath: Path, cache: ResolutionCache) { const forDirPath = packageDirWatcher.dirPathToWatcher.get(dirPath)!; // If this is the first time cache is added for this dirPath, add cache refcount to watcher as well if (!forDirPath.refCount.has(cache)) addRefCountToCacheMap(forDirPath.watcher.refCount, cache); addRefCountToCacheMap(forDirPath.refCount, cache); } function removeRefToDirPathWatcherOfPackageDir(packageDirPath: Path, dirPath: Path) { const packageDirWatcher = packageDirWatchers.get(packageDirPath); if (!packageDirWatcher) return; const forDirPath = packageDirWatcher.dirPathToWatcher.get(dirPath); if (!forDirPath) return; releaseRefCountFromMap(forDirPath.refCount, currentCache!); if (forDirPath.refCount.has(currentCache!)) return; // Release the watcher refcount releaseRefCountFromMap(forDirPath.watcher.refCount, currentCache!); if (forDirPath.refCount.size !== 0) return; closeDirectoryWatchesOfFailedLookup(forDirPath.watcher, packageDirWatcher.isSymlink ? packageDirPath : dirPath, /*nonRecursive*/ false); packageDirWatcher.dirPathToWatcher.delete(dirPath); if (packageDirWatcher.isSymlink) releaseRefCountFromMap(dirPathToSymlinkPackageRefCount, dirPath); if (packageDirWatcher.dirPathToWatcher.size === 0) { packageDirWatchers.delete(packageDirPath); } } function getDirectoryWatchesOfFailedLookup(dirPath: Path, nonRecursive: boolean | undefined) { const watchMap = !nonRecursive ? directoryWatchesOfFailedLookups : nonRecursiveDirectoryWatchesOfFailedLookups; return watchMap.get(dirPath); } function createOrAddRefToDirectoryWatchOfFailedLookups( dir: string, dirPath: Path, nonRecursive: boolean | undefined, cache: ResolutionCache, ) { const watchMap = !nonRecursive ? directoryWatchesOfFailedLookups : nonRecursiveDirectoryWatchesOfFailedLookups; let dirWatcher = watchMap.get(dirPath); if (dirWatcher) { addRefCountToCacheMap(dirWatcher.refCount, cache); } else { watchMap.set( dirPath, dirWatcher = { watcher: cache.resolutionHost.watchDirectoryOfFailedLookupLocation( dir, fileOrDirectory => onDirectoryWatcher(dirPath, nonRecursive, fileOrDirectory), nonRecursive ? WatchDirectoryFlags.None : WatchDirectoryFlags.Recursive, ), refCount: new Map([[cache, 1]]), }, ); } return dirWatcher; } function removeDirectoryWatcher(dirPath: Path, nonRecursive: boolean, forCaches: Set | RefCountCache) { const dirWatcher = getDirectoryWatchesOfFailedLookup(dirPath, nonRecursive); // Do not close the watcher yet since it might be needed by other failed lookup locations. if (dirWatcher) releaseRefCountFromMapForCaches(dirWatcher.refCount, forCaches); closeDirectoryWatchesOfFailedLookup(dirWatcher, dirPath, nonRecursive); } function closeDirectoryWatchesOfFailedLookup(watcher: DirectoryWatchesOfFailedLookup | undefined, path: Path, nonRecursive: boolean | undefined) { if (watcher && watcher.refCount.size === 0) { if (!nonRecursive) directoryWatchesOfFailedLookups.delete(path); else nonRecursiveDirectoryWatchesOfFailedLookups.delete(path); watcher.watcher.close(); } } function onDirectoryWatcher(dirPath: Path, nonRecursive: boolean | undefined, fileOrDirectory: string, fileOrDirectoryPath?: Path) { const refCountCache = getDirectoryWatchesOfFailedLookup(dirPath, nonRecursive)?.refCount; if (!refCountCache) return; fileOrDirectoryPath ??= sharedCacheHost.toPath(fileOrDirectory); refCountCache.forEach((_refCount, cache) => { // Since the file existence changed, update the sourceFiles cache cache.resolutionHost.getCachedDirectoryStructureHost()?.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); }); scheduleInvalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath, refCountCache); } function watchAffectingLocationsOfResolution( resolution: ResolutionWithFailedLookupLocations, watchedResolutionInfo: WatchedResolutionInfo, ) { if (resolution.affectingLocations?.length === watchedResolutionInfo.watchedAffected) { if (!watchedResolutionInfo.caches.has(currentCache!)) { resolution.affectingLocations?.forEach(affectingLocation => addRefCountToCacheMap( fileWatchesOfAffectingLocations.get(affectingLocation)!.resolutions!, currentCache!, ) ); } return; } if (!watchedResolutionInfo.watchedAffected && !watchedResolutionInfo.watchedFailed) resolutionsWithOnlyAffectingLocations.add(resolution); // Watch package json for (let i = watchedResolutionInfo.watchedAffected || 0; i < resolution.affectingLocations!.length; i++) { createFileWatcherOfAffectingLocation(resolution.affectingLocations![i], /*forResolution*/ true); addRefCountToMapForCaches( fileWatchesOfAffectingLocations.get(resolution.affectingLocations![i])!.resolutions!, watchedResolutionInfo.caches, currentCache, ); } watchedResolutionInfo.watchedAffected = resolution.affectingLocations?.length; } function createFileWatcherOfAffectingLocation(affectingLocation: string, forResolution?: true) { if (!forResolution) addRefCountToCacheMap(inUseResolutionCaches, currentCache!); const fileWatcher = fileWatchesOfAffectingLocations.get(affectingLocation); if (fileWatcher) { addRefCountToCacheMap(forResolution ? fileWatcher.resolutions ??= new Map() : fileWatcher.files ??= new Map(), currentCache!); return; } let locationToWatch = affectingLocation; let isSymlink = false; let symlinkWatcher: FileWatcherOfAffectingLocation | undefined; if (currentCache!.resolutionHost.realpath) { locationToWatch = currentCache!.resolutionHost.realpath(affectingLocation); if (affectingLocation !== locationToWatch) { isSymlink = true; symlinkWatcher = fileWatchesOfAffectingLocations.get(locationToWatch); } } const resolutions = forResolution ? new Map([[currentCache!, 1]]) : undefined; const files = forResolution ? undefined : new Map([[currentCache!, 1]]); if (!isSymlink || !symlinkWatcher) { const watcher: FileWatcherOfAffectingLocation = { watcher: canWatchAffectingLocation(sharedCacheHost.toPath(locationToWatch)) ? currentCache!.resolutionHost.watchAffectingFileLocation(locationToWatch, (fileName, eventKind) => { const refCountCaches = new Set(); onFileWatcherOfAffectingLocation(locationToWatch, refCountCaches); const seenCaches = new Set(); refCountCaches.forEach(caches => forRefCountCaches(caches, cache => { if (!tryAddToSet(seenCaches, cache)) return; cache.resolutionHost.getCachedDirectoryStructureHost()?.addOrDeleteFile(fileName, sharedCacheHost.toPath(locationToWatch), eventKind); cache.resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations(); }, /*skipCache*/ undefined) ); }) : noopFileWatcher, resolutions: isSymlink ? undefined : resolutions, files: isSymlink ? undefined : files, symlinks: undefined, }; fileWatchesOfAffectingLocations.set(locationToWatch, watcher); addRefToPackageJson(sharedCacheHost.toPath(locationToWatch)); if (isSymlink) symlinkWatcher = watcher; } if (isSymlink) { Debug.assert(!!symlinkWatcher); const watcher: FileWatcherOfAffectingLocation = { watcher: { close: () => { const symlinkWatcher = fileWatchesOfAffectingLocations.get(locationToWatch); // Close symlink watcher if no ref if (symlinkWatcher?.symlinks?.delete(affectingLocation) && !symlinkWatcher.symlinks.size && !symlinkWatcher.resolutions?.size && !symlinkWatcher.files?.size) { fileWatchesOfAffectingLocations.delete(locationToWatch); releasePackageJson(sharedCacheHost.toPath(locationToWatch)); symlinkWatcher.watcher.close(); } }, }, resolutions, files, symlinks: undefined, }; fileWatchesOfAffectingLocations.set(affectingLocation, watcher); addRefToPackageJson(sharedCacheHost.toPath(affectingLocation)); (symlinkWatcher.symlinks ??= new Set()).add(affectingLocation); } } function releaseFileWatcherOfAffectingLocation(location: string) { releaseRefCountFromMap(inUseResolutionCaches, currentCache!); const watcher = fileWatchesOfAffectingLocations.get(location)!; releaseRefCountFromMap(watcher.files!, currentCache!); } function closeFileWatcherOfAffectingLocation(path: string, watcher?: FileWatcherOfAffectingLocation) { watcher ??= fileWatchesOfAffectingLocations.get(path); if (watcher && !watcher.files?.size && !watcher.resolutions?.size && !watcher.symlinks?.size) { fileWatchesOfAffectingLocations.delete(path); releasePackageJson(sharedCacheHost.toPath(path)); watcher.watcher.close(); } } function onFileWatcherOfAffectingLocation(path: string, seenCaches: Set) { const watcher = fileWatchesOfAffectingLocations.get(path); if (watcher?.resolutions?.size) { (affectingPathChecks ??= new Set()).add(path); seenCaches.add(watcher.resolutions); } if (watcher?.files?.size) { watcher.files.forEach((_refCount, cache) => cache.invalidateAffectingFileWatcher(path)); seenCaches.add(watcher.files); } moduleResolutionCache.getPackageJsonInfoCache().getInternalMap()?.delete(sharedCacheHost.toPath(path)); watcher?.symlinks?.forEach(path => onFileWatcherOfAffectingLocation(path, seenCaches)); } function releaseResolution( resolution: ResolutionWithFailedLookupLocations, moduleOrTypeRefCache: ModuleOrTypeReferenceResolutionCache | undefined, ) { releaseRefCountFromMap(inUseResolutionCaches, currentCache!); const watchedResolutionInfo = watchedResolutionInfoMap.get(resolution)!; watchedResolutionInfo.caches.delete(currentCache!); const forCaches = new Set([currentCache!]); watchedResolutionInfo.dirWatches?.forEach(dirPath => removeDirectoryWatcher(dirPath, /*nonRecursive*/ false, forCaches)); watchedResolutionInfo.nonRecursiveDirWatches?.forEach(dirPath => removeDirectoryWatcher(dirPath, /*nonRecursive*/ true, forCaches)); watchedResolutionInfo.packageDirWatchers?.forEach((dirPaths, packageDirPath) => dirPaths.forEach( dirPath => removeRefToDirPathWatcherOfPackageDir(packageDirPath, dirPath), ) ); resolution.affectingLocations?.forEach(affectingLocation => { const watcher = fileWatchesOfAffectingLocations.get(affectingLocation); if (watcher?.resolutions) releaseRefCountFromMap(watcher.resolutions, currentCache!); closeFileWatcherOfAffectingLocation(affectingLocation, watcher); }); if (watchedResolutionInfo.caches.size) return false; watchedResolutionInfoMap.delete(resolution); if (resolutionsWithFailedLookups.delete(resolution)) { resolution.failedLookupLocations?.forEach(releaseIfPackageJson); if (resolution.alternateResult) releaseIfPackageJson(resolution.alternateResult); } resolutionsWithOnlyAffectingLocations.delete(resolution); const resolved = getModuleOrTypeRefResolved(resolution); if (resolved && resolved.resolvedFileName) { const key = sharedCacheHost.toPath(resolved.resolvedFileName); const resolutions = resolvedFileToResolution.get(key); if (resolutions?.delete(resolution) && !resolutions.size) resolvedFileToResolution.delete(key); } if (isResolvedWithGlobalCachePass(resolution)) { // Remove globalCacheResult from primary resolution const primary = resolution.globalCacheResolution.primary; const primaryWatchedInfo = watchedResolutionInfoMap.get(primary)!; if (primaryWatchedInfo.caches.size) { primary.globalCacheResolution = { primary }; } else { // Release primary as well watchedResolutionInfoMap.delete(primary); } } else if (resolution.globalCacheResolution?.globalResult) { // Keep this in watchInfo if has a globalCacheResult.globalResolution watchedResolutionInfoMap.set(resolution, watchedResolutionInfo); watchedResolutionInfo.dirWatches = undefined; watchedResolutionInfo.nonRecursiveDirWatches = undefined; watchedResolutionInfo.packageDirWatchers = undefined; watchedResolutionInfo.watchedFailed = undefined; watchedResolutionInfo.watchedAffected = undefined; watchedResolutionInfo.isInvalidated = undefined; watchedResolutionInfo.rootDirInfo = undefined; } if (moduleOrTypeRefCache) (cachesNeedingGc ??= new Set()).add(moduleOrTypeRefCache.sharedCache!); return true; } function invalidateResolutions( resolutions: Set | Map | undefined, canInvalidate: (resolution: ResolutionWithFailedLookupLocations) => boolean | undefined, ) { if (!resolutions) return false; let invalidated = false; resolutions.forEach(resolution => invalidated = invalidateResolution(resolution, canInvalidate) || invalidated); return invalidated; } function invalidateResolution( resolution: ResolutionWithFailedLookupLocations, canInvalidate: (resolution: ResolutionWithFailedLookupLocations) => boolean | undefined, ) { const watchedResolutionInfo = watchedResolutionInfoMap.get(resolution)!; if (watchedResolutionInfo.isInvalidated || !canInvalidate(resolution)) return false; watchedResolutionInfo.isInvalidated = true; watchedResolutionInfo.caches.forEach(cache => cache !== currentCache ? cache.invalidateResolution(resolution) : undefined ); return true; } function invalidateResolutionOfFile(filePath: Path) { return invalidateResolutions(resolvedFileToResolution.get(filePath), returnTrue); } function scheduleInvalidateResolutionOfFailedLookupLocation( fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean, refCountCache: RefCountCache, ) { if (isCreatingWatchedDirectory) { // Watching directory is created // Invalidate any resolution has failed lookup in this directory (isInDirectoryChecks ||= new Set()).add(fileOrDirectoryPath); } else { // If something to do with folder/file starting with "." in node_modules folder, skip it const updatedPath = removeIgnoredPath(fileOrDirectoryPath); if (!updatedPath) return false; fileOrDirectoryPath = updatedPath; // prevent saving an open file from over-eagerly triggering invalidation if (sharedCacheHost.fileIsOpen(fileOrDirectoryPath)) { return false; } // Some file or directory in the watching directory is created // Return early if it does not have any of the watching extension or not the custom failed lookup path const dirOfFileOrDirectory = getDirectoryPath(fileOrDirectoryPath); if ( isNodeModulesAtTypesDirectory(fileOrDirectoryPath) || isNodeModulesDirectory(fileOrDirectoryPath) || isNodeModulesAtTypesDirectory(dirOfFileOrDirectory) || isNodeModulesDirectory(dirOfFileOrDirectory) ) { // Invalidate any resolution from this directory (failedLookupChecks ||= new Set()).add(fileOrDirectoryPath); (startsWithPathChecks ||= new Set()).add(fileOrDirectoryPath); } else { // Ignore .map files if (fileExtensionIs(fileOrDirectoryPath, ".map")) { return false; } // Ignore emits from the program if all caches ignore it if ( !forEachKey( refCountCache, cache => !isEmittedFileOfProgram( cache.resolutionHost.getCurrentProgram(), fileOrDirectoryPath, ), ) ) return false; // Resolution need to be invalidated if failed lookup location is same as the file or directory getting created (failedLookupChecks ||= new Set()).add(fileOrDirectoryPath); // Also any path that starts with this path should be added just in case if this is directory notification // and we dont get any notification for file (startsWithPathChecks ||= new Set()).add(fileOrDirectoryPath); // If the invalidated file is from a node_modules package, invalidate everything else // in the package since we might not get notifications for other files in the package. // This hardens our logic against unreliable file watchers. const packagePath = parseNodeModuleFromPath(fileOrDirectoryPath, /*isFolder*/ true); if (packagePath) (startsWithPathChecks ||= new Set()).add(packagePath as Path); } } refCountCache.forEach((_refCount, cache) => cache.resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations()); } function invalidatePackageJsonMap() { const packageJsonMap = moduleResolutionCache.getPackageJsonInfoCache().getInternalMap(); if (packageJsonMap && (failedLookupChecks || startsWithPathChecks || isInDirectoryChecks)) { packageJsonMap.forEach((_value, path) => isInvalidatedFailedLookup(path) ? packageJsonMap.delete(path) : undefined); } } function invalidateResolutionsOfFailedLookupLocations() { if (!failedLookupChecks && !startsWithPathChecks && !isInDirectoryChecks && !affectingPathChecks) { return false; } let invalidated = invalidateResolutions(resolutionsWithFailedLookups, canInvalidateFailedLookupResolution); invalidatePackageJsonMap(); failedLookupChecks = undefined; startsWithPathChecks = undefined; isInDirectoryChecks = undefined; invalidated = invalidateResolutions(resolutionsWithOnlyAffectingLocations, canInvalidatedFailedLookupResolutionWithAffectingLocation) || invalidated; affectingPathChecks = undefined; return invalidated; } function canInvalidateFailedLookupResolution(resolution: ResolutionWithFailedLookupLocations) { if (canInvalidatedFailedLookupResolutionWithAffectingLocation(resolution)) return true; if (!failedLookupChecks && !startsWithPathChecks && !isInDirectoryChecks) return false; return resolution.failedLookupLocations?.some(location => isInvalidatedFailedLookup(sharedCacheHost.toPath(location))) || (!!resolution.alternateResult && isInvalidatedFailedLookup(sharedCacheHost.toPath(resolution.alternateResult))); } function isInvalidatedFailedLookup(locationPath: Path) { return failedLookupChecks?.has(locationPath) || firstDefinedIterator(startsWithPathChecks?.keys() || [], fileOrDirectoryPath => startsWith(locationPath, fileOrDirectoryPath) ? true : undefined) || firstDefinedIterator(isInDirectoryChecks?.keys() || [], dirPath => locationPath.length > dirPath.length && startsWith(locationPath, dirPath) && (isDiskPathRoot(dirPath) || locationPath[dirPath.length] === directorySeparator) ? true : undefined); } function canInvalidatedFailedLookupResolutionWithAffectingLocation(resolution: ResolutionWithFailedLookupLocations) { return !!affectingPathChecks && resolution.affectingLocations?.some(location => affectingPathChecks!.has(location)); } function createTypeRootsWatch(typeRoot: string, cache: ResolutionCache): FileWatcher { let watcher = typeRootsWatches.get(typeRoot); if (watcher) { watcher.refCount.add(cache); } else { typeRootsWatches.set( typeRoot, watcher = { watcher: canWatchAtTypes(sharedCacheHost.toPath(typeRoot)) ? cache.resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => { const fileOrDirectoryPath = sharedCacheHost.toPath(fileOrDirectory); typeRootsWatches.get(typeRoot)?.refCount.forEach(cache => { // Since the file existence changed, update the sourceFiles cache cache.resolutionHost.getCachedDirectoryStructureHost()?.addOrDeleteFileOrDirectory( fileOrDirectory, fileOrDirectoryPath, ); cache.invalidateTypeRoot(); }); // Since directory watchers invoked are flaky, the failed lookup location events might not be triggered // So handle to failed lookup locations here as well to ensure we are invalidating resolutions const fileOrDirectoryPathComponents = getPathComponents(fileOrDirectoryPath); directoryWatchesOfFailedLookups.forEach((_watcher, dirPath) => { if (isInDirectoryPath(getPathComponents(dirPath), fileOrDirectoryPathComponents)) { onDirectoryWatcher(dirPath, /*nonRecursive*/ false, fileOrDirectory, fileOrDirectoryPath); } }); nonRecursiveDirectoryWatchesOfFailedLookups.forEach((_watcher, dirPath) => { const dirPathComponents = getPathComponents(dirPath); if ( isInDirectoryPath(dirPathComponents, fileOrDirectoryPathComponents) && (dirPathComponents.length === fileOrDirectoryPathComponents.length || dirPathComponents.length + 1 === fileOrDirectoryPathComponents.length) ) { onDirectoryWatcher(dirPath, /*nonRecursive*/ true, fileOrDirectory, fileOrDirectoryPath); } }); }, WatchDirectoryFlags.Recursive) : noopFileWatcher, refCount: new Set([cache]), }, ); } const result: FileWatcher = { close: () => { const existing = typeRootsWatches.get(typeRoot); if (existing?.refCount.delete(cache)) { releaseRefCountFromMap(inUseResolutionCaches, cache); closeTypeRootsWatch(typeRoot, existing); } }, }; addRefCountToCacheMap(inUseResolutionCaches, cache); return result; } function closeTypeRootsWatch(typeRoot: string, watcher: TypeRootWatch) { if (!watcher.refCount.size) { typeRootsWatches.delete(typeRoot); watcher.watcher.close(); } } } /** @internal */ export function enableSharingModuleOrTypeReferenceResolutionCache>( moduleOrTypeRefCache: T, sharedCache: T, ): T { const getFromDirectoryCache = moduleOrTypeRefCache.getFromDirectoryCache; moduleOrTypeRefCache.getFromDirectoryCache = (name, mode, directoryName, redirectedReference) => { let result = getFromDirectoryCache.call(moduleOrTypeRefCache, name, mode, directoryName, redirectedReference); if (result) return result; result = withSharingModuleOrTypeReferenceResolutionCache( moduleOrTypeRefCache, sharedCache => sharedCache.getFromDirectoryCache(name, mode, directoryName, redirectedReference), ); if (result) { moduleOrTypeRefCache.setPerDirectoryAndNonRelativeNameCacheResult( name, mode, directoryName, redirectedReference, result, ); } return result; }; const getFromNonRelativeNameCache = moduleOrTypeRefCache.getFromNonRelativeNameCache; moduleOrTypeRefCache.getFromNonRelativeNameCache = (nonRelativeModuleName, mode, directoryName, redirectedReference) => getFromNonRelativeNameCache.call(moduleOrTypeRefCache, nonRelativeModuleName, mode, directoryName, redirectedReference) || withSharingModuleOrTypeReferenceResolutionCache( moduleOrTypeRefCache, sharedCache => sharedCache?.getFromNonRelativeNameCache(nonRelativeModuleName, mode, directoryName, redirectedReference), ); const setPerDirectoryAndNonRelativeNameCacheResult = moduleOrTypeRefCache.setPerDirectoryAndNonRelativeNameCacheResult; moduleOrTypeRefCache.setPerDirectoryAndNonRelativeNameCacheResult = (name, mode, directoryName, redirectedReference, result, primary) => { setPerDirectoryAndNonRelativeNameCacheResult.call(moduleOrTypeRefCache, name, mode, directoryName, redirectedReference, result, primary); if (primary) return; // Already in the cache result = result.globalCacheResolution?.primary || result; withSharingModuleOrTypeReferenceResolutionCache( moduleOrTypeRefCache, sharedCache => sharedCache.setPerDirectoryAndNonRelativeNameCacheResult(name, mode, directoryName, redirectedReference, result), ); }; const update = moduleOrTypeRefCache.update; moduleOrTypeRefCache.update = options => { update.call(moduleOrTypeRefCache, options); moduleOrTypeRefCache.sharedCache?.update(options); }; moduleOrTypeRefCache.sharedCache = sharedCache; return moduleOrTypeRefCache; } function withSharingModuleOrTypeReferenceResolutionCache, R>( moduleOrTypeRefCache: T, cb: (sharedCache: ModuleOrTypeReferenceResolutionCache) => R, ): R | undefined { if (!moduleOrTypeRefCache.sharedCache) return; moduleOrTypeRefCache.sharedCache.update(moduleOrTypeRefCache.options()); return cb(moduleOrTypeRefCache.sharedCache); } function addRefCountToCacheMap(refCountCache: Map, key: K) { refCountCache.set(key, (refCountCache.get(key) ?? 0) + 1); } function releaseRefCountFromMap(refCountCache: Map, key: K) { const existing = refCountCache.get(key)!; if (existing !== 1) refCountCache.set(key, existing - 1); else refCountCache.delete(key); } function addRefCountToMapForCaches(refCountCache: RefCountCache, forCaches: Set | RefCountCache, alreadyAddedCache: ResolutionCache | undefined) { forRefCountCaches(forCaches, cache => addRefCountToCacheMap(refCountCache, cache), alreadyAddedCache); } function releaseRefCountFromMapForCaches(refCountCache: RefCountCache, forCaches: Set | RefCountCache) { forRefCountCaches(forCaches, cache => releaseRefCountFromMap(refCountCache, cache), /*skipCache*/ undefined); } function forRefCountCaches(forCaches: Set | RefCountCache, cb: (cache: ResolutionCache) => void, skipCache: ResolutionCache | undefined) { forCaches.forEach((_refCount, cache) => { if (cache === skipCache) return; cb(cache); }); }