Repository: denoland/deno
Pull request: fix(lsp): create cacheable `ExportInfoMap` per language service

Extracted out of https://github.com/denoland/deno/pull/28131

This needs to be created per language service so that it can provide the current program correctly.

Changed files:
filename: cli/tsc/97_ts_host.js
patch: @@ -430,13 +430,6 @@ const hostImpl = {
     return projectVersion;
   },
   // @ts-ignore Undocumented method.
-  getCachedExportInfoMap() {
-    return exportMapCache;
-  },
-  getGlobalTypingsCacheLocation() {
-    return undefined;
-  },
-  // @ts-ignore Undocumented method.
   toPath(fileName) {
     // @ts-ignore Undocumented function.
     return ts.toPath(
@@ -768,9 +761,6 @@ for (const [key, value] of Object.entries(hostImpl)) {
   }
 }
 
-// @ts-ignore Undocumented function.
-const exportMapCache = ts.createCacheableExportInfoMap(host);
-
 // override the npm install @types package diagnostics to be deno specific
 ts.setLocalizedDiagnosticMessages((() => {
   const nodeMessage = "Cannot find name '{0}'."; // don't offer any suggestions

filename: cli/tsc/98_lsp.js
patch: @@ -284,6 +284,34 @@ async function pollRequests() {
 
 let hasStarted = false;
 
+function createLs() {
+  let exportInfoMap = undefined;
+  const newHost = {
+    ...host,
+    getCachedExportInfoMap: () => {
+      // this export info map is specific to
+      // the language service instance
+      return exportInfoMap;
+    },
+  };
+  const ls = ts.createLanguageService(
+    newHost,
+    documentRegistry,
+  );
+  exportInfoMap = ts.createCacheableExportInfoMap({
+    getCurrentProgram() {
+      return ls.getProgram();
+    },
+    getGlobalTypingsCacheLocation() {
+      return undefined;
+    },
+    getPackageJsonAutoImportProvider() {
+      return undefined;
+    },
+  });
+  return ls;
+}
+
 /** @param {boolean} enableDebugLogging */
 export async function serverMainLoop(enableDebugLogging) {
   ts.deno.setEnterSpan(ops.op_make_span);
@@ -293,10 +321,7 @@ export async function serverMainLoop(enableDebugLogging) {
   }
   hasStarted = true;
   LANGUAGE_SERVICE_ENTRIES.unscoped = {
-    ls: ts.createLanguageService(
-      host,
-      documentRegistry,
-    ),
+    ls: createLs(),
     compilerOptions: lspTsConfigToCompilerOptions({
       "allowJs": true,
       "esModuleInterop": true,
@@ -398,9 +423,7 @@ function serverRequestInner(id, method, args, scope, maybeChange) {
       for (const [scope, config] of newConfigsByScope) {
         LAST_REQUEST_SCOPE.set(scope);
         const oldEntry = LANGUAGE_SERVICE_ENTRIES.byScope.get(scope);
-        const ls = oldEntry
-          ? oldEntry.ls
-          : ts.createLanguageService(host, documentRegistry);
+        const ls = oldEntry ? oldEntry.ls : createLs();
         const compilerOptions = lspTsConfigToCompilerOptions(config);
         newByScope.set(scope, { ls, compilerOptions });
         LANGUAGE_SERVICE_ENTRIES.byScope.delete(scope);

filename: cli/tsc/dts/typescript.d.ts
patch: @@ -7753,6 +7753,40 @@ declare namespace ts {
         span: TextSpan;
         preferences: UserPreferences;
     }
+    function createCacheableExportInfoMap(host: CacheableExportInfoMapHost): ExportInfoMap;
+    enum ExportKind {
+        Named = 0,
+        Default = 1,
+        ExportEquals = 2,
+        UMD = 3,
+        Module = 4,
+    }
+    interface SymbolExportInfo {
+        readonly symbol: Symbol;
+        readonly moduleSymbol: Symbol;
+        /** Set if `moduleSymbol` is an external module, not an ambient module */
+        moduleFileName: string | undefined;
+        exportKind: ExportKind;
+        targetFlags: SymbolFlags;
+        /** True if export was only found via the package.json AutoImportProvider (for telemetry). */
+        isFromPackageJson: boolean;
+    }
+    interface ExportInfoMap {
+        isUsableByFile(importingFile: Path): boolean;
+        clear(): void;
+        add(importingFile: Path, symbol: Symbol, key: __String, moduleSymbol: Symbol, moduleFile: SourceFile | undefined, exportKind: ExportKind, isFromPackageJson: boolean, checker: TypeChecker): void;
+        get(importingFile: Path, key: ExportMapInfoKey): readonly SymbolExportInfo[] | undefined;
+        search<T>(importingFile: Path, preferCapitalized: boolean, matches: (name: string, targetFlags: SymbolFlags) => boolean, action: (info: readonly SymbolExportInfo[], symbolName: string, isFromAmbientModule: boolean, key: ExportMapInfoKey) => T | undefined): T | undefined;
+        releaseSymbols(): void;
+        isEmpty(): boolean;
+        /** @returns Whether the change resulted in the cache being cleared */
+        onFileChanged(oldSourceFile: SourceFile, newSourceFile: SourceFile, typeAcquisitionEnabled: boolean): boolean;
+    }
+    interface CacheableExportInfoMapHost {
+        getCurrentProgram(): Program | undefined;
+        getPackageJsonAutoImportProvider(): Program | undefined;
+        getGlobalTypingsCacheLocation(): string | undefined;
+    }
     type ExportMapInfoKey = string & {
         __exportInfoKey: void;
     };