1/*@internal*/ 2namespace ts.server { 3 export interface ModuleSpecifierResolutionCacheHost { 4 watchNodeModulesForPackageJsonChanges(directoryPath: string): FileWatcher; 5 } 6 7 export function createModuleSpecifierCache(host: ModuleSpecifierResolutionCacheHost): ModuleSpecifierCache { 8 let containedNodeModulesWatchers: ESMap<string, FileWatcher> | undefined; 9 let cache: ESMap<Path, ResolvedModuleSpecifierInfo> | undefined; 10 let currentKey: string | undefined; 11 const result: ModuleSpecifierCache = { 12 get(fromFileName, toFileName, preferences, options) { 13 if (!cache || currentKey !== key(fromFileName, preferences, options)) return undefined; 14 return cache.get(toFileName); 15 }, 16 set(fromFileName, toFileName, preferences, options, modulePaths, moduleSpecifiers) { 17 ensureCache(fromFileName, preferences, options).set(toFileName, createInfo(modulePaths, moduleSpecifiers, /*isBlockedByPackageJsonDependencies*/ false)); 18 19 // If any module specifiers were generated based off paths in node_modules, 20 // a package.json file in that package was read and is an input to the cached. 21 // Instead of watching each individual package.json file, set up a wildcard 22 // directory watcher for any node_modules referenced and clear the cache when 23 // it sees any changes. 24 if (moduleSpecifiers) { 25 for (const p of modulePaths) { 26 if (p.isInNodeModules) { 27 // No trailing slash 28 const nodeModulesPath = p.path.substring(0, p.path.indexOf(nodeModulesPathPart) + nodeModulesPathPart.length - 1); 29 if (!containedNodeModulesWatchers?.has(nodeModulesPath)) { 30 (containedNodeModulesWatchers ||= new Map()).set( 31 nodeModulesPath, 32 host.watchNodeModulesForPackageJsonChanges(nodeModulesPath), 33 ); 34 } 35 } 36 } 37 } 38 }, 39 setModulePaths(fromFileName, toFileName, preferences, options, modulePaths) { 40 const cache = ensureCache(fromFileName, preferences, options); 41 const info = cache.get(toFileName); 42 if (info) { 43 info.modulePaths = modulePaths; 44 } 45 else { 46 cache.set(toFileName, createInfo(modulePaths, /*moduleSpecifiers*/ undefined, /*isBlockedByPackageJsonDependencies*/ undefined)); 47 } 48 }, 49 setBlockedByPackageJsonDependencies(fromFileName, toFileName, preferences, options, isBlockedByPackageJsonDependencies) { 50 const cache = ensureCache(fromFileName, preferences, options); 51 const info = cache.get(toFileName); 52 if (info) { 53 info.isBlockedByPackageJsonDependencies = isBlockedByPackageJsonDependencies; 54 } 55 else { 56 cache.set(toFileName, createInfo(/*modulePaths*/ undefined, /*moduleSpecifiers*/ undefined, isBlockedByPackageJsonDependencies)); 57 } 58 }, 59 clear() { 60 containedNodeModulesWatchers?.forEach(watcher => watcher.close()); 61 cache?.clear(); 62 containedNodeModulesWatchers?.clear(); 63 currentKey = undefined; 64 }, 65 count() { 66 return cache ? cache.size : 0; 67 } 68 }; 69 if (Debug.isDebugging) { 70 Object.defineProperty(result, "__cache", { get: () => cache }); 71 } 72 return result; 73 74 function ensureCache(fromFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions) { 75 const newKey = key(fromFileName, preferences, options); 76 if (cache && (currentKey !== newKey)) { 77 result.clear(); 78 } 79 currentKey = newKey; 80 return cache ||= new Map(); 81 } 82 83 function key(fromFileName: Path, preferences: UserPreferences, options: ModuleSpecifierOptions) { 84 return `${fromFileName},${preferences.importModuleSpecifierEnding},${preferences.importModuleSpecifierPreference},${options.overrideImportMode}`; 85 } 86 87 function createInfo( 88 modulePaths: readonly ModulePath[] | undefined, 89 moduleSpecifiers: readonly string[] | undefined, 90 isBlockedByPackageJsonDependencies: boolean | undefined, 91 ): ResolvedModuleSpecifierInfo { 92 return { modulePaths, moduleSpecifiers, isBlockedByPackageJsonDependencies }; 93 } 94 } 95} 96