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