Files
TypeScript/src/compiler/sharedResolutionCache.ts
T

1431 lines
68 KiB
TypeScript

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<T> {
sharedCache?: ModuleOrTypeReferenceResolutionCache<T>;
}
}
/**
* 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<Path, Set<ResolutionWithFailedLookupLocations>>;
resolutionsWithFailedLookups: Set<ResolutionWithFailedLookupLocations>;
packageJsonRefCount: Map<Path, number>;
resolutionsWithOnlyAffectingLocations: Set<ResolutionWithFailedLookupLocations>;
watchedResolutionInfoMap: Map<ResolutionWithFailedLookupLocations, WatchedResolutionInfo>;
typeRootsWatches: Map<string, TypeRootWatch>;
inUseResolutionCaches: RefCountCache;
cacheToOptions: Map<ResolutionCache, Set<CompilerOptions>>;
directoryWatchesOfFailedLookups: Map<Path, DirectoryWatchesOfFailedLookup>;
nonRecursiveDirectoryWatchesOfFailedLookups: Map<Path, DirectoryWatchesOfFailedLookup>;
fileWatchesOfAffectingLocations: Map<string, FileWatcherOfAffectingLocation>;
packageDirWatchers: Map<Path, PackageDirWatcher>;
dirPathToSymlinkPackageRefCount: Map<Path, number>;
clear(cache: ResolutionCache): void;
startCachingPerDirectoryResolution(cache: ResolutionCache): void;
finishCachingPerDirectoryResolution(): void;
compactCaches(availableOptions: Set<CompilerOptions>, cache: ResolutionCache): void;
watchResolution<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>(
resolution: T,
getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>,
redirectedReference: ResolvedProjectReference | undefined,
): void;
releaseResolution(
resolution: ResolutionWithFailedLookupLocations,
moduleOrTypeRefCache: ModuleOrTypeReferenceResolutionCache<ResolutionWithFailedLookupLocations>,
): 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<ResolutionCache>;
watchedFailed?: number;
watchedAffected?: number;
dirWatches?: Set<Path>;
nonRecursiveDirWatches?: Set<Path>;
packageDirWatchers?: Map<Path, Set<Path>>;
rootDirInfo?: RootDirInfo;
}
/** @internal */
export type ResolutionWithFailedLookupLocations =
| ResolvedModuleWithFailedLookupLocations
| (
& ResolvedTypeReferenceDirectiveWithFailedLookupLocations
// Just so we can use this directly. These any ways are optional properties
& Pick<ResolvedModuleWithFailedLookupLocations, "alternateResult" | "globalCacheResolution">
);
/** @internal */
export interface SharedResolutionCacheHost {
getCurrentDirectory(): string;
toPath(fileName: string): Path;
getCanonicalFileName: GetCanonicalFileName;
preferNonRecursiveWatch: boolean | undefined;
fileIsOpen(filePath: Path): boolean;
}
/** @internal */
export type RefCountCache = Map<ResolutionCache, number>;
/** @internal */
export interface FileWatcherOfAffectingLocation {
/** watcher for the lookup */
watcher: FileWatcher;
resolutions: RefCountCache | undefined;
files: RefCountCache | undefined;
symlinks: Set<string> | 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<Path, DirPathToWatcherOfPackageDirWatcher>;
isSymlink: boolean;
}
/** @internal */
export interface TypeRootWatch {
watcher: FileWatcher;
refCount: Set<ResolutionCache>;
}
/** @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<PathPathComponents>, 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<PathPathComponents>, 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<PathPathComponents>, fileOrDirComponents: Readonly<PathPathComponents>) {
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<PathPathComponents>,
isRootWatchable: boolean,
getCurrentDirectory: () => string | undefined,
preferNonRecursiveWatch: boolean | undefined,
): DirectoryOfFailedLookupWatch | undefined {
const failedLookupPathComponents: Readonly<PathPathComponents> = 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<PathPathComponents>,
dirPathComponentsLength: number,
perceivedOsRootLength: number,
nodeModulesIndex: number,
rootPathComponents: Readonly<PathPathComponents>,
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<PathPathComponents>,
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<T extends ResolutionWithFailedLookupLocations = ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName = ResolutionWithResolvedFileName> = (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<ResolutionWithFailedLookupLocations>();
const resolutionsWithOnlyAffectingLocations = new Set<ResolutionWithFailedLookupLocations>();
const resolvedFileToResolution = new Map<Path, Set<ResolutionWithFailedLookupLocations>>();
const impliedFormatPackageJsons = new Map<Path, readonly string[]>();
const watchedResolutionInfoMap = new Map<ResolutionWithFailedLookupLocations, WatchedResolutionInfo>();
const typeRootsWatches = new Map<string, TypeRootWatch>();
const inUseResolutionCaches = new Map<ResolutionCache, number>();
const cacheToOptions = new Map<ResolutionCache, Set<CompilerOptions>>();
let affectingPathChecks: Set<string> | undefined;
let failedLookupChecks: Set<Path> | undefined;
let startsWithPathChecks: Set<Path> | undefined;
let isInDirectoryChecks: Set<Path> | undefined;
let cachesNeedingGc: Set<ModuleOrTypeReferenceResolutionCache<ResolutionWithFailedLookupLocations>> | undefined;
let potentiallyUnreferencedDirWatchers: Set<Path> | 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<Path, number>();
let potentiallyUnwatchedPackageJsons: Set<Path> | undefined;
const directoryWatchesOfFailedLookups = new Map<Path, DirectoryWatchesOfFailedLookup>();
const nonRecursiveDirectoryWatchesOfFailedLookups = new Map<Path, DirectoryWatchesOfFailedLookup>();
const fileWatchesOfAffectingLocations = new Map<string, FileWatcherOfAffectingLocation>();
const isSymlinkCache = new Map<Path, boolean>();
const packageDirWatchers = new Map<Path, PackageDirWatcher>(); // Watching packageDir if symlink otherwise watching dirPath
const dirPathToSymlinkPackageRefCount = new Map<Path, number>(); // 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<CompilerOptions>, cache: ResolutionCache) {
if (availableOptions.size) cacheToOptions.set(cache, availableOptions);
else cacheToOptions.delete(cache);
compactCachesWoker();
}
function compactCachesWoker() {
let availableOptions: Set<CompilerOptions>;
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<ResolutionWithFailedLookupLocations>,
) {
considerNonWatchedResolutionAsInvalidated = true;
cache.gc(watchedResolutionInfoMap);
considerNonWatchedResolutionAsInvalidated = false;
}
function getValidResolution<T extends ResolutionWithFailedLookupLocations>(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<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>(
resolution: T,
getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>,
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<ResolutionCache> | 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<RefCountCache>();
onFileWatcherOfAffectingLocation(locationToWatch, refCountCaches);
const seenCaches = new Set<ResolutionCache>();
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<RefCountCache>) {
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<ResolutionWithFailedLookupLocations> | 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<ResolutionWithFailedLookupLocations> | Map<string, ResolutionWithFailedLookupLocations> | 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<T extends ModuleOrTypeReferenceResolutionCache<any>>(
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<T extends ModuleOrTypeReferenceResolutionCache<any>, R>(
moduleOrTypeRefCache: T,
cb: (sharedCache: ModuleOrTypeReferenceResolutionCache<any>) => R,
): R | undefined {
if (!moduleOrTypeRefCache.sharedCache) return;
moduleOrTypeRefCache.sharedCache.update(moduleOrTypeRefCache.options());
return cb(moduleOrTypeRefCache.sharedCache);
}
function addRefCountToCacheMap<K>(refCountCache: Map<K, number>, key: K) {
refCountCache.set(key, (refCountCache.get(key) ?? 0) + 1);
}
function releaseRefCountFromMap<K>(refCountCache: Map<K, number>, 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<ResolutionCache> | RefCountCache, alreadyAddedCache: ResolutionCache | undefined) {
forRefCountCaches(forCaches, cache => addRefCountToCacheMap(refCountCache, cache), alreadyAddedCache);
}
function releaseRefCountFromMapForCaches(refCountCache: RefCountCache, forCaches: Set<ResolutionCache> | RefCountCache) {
forRefCountCaches(forCaches, cache => releaseRefCountFromMap(refCountCache, cache), /*skipCache*/ undefined);
}
function forRefCountCaches(forCaches: Set<ResolutionCache> | RefCountCache, cb: (cache: ResolutionCache) => void, skipCache: ResolutionCache | undefined) {
forCaches.forEach((_refCount, cache) => {
if (cache === skipCache) return;
cb(cache);
});
}