From b5df1c5113e018d01360cbd35de8586c485d503a Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Tue, 25 May 2021 12:46:55 -0700
Subject: [PATCH 01/11] Fix discovery of more pnpm symlinks

---
 src/compiler/checker.ts          |  1 +
 src/compiler/moduleSpecifiers.ts |  8 +++++---
 src/compiler/program.ts          |  7 +++----
 src/compiler/types.ts            |  5 +++--
 src/compiler/utilities.ts        | 19 ++++++++++---------
 src/server/project.ts            | 14 +++++++++-----
 src/services/types.ts            |  2 +-
 src/services/utilities.ts        |  1 +
 8 files changed, 33 insertions(+), 24 deletions(-)

diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 7f784c44beb3c..1e24daf252b14 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -4485,6 +4485,7 @@ namespace ts {
                         getSourceFiles: () => host.getSourceFiles(),
                         getCurrentDirectory: () => host.getCurrentDirectory(),
                         getSymlinkCache: maybeBind(host, host.getSymlinkCache),
+                        getResolvedTypeReferenceDirectives: maybeBind(host, host.getResolvedTypeReferenceDirectives),
                         useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames),
                         redirectTargetsMap: host.redirectTargetsMap,
                         getProjectReferenceRedirect: fileName => host.getProjectReferenceRedirect(fileName),
diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts
index 45b31154344c8..aaa62034ffe20 100644
--- a/src/compiler/moduleSpecifiers.ts
+++ b/src/compiler/moduleSpecifiers.ts
@@ -294,10 +294,12 @@ namespace ts.moduleSpecifiers {
             const result = forEach(targets, p => !(shouldFilterIgnoredPaths && containsIgnoredPath(p)) && cb(p, referenceRedirect === p));
             if (result) return result;
         }
-        const links = host.getSymlinkCache
-            ? host.getSymlinkCache()
-            : discoverProbableSymlinks(host.getSourceFiles(), getCanonicalFileName, cwd);
 
+        const links = host.getSymlinkCache?.() || discoverProbableSymlinks(
+            host.getSourceFiles(),
+            host.getResolvedTypeReferenceDirectives ? arrayFrom(host.getResolvedTypeReferenceDirectives?.().values()) : emptyArray,
+            getCanonicalFileName,
+            cwd);
         const symlinkedDirectories = links.getSymlinkedDirectoriesByRealpath();
         const fullImportedFileName = getNormalizedAbsolutePath(importedFileName, cwd);
         const result = symlinkedDirectories && forEachAncestorDirectory(getDirectoryPath(fullImportedFileName), realPathDirectory => {
diff --git a/src/compiler/program.ts b/src/compiler/program.ts
index 102517c4ce6fd..4dff0ffd8eed7 100644
--- a/src/compiler/program.ts
+++ b/src/compiler/program.ts
@@ -1650,6 +1650,7 @@ namespace ts {
                 getLibFileFromReference: program.getLibFileFromReference,
                 isSourceFileFromExternalLibrary,
                 getResolvedProjectReferenceToRedirect,
+                getResolvedTypeReferenceDirectives: program.getResolvedTypeReferenceDirectives,
                 getProjectReferenceRedirect,
                 isSourceOfProjectReferenceRedirect,
                 getSymlinkCache,
@@ -3660,11 +3661,9 @@ namespace ts {
         }
 
         function getSymlinkCache(): SymlinkCache {
-            if (host.getSymlinkCache) {
-                return host.getSymlinkCache();
-            }
-            return symlinks || (symlinks = discoverProbableSymlinks(
+            return host.getSymlinkCache?.() || (symlinks ||= discoverProbableSymlinks(
                 files,
+                arrayFrom(resolvedTypeReferenceDirectives.values()),
                 getCanonicalFileName,
                 host.getCurrentDirectory()));
         }
diff --git a/src/compiler/types.ts b/src/compiler/types.ts
index f395014baad1d..f11e2724b9df6 100644
--- a/src/compiler/types.ts
+++ b/src/compiler/types.ts
@@ -6559,7 +6559,7 @@ namespace ts {
 
         // TODO: later handle this in better way in builder host instead once the api for tsbuild finalizes and doesn't use compilerHost as base
         /*@internal*/createDirectory?(directory: string): void;
-        /*@internal*/getSymlinkCache?(): SymlinkCache;
+        /*@internal*/getSymlinkCache?(): SymlinkCache | undefined;
 
         // For testing:
         /*@internal*/ disableUseFileVersionAsSignature?: boolean;
@@ -8132,7 +8132,8 @@ namespace ts {
         directoryExists?(path: string): boolean;
         readFile?(path: string): string | undefined;
         realpath?(path: string): string;
-        getSymlinkCache?(): SymlinkCache;
+        getSymlinkCache?(): SymlinkCache | undefined;
+        getResolvedTypeReferenceDirectives?(): ReadonlyESMap<string, ResolvedTypeReferenceDirective | undefined>;
         getModuleSpecifierCache?(): ModuleSpecifierCache;
         getGlobalTypingsCacheLocation?(): string | undefined;
         getNearestAncestorDirectoryWithPackageJson?(fileName: string, rootDir?: string): string | undefined;
diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts
index 8271e6adfc22f..dd497f68a6353 100644
--- a/src/compiler/utilities.ts
+++ b/src/compiler/utilities.ts
@@ -6227,18 +6227,19 @@ namespace ts {
         };
     }
 
-    export function discoverProbableSymlinks(files: readonly SourceFile[], getCanonicalFileName: GetCanonicalFileName, cwd: string): SymlinkCache {
+    export function discoverProbableSymlinks(files: readonly SourceFile[], typeReferenceDirectives: readonly (ResolvedTypeReferenceDirective | undefined)[], getCanonicalFileName: GetCanonicalFileName, cwd: string): SymlinkCache {
         const cache = createSymlinkCache(cwd, getCanonicalFileName);
-        const symlinks = flatMap(files, sf => {
-            const pairs = sf.resolvedModules && arrayFrom(mapDefinedIterator(sf.resolvedModules.values(), res =>
-                res?.originalPath ? [res.resolvedFileName, res.originalPath] as const : undefined));
-            return concatenate(pairs, sf.resolvedTypeReferenceDirectiveNames && arrayFrom(mapDefinedIterator(sf.resolvedTypeReferenceDirectiveNames.values(), res =>
-                res?.originalPath && res.resolvedFileName ? [res.resolvedFileName, res.originalPath] as const : undefined)));
+        const symlinksFromFiles: readonly (ResolvedModuleFull | ResolvedTypeReferenceDirective | undefined)[] = flatMap(files, sf => {
+            return sf.resolvedModules && arrayFrom(mapDefinedIterator(sf.resolvedModules.values(), res =>
+                res?.originalPath ? res : undefined));
         });
 
-        for (const [resolvedPath, originalPath] of symlinks) {
-            cache.setSymlinkedFile(toPath(originalPath, cwd, getCanonicalFileName), resolvedPath);
-            const [commonResolved, commonOriginal] = guessDirectorySymlink(resolvedPath, originalPath, cwd, getCanonicalFileName) || emptyArray;
+        const resolutions = symlinksFromFiles.concat(typeReferenceDirectives);
+        for (const resolution of resolutions) {
+            if (!resolution || !resolution.originalPath || !resolution.resolvedFileName) continue;
+            const { resolvedFileName, originalPath } = resolution;
+            cache.setSymlinkedFile(toPath(originalPath, cwd, getCanonicalFileName), resolvedFileName);
+            const [commonResolved, commonOriginal] = guessDirectorySymlink(resolvedFileName, originalPath, cwd, getCanonicalFileName) || emptyArray;
             if (commonResolved && commonOriginal) {
                 cache.setSymlinkedDirectory(
                     commonOriginal,
diff --git a/src/server/project.ts b/src/server/project.ts
index ef48ebf530bc2..6457c0ec75283 100644
--- a/src/server/project.ts
+++ b/src/server/project.ts
@@ -351,10 +351,14 @@ namespace ts.server {
             return this.projectService.typingsCache;
         }
 
-        /*@internal*/
-        getSymlinkCache(): SymlinkCache {
-            return this.symlinks || (this.symlinks = discoverProbableSymlinks(
-                this.program?.getSourceFiles() || emptyArray,
+        /**
+         * @internal
+         * Returns undefined prior to program creation.
+         */
+        getSymlinkCache(): SymlinkCache | undefined {
+            return this.symlinks || this.program && (this.symlinks = discoverProbableSymlinks(
+                this.program.getSourceFiles() || emptyArray,
+                arrayFrom(this.program.getResolvedTypeReferenceDirectives().values()),
                 this.getCanonicalFileName,
                 this.getCurrentDirectory()));
         }
@@ -1902,7 +1906,7 @@ namespace ts.server {
                     moduleResolutionHost));
 
                 const program = hostProject.getCurrentProgram()!;
-                const symlinkCache = hostProject.getSymlinkCache();
+                const symlinkCache = hostProject.getSymlinkCache()!; // Guaranteed to be set when program is set
                 for (const resolution of resolutions) {
                     if (!resolution.resolvedTypeReferenceDirective?.resolvedFileName) continue;
                     const { resolvedFileName, originalPath } = resolution.resolvedTypeReferenceDirective;
diff --git a/src/services/types.ts b/src/services/types.ts
index be1d09ff69c57..75c696d35137a 100644
--- a/src/services/types.ts
+++ b/src/services/types.ts
@@ -277,7 +277,7 @@ namespace ts {
         /* @internal */
         getGlobalTypingsCacheLocation?(): string | undefined;
         /* @internal */
-        getSymlinkCache?(files?: readonly SourceFile[]): SymlinkCache;
+        getSymlinkCache?(files?: readonly SourceFile[]): SymlinkCache | undefined;
 
         /*
          * Required for full import and type reference completions.
diff --git a/src/services/utilities.ts b/src/services/utilities.ts
index 6ffa3607c8ccb..a2431e7fe3411 100644
--- a/src/services/utilities.ts
+++ b/src/services/utilities.ts
@@ -1847,6 +1847,7 @@ namespace ts {
             getGlobalTypingsCacheLocation: maybeBind(host, host.getGlobalTypingsCacheLocation),
             getSourceFiles: () => program.getSourceFiles(),
             redirectTargetsMap: program.redirectTargetsMap,
+            getResolvedTypeReferenceDirectives: program.getResolvedTypeReferenceDirectives,
             getProjectReferenceRedirect: fileName => program.getProjectReferenceRedirect(fileName),
             isSourceOfProjectReferenceRedirect: fileName => program.isSourceOfProjectReferenceRedirect(fileName),
             getNearestAncestorDirectoryWithPackageJson: maybeBind(host, host.getNearestAncestorDirectoryWithPackageJson),

From 7c72d49ec6cb39b39cbdd07f876c080c3b360957 Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Tue, 25 May 2021 15:31:26 -0700
Subject: [PATCH 02/11] Add some tests

---
 src/harness/client.ts                         | 10 +++----
 tests/cases/fourslash/fourslash.ts            |  2 ++
 .../server/importNameCodeFix_pnpm1.ts         | 15 ++++++++++
 .../importStatementCompletions_pnpm1.ts       | 30 +++++++++++++++++++
 4 files changed, 51 insertions(+), 6 deletions(-)
 create mode 100644 tests/cases/fourslash/server/importNameCodeFix_pnpm1.ts
 create mode 100644 tests/cases/fourslash/server/importStatementCompletions_pnpm1.ts

diff --git a/src/harness/client.ts b/src/harness/client.ts
index 97e05c563b914..c7d432a0c1dcb 100644
--- a/src/harness/client.ts
+++ b/src/harness/client.ts
@@ -196,18 +196,16 @@ namespace ts.server {
             // Not passing along 'preferences' because server should already have those from the 'configure' command
             const args: protocol.CompletionsRequestArgs = this.createFileLocationRequestArgs(fileName, position);
 
-            const request = this.processRequest<protocol.CompletionsRequest>(CommandNames.Completions, args);
-            const response = this.processResponse<protocol.CompletionsResponse>(request);
+            const request = this.processRequest<protocol.CompletionsRequest>(CommandNames.CompletionInfo, args);
+            const response = this.processResponse<protocol.CompletionInfoResponse>(request);
 
             return {
                 isGlobalCompletion: false,
                 isMemberCompletion: false,
                 isNewIdentifierLocation: false,
-                entries: response.body!.map<CompletionEntry>(entry => { // TODO: GH#18217
+                entries: response.body!.entries.map<CompletionEntry>(entry => { // TODO: GH#18217
                     if (entry.replacementSpan !== undefined) {
-                        const { name, kind, kindModifiers, sortText, replacementSpan, hasAction, source, data, isRecommended } = entry;
-                        // TODO: GH#241
-                        const res: CompletionEntry = { name, kind, kindModifiers, sortText, replacementSpan: this.decodeSpan(replacementSpan, fileName), hasAction, source, data: data as any, isRecommended };
+                        const res: CompletionEntry = { ...entry, data: entry.data as any, replacementSpan: this.decodeSpan(entry.replacementSpan, fileName) };
                         return res;
                     }
 
diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts
index 2ceb0eb0897ab..26c3c220b7243 100644
--- a/tests/cases/fourslash/fourslash.ts
+++ b/tests/cases/fourslash/fourslash.ts
@@ -625,6 +625,8 @@ declare namespace FourSlashInterface {
         readonly includeCompletionsForModuleExports?: boolean;
         readonly includeCompletionsForImportStatements?: boolean;
         readonly includeCompletionsWithSnippetText?: boolean;
+        readonly includeCompletionsWithInsertText?: boolean;
+        /** @deprecated use `includeCompletionsWithInsertText` */
         readonly includeInsertTextCompletions?: boolean;
         readonly includeAutomaticOptionalChainCompletions?: boolean;
         readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative";
diff --git a/tests/cases/fourslash/server/importNameCodeFix_pnpm1.ts b/tests/cases/fourslash/server/importNameCodeFix_pnpm1.ts
new file mode 100644
index 0000000000000..61eb984c7ced5
--- /dev/null
+++ b/tests/cases/fourslash/server/importNameCodeFix_pnpm1.ts
@@ -0,0 +1,15 @@
+/// <reference path="../fourslash.ts" />
+
+// @Filename: /project/tsconfig.json
+//// { "compilerOptions": { "module": "commonjs" } }
+
+// @Filename: /project/node_modules/.pnpm/@types+react@17.0.7/node_modules/@types/react/index.d.ts
+//// export declare function Component(): void;
+
+// @Filename: /project/index.ts
+//// Component/**/
+
+// @link: /project/node_modules/.pnpm/@types+react@17.0.7/node_modules/@types/react -> /project/node_modules/@types/react
+
+goTo.marker("");
+verify.importFixAtPosition([`import { Component } from "react";\r\n\r\nComponent`]);
diff --git a/tests/cases/fourslash/server/importStatementCompletions_pnpm1.ts b/tests/cases/fourslash/server/importStatementCompletions_pnpm1.ts
new file mode 100644
index 0000000000000..b57b846805dcd
--- /dev/null
+++ b/tests/cases/fourslash/server/importStatementCompletions_pnpm1.ts
@@ -0,0 +1,30 @@
+/// <reference path="../fourslash.ts" />
+
+// @Filename: /project/tsconfig.json
+//// { "compilerOptions": { "module": "commonjs" } }
+
+// @Filename: /project/node_modules/.pnpm/@types+react@17.0.7/node_modules/@types/react/index.d.ts
+//// export declare function Component(): void;
+
+// @Filename: /project/index.ts
+//// [|import Com/**/|]
+
+// @link: /project/node_modules/.pnpm/@types+react@17.0.7/node_modules/@types/react -> /project/node_modules/@types/react
+
+goTo.marker("");
+verify.completions({
+  marker: "",
+  exact: [{
+    name: "Component",
+    source: "react",
+    insertText: `import { Component$1 } from "react";`,
+    isSnippet: true,
+    replacementSpan: test.ranges()[0],
+    sourceDisplay: "react",
+  }],
+  preferences: {
+    includeCompletionsForImportStatements: true,
+    includeCompletionsWithInsertText: true,
+    includeCompletionsWithSnippetText: true,
+  }
+});

From 16e40e2c55bc37176d0d94c8ecad0f2312175e7a Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Tue, 25 May 2021 16:32:05 -0700
Subject: [PATCH 03/11] =?UTF-8?q?Never=20show=20pnpm=20paths=20in=20auto?=
 =?UTF-8?q?=20imports,=20even=20if=20there=E2=80=99s=20no=20other=20path?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/compiler/moduleSpecifiers.ts              | 13 +++++++--
 src/compiler/types.ts                         |  1 +
 src/services/codefixes/importFixes.ts         | 11 +++++---
 src/services/completions.ts                   |  2 +-
 src/services/utilities.ts                     |  1 +
 .../importStatementCompletions_pnpm2.ts       | 28 +++++++++++++++++++
 6 files changed, 49 insertions(+), 7 deletions(-)
 create mode 100644 tests/cases/fourslash/server/importStatementCompletions_pnpm2.ts

diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts
index aaa62034ffe20..bb9d04333e58a 100644
--- a/src/compiler/moduleSpecifiers.ts
+++ b/src/compiler/moduleSpecifiers.ts
@@ -163,7 +163,16 @@ namespace ts.moduleSpecifiers {
 
         return pathsSpecifiers?.length ? pathsSpecifiers :
             nodeModulesSpecifiers?.length ? nodeModulesSpecifiers :
-            Debug.checkDefined(relativeSpecifiers);
+            fallback();
+
+        // Unless 'preferFailureToResultsWithIgnoredPaths' is set, we *must* generate a relative path fallback.
+        // (The declaration emitter must write something, but auto-imports may exclude bad suggestions.)
+        function fallback() {
+            if (host.preferFailureToResultsWithIgnoredPaths?.()) {
+                return relativeSpecifiers || emptyArray;
+            }
+            return Debug.checkDefined(relativeSpecifiers);
+        }
     }
 
     interface Info {
@@ -286,7 +295,7 @@ namespace ts.moduleSpecifiers {
         const redirects = host.redirectTargetsMap.get(importedPath) || emptyArray;
         const importedFileNames = [...(referenceRedirect ? [referenceRedirect] : emptyArray), importedFileName, ...redirects];
         const targets = importedFileNames.map(f => getNormalizedAbsolutePath(f, cwd));
-        let shouldFilterIgnoredPaths = !every(targets, containsIgnoredPath);
+        let shouldFilterIgnoredPaths = host.preferFailureToResultsWithIgnoredPaths?.() || !every(targets, containsIgnoredPath);
 
         if (!preferSymlinks) {
             // Symlinks inside ignored paths are already filtered out of the symlink cache,
diff --git a/src/compiler/types.ts b/src/compiler/types.ts
index f11e2724b9df6..daf7ccdc90dac 100644
--- a/src/compiler/types.ts
+++ b/src/compiler/types.ts
@@ -8137,6 +8137,7 @@ namespace ts {
         getModuleSpecifierCache?(): ModuleSpecifierCache;
         getGlobalTypingsCacheLocation?(): string | undefined;
         getNearestAncestorDirectoryWithPackageJson?(fileName: string, rootDir?: string): string | undefined;
+        preferFailureToResultsWithIgnoredPaths?(): boolean;
 
         getSourceFiles(): readonly SourceFile[];
         readonly redirectTargetsMap: RedirectTargetsMap;
diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts
index 061aeaaca7733..a1d5692324ee8 100644
--- a/src/services/codefixes/importFixes.ts
+++ b/src/services/codefixes/importFixes.ts
@@ -66,7 +66,9 @@ namespace ts.codefix {
             const preferTypeOnlyImport = !!usageIsTypeOnly && compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error;
             const useRequire = shouldUseRequire(sourceFile, program);
             const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, /*position*/ undefined, preferTypeOnlyImport, useRequire, host, preferences);
-            addImport({ fixes: [fix], symbolName });
+            if (fix) {
+                addImport({ fixes: [fix], symbolName });
+            }
         }
 
         function addImport(info: FixesInfo) {
@@ -203,7 +205,7 @@ namespace ts.codefix {
             : getAllReExportingModules(sourceFile, exportedSymbol, moduleSymbol, symbolName, host, program, /*useAutoImportProvider*/ true);
         const useRequire = shouldUseRequire(sourceFile, program);
         const preferTypeOnlyImport = compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && !isSourceFileJS(sourceFile) && isValidTypeOnlyAliasUseSite(getTokenAtPosition(sourceFile, position));
-        const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, position, preferTypeOnlyImport, useRequire, host, preferences);
+        const fix = Debug.checkDefined(getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, position, preferTypeOnlyImport, useRequire, host, preferences));
         return { moduleSpecifier: fix.moduleSpecifier, codeAction: codeFixActionToCodeAction(codeActionForFix({ host, formatContext, preferences }, sourceFile, symbolName, fix, getQuotePreference(sourceFile, preferences))) };
     }
 
@@ -274,7 +276,7 @@ namespace ts.codefix {
         program: Program,
         host: LanguageServiceHost,
         preferences: UserPreferences
-    ): { exportInfo?: SymbolExportInfo, moduleSpecifier: string } {
+    ): { exportInfo?: SymbolExportInfo, moduleSpecifier: string } | undefined {
         return getBestFix(getNewImportFixes(program, importingFile, /*position*/ undefined, /*preferTypeOnlyImport*/ false, /*useRequire*/ false, exportInfo, host, preferences), importingFile, host);
     }
 
@@ -529,7 +531,8 @@ namespace ts.codefix {
         return sort(fixes, (a, b) => compareValues(a.kind, b.kind) || compareModuleSpecifiers(a, b, allowsImportingSpecifier));
     }
 
-    function getBestFix<T extends ImportFix>(fixes: readonly T[], sourceFile: SourceFile, host: LanguageServiceHost): T {
+    function getBestFix<T extends ImportFix>(fixes: readonly T[], sourceFile: SourceFile, host: LanguageServiceHost): T | undefined {
+        if (!some(fixes)) return;
         // These will always be placed first if available, and are better than other kinds
         if (fixes[0].kind === ImportFixKind.UseNamespace || fixes[0].kind === ImportFixKind.AddToExisting) {
             return fixes[0];
diff --git a/src/services/completions.ts b/src/services/completions.ts
index e97753908f7c4..0ce98c3f0d26a 100644
--- a/src/services/completions.ts
+++ b/src/services/completions.ts
@@ -1762,7 +1762,7 @@ namespace ts.Completions {
                     // If we don't need to resolve module specifiers, we can use any re-export that is importable at all
                     // (We need to ensure that at least one is importable to show a completion.)
                     const { moduleSpecifier, exportInfo } = resolveModuleSpecifiers
-                        ? codefix.getModuleSpecifierForBestExportInfo(info, sourceFile, program, host, preferences)
+                        ? codefix.getModuleSpecifierForBestExportInfo(info, sourceFile, program, host, preferences) || {}
                         : { moduleSpecifier: undefined, exportInfo: find(info, isImportableExportInfo) };
                     if (!exportInfo) return;
                     const moduleFile = tryCast(exportInfo.moduleSymbol.valueDeclaration, isSourceFile);
diff --git a/src/services/utilities.ts b/src/services/utilities.ts
index a2431e7fe3411..0f9dd7f202ff8 100644
--- a/src/services/utilities.ts
+++ b/src/services/utilities.ts
@@ -1852,6 +1852,7 @@ namespace ts {
             isSourceOfProjectReferenceRedirect: fileName => program.isSourceOfProjectReferenceRedirect(fileName),
             getNearestAncestorDirectoryWithPackageJson: maybeBind(host, host.getNearestAncestorDirectoryWithPackageJson),
             getFileIncludeReasons: () => program.getFileIncludeReasons(),
+            preferFailureToResultsWithIgnoredPaths: returnTrue,
         };
     }
 
diff --git a/tests/cases/fourslash/server/importStatementCompletions_pnpm2.ts b/tests/cases/fourslash/server/importStatementCompletions_pnpm2.ts
new file mode 100644
index 0000000000000..3b4939b470da9
--- /dev/null
+++ b/tests/cases/fourslash/server/importStatementCompletions_pnpm2.ts
@@ -0,0 +1,28 @@
+/// <reference path="../fourslash.ts" />
+
+// @Filename: /project/tsconfig.json
+//// { "compilerOptions": { "module": "commonjs" } }
+
+// @Filename: /project/node_modules/.pnpm/@types+react@17.0.7/node_modules/@types/react/index.d.ts
+//// import "csstype";
+//// export declare function Component(): void;
+
+// @Filename: /project/node_modules/.pnpm/csstype@3.0.8/node_modules/csstype/index.d.ts
+//// export interface SvgProperties {}
+
+// @Filename: /project/index.ts
+//// [|import SvgProp/**/|]
+
+// @link: /project/node_modules/.pnpm/@types+react@17.0.7/node_modules/@types/react -> /project/node_modules/@types/react
+// @link: /project/node_modules/.pnpm/csstype@3.0.8/node_modules/csstype -> /project/node_modules/.pnpm/@types+react@17.0.7/node_modules/csstype
+
+goTo.marker("");
+verify.completions({
+  marker: "",
+  exact: [],
+  preferences: {
+    includeCompletionsForImportStatements: true,
+    includeCompletionsWithInsertText: true,
+    includeCompletionsWithSnippetText: true,
+  }
+});

From 3794e048369cc0e7087922ae2775b077d684be9a Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Tue, 25 May 2021 17:21:41 -0700
Subject: [PATCH 04/11] Import statement completions can return none

---
 src/services/completions.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/services/completions.ts b/src/services/completions.ts
index 0ce98c3f0d26a..fb1fb0fcfad52 100644
--- a/src/services/completions.ts
+++ b/src/services/completions.ts
@@ -1594,6 +1594,7 @@ namespace ts.Completions {
 
         function tryGetImportCompletionSymbols(): GlobalsSearch {
             if (!importCompletionNode) return GlobalsSearch.Continue;
+            isNewIdentifierLocation = true;
             collectAutoImports(/*resolveModuleSpecifiers*/ true);
             return GlobalsSearch.Success;
         }

From a522672aee395e538c851e1332ed5500980c7b81 Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Wed, 26 May 2021 08:53:35 -0700
Subject: [PATCH 05/11] Fix tests

---
 src/harness/client.ts                                       | 6 +++---
 tests/cases/fourslash/importStatementCompletions1.ts        | 2 ++
 .../importStatementCompletions_esModuleInterop1.ts          | 1 +
 .../importStatementCompletions_esModuleInterop2.ts          | 1 +
 .../importStatementCompletions_noPatternAmbient.ts          | 1 +
 .../cases/fourslash/importStatementCompletions_noSnippet.ts | 1 +
 tests/cases/fourslash/importStatementCompletions_quotes.ts  | 1 +
 .../fourslash/importStatementCompletions_semicolons.ts      | 1 +
 .../fourslash/server/importStatementCompletions_pnpm1.ts    | 1 +
 .../fourslash/server/importStatementCompletions_pnpm2.ts    | 1 +
 10 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/src/harness/client.ts b/src/harness/client.ts
index c7d432a0c1dcb..46c90b94b3c81 100644
--- a/src/harness/client.ts
+++ b/src/harness/client.ts
@@ -200,9 +200,9 @@ namespace ts.server {
             const response = this.processResponse<protocol.CompletionInfoResponse>(request);
 
             return {
-                isGlobalCompletion: false,
-                isMemberCompletion: false,
-                isNewIdentifierLocation: false,
+                isGlobalCompletion: response.body!.isGlobalCompletion,
+                isMemberCompletion: response.body!.isMemberCompletion,
+                isNewIdentifierLocation: response.body!.isNewIdentifierLocation,
                 entries: response.body!.entries.map<CompletionEntry>(entry => { // TODO: GH#18217
                     if (entry.replacementSpan !== undefined) {
                         const res: CompletionEntry = { ...entry, data: entry.data as any, replacementSpan: this.decodeSpan(entry.replacementSpan, fileName) };
diff --git a/tests/cases/fourslash/importStatementCompletions1.ts b/tests/cases/fourslash/importStatementCompletions1.ts
index c28aa81c7922b..6e08d5d3d46b9 100644
--- a/tests/cases/fourslash/importStatementCompletions1.ts
+++ b/tests/cases/fourslash/importStatementCompletions1.ts
@@ -23,6 +23,7 @@
 
 [0, 1, 2, 3, 4, 5].forEach(marker => {
   verify.completions({
+    isNewIdentifierLocation: true,
     marker: "" + marker,
     exact: [{
       name: "foo",
@@ -65,6 +66,7 @@
 
 [6, 7, 8, 9, 10, 11, 12].forEach(marker => {
   verify.completions({
+    isNewIdentifierLocation: true,
     marker: "" + marker,
     exact: [],
     preferences: {
diff --git a/tests/cases/fourslash/importStatementCompletions_esModuleInterop1.ts b/tests/cases/fourslash/importStatementCompletions_esModuleInterop1.ts
index a6d2083489b89..4d41ee5702119 100644
--- a/tests/cases/fourslash/importStatementCompletions_esModuleInterop1.ts
+++ b/tests/cases/fourslash/importStatementCompletions_esModuleInterop1.ts
@@ -10,6 +10,7 @@
 //// [|import f/**/|]
 
 verify.completions({
+  isNewIdentifierLocation: true,
   marker: "",
   exact: [{
     name: "foo",
diff --git a/tests/cases/fourslash/importStatementCompletions_esModuleInterop2.ts b/tests/cases/fourslash/importStatementCompletions_esModuleInterop2.ts
index c6a5b8e25e1f6..6d0d6ecd4dbf1 100644
--- a/tests/cases/fourslash/importStatementCompletions_esModuleInterop2.ts
+++ b/tests/cases/fourslash/importStatementCompletions_esModuleInterop2.ts
@@ -10,6 +10,7 @@
 //// [|import f/**/|]
 
 verify.completions({
+  isNewIdentifierLocation: true,
   marker: "",
   exact: [{
     name: "foo",
diff --git a/tests/cases/fourslash/importStatementCompletions_noPatternAmbient.ts b/tests/cases/fourslash/importStatementCompletions_noPatternAmbient.ts
index 78bad2241b0c8..f92eecec4937c 100644
--- a/tests/cases/fourslash/importStatementCompletions_noPatternAmbient.ts
+++ b/tests/cases/fourslash/importStatementCompletions_noPatternAmbient.ts
@@ -10,6 +10,7 @@
 //// import style/**/
 
 verify.completions({
+  isNewIdentifierLocation: true,
   marker: "",
   exact: [],
   preferences: {
diff --git a/tests/cases/fourslash/importStatementCompletions_noSnippet.ts b/tests/cases/fourslash/importStatementCompletions_noSnippet.ts
index 8f87dba53bb39..7545caf97a537 100644
--- a/tests/cases/fourslash/importStatementCompletions_noSnippet.ts
+++ b/tests/cases/fourslash/importStatementCompletions_noSnippet.ts
@@ -7,6 +7,7 @@
 //// [|import f/**/|]
 
 verify.completions({
+  isNewIdentifierLocation: true,
   marker: "",
   exact: [{
     name: "foo",
diff --git a/tests/cases/fourslash/importStatementCompletions_quotes.ts b/tests/cases/fourslash/importStatementCompletions_quotes.ts
index 845dd0a205caf..e39b96a530266 100644
--- a/tests/cases/fourslash/importStatementCompletions_quotes.ts
+++ b/tests/cases/fourslash/importStatementCompletions_quotes.ts
@@ -8,6 +8,7 @@
 //// [|import f/**/|]
 
 verify.completions({
+  isNewIdentifierLocation: true,
   marker: "",
   exact: [{
     name: "foo",
diff --git a/tests/cases/fourslash/importStatementCompletions_semicolons.ts b/tests/cases/fourslash/importStatementCompletions_semicolons.ts
index fb61be82d790a..6674caee82965 100644
--- a/tests/cases/fourslash/importStatementCompletions_semicolons.ts
+++ b/tests/cases/fourslash/importStatementCompletions_semicolons.ts
@@ -8,6 +8,7 @@
 //// [|import f/**/|]
 
 verify.completions({
+  isNewIdentifierLocation: true,
   marker: "",
   exact: [{
     name: "foo",
diff --git a/tests/cases/fourslash/server/importStatementCompletions_pnpm1.ts b/tests/cases/fourslash/server/importStatementCompletions_pnpm1.ts
index b57b846805dcd..9e6d16ced2eab 100644
--- a/tests/cases/fourslash/server/importStatementCompletions_pnpm1.ts
+++ b/tests/cases/fourslash/server/importStatementCompletions_pnpm1.ts
@@ -13,6 +13,7 @@
 
 goTo.marker("");
 verify.completions({
+  isNewIdentifierLocation: true,
   marker: "",
   exact: [{
     name: "Component",
diff --git a/tests/cases/fourslash/server/importStatementCompletions_pnpm2.ts b/tests/cases/fourslash/server/importStatementCompletions_pnpm2.ts
index 3b4939b470da9..5649c2447686b 100644
--- a/tests/cases/fourslash/server/importStatementCompletions_pnpm2.ts
+++ b/tests/cases/fourslash/server/importStatementCompletions_pnpm2.ts
@@ -18,6 +18,7 @@
 
 goTo.marker("");
 verify.completions({
+  isNewIdentifierLocation: true,
   marker: "",
   exact: [],
   preferences: {

From e066ba4a826158b7693dda1d36f6843f141e29a1 Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Thu, 27 May 2021 12:31:33 -0700
Subject: [PATCH 06/11] Add failing test showing poor symlink cache reuse

---
 src/testRunner/tsconfig.json                  |  1 +
 .../unittests/tsserver/symlinkCache.ts        | 81 +++++++++++++++++++
 2 files changed, 82 insertions(+)
 create mode 100644 src/testRunner/unittests/tsserver/symlinkCache.ts

diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json
index 439b447d6396f..b93ec82423462 100644
--- a/src/testRunner/tsconfig.json
+++ b/src/testRunner/tsconfig.json
@@ -206,6 +206,7 @@
         "unittests/tsserver/session.ts",
         "unittests/tsserver/skipLibCheck.ts",
         "unittests/tsserver/smartSelection.ts",
+        "unittests/tsserver/symlinkCache.ts",
         "unittests/tsserver/symLinks.ts",
         "unittests/tsserver/syntacticServer.ts",
         "unittests/tsserver/syntaxOperations.ts",
diff --git a/src/testRunner/unittests/tsserver/symlinkCache.ts b/src/testRunner/unittests/tsserver/symlinkCache.ts
new file mode 100644
index 0000000000000..80d996eee4ff2
--- /dev/null
+++ b/src/testRunner/unittests/tsserver/symlinkCache.ts
@@ -0,0 +1,81 @@
+namespace ts.projectSystem {
+    const appTsconfigJson: File = {
+        path: "/packages/app/tsconfig.json",
+        content: `
+        {
+            "compilerOptions": {
+                "module": "commonjs",
+                "outDir": "dist",
+                "rootDir": "src",
+                "baseUrl": "."
+            }
+            "references": [{ "path": "../dep" }]
+        }`
+    };
+
+    const appSrcIndexTs: File = {
+        path: "/packages/app/src/index.ts",
+        content: `import "dep";`
+    };
+
+    const depPackageJson: File = {
+        path: "/packages/dep/package.json",
+        content: `{ "name": "dep", "main": "dist/index.js", "types": "dist/index.d.ts" }`
+    };
+
+    const depTsconfigJson: File = {
+        path: "/packages/dep/tsconfig.json",
+        content: `
+        {
+            "compilerOptions": { "outDir": "dist", "rootDir": "src", "module": "commonjs" }
+        }`
+    };
+
+    const depSrcIndexTs: File = {
+        path: "/packages/dep/src/index.ts",
+        content: `
+        import "./sub/folder";`
+    };
+
+    const depSrcSubFolderIndexTs: File = {
+        path: "/packages/dep/src/sub/folder/index.ts",
+        content: `export const dep = 0;`
+    };
+
+    const link: SymLink = {
+        path: "/packages/dep",
+        symLink: "/packages/app/node_modules/dep",
+    };
+
+
+    describe("unittests:: tsserver:: symlinkCache", () => {
+        it("contains symlinks discovered by project references resolution after program creation", () => {
+            const { session, projectService } = setup();
+            openFilesForSession([appSrcIndexTs], session);
+            const project = projectService.configuredProjects.get(appTsconfigJson.path)!;
+            assert.deepEqual(
+                project.getSymlinkCache()?.getSymlinkedDirectories()?.get(link.symLink as Path),
+                { real: link.path, realPath: link.path as Path }
+            );
+        });
+    });
+
+    function setup() {
+        const host = createServerHost([
+            appTsconfigJson,
+            appSrcIndexTs,
+            depPackageJson,
+            depTsconfigJson,
+            depSrcIndexTs,
+            depSrcSubFolderIndexTs,
+            link,
+        ]);
+        const session = createSession(host);
+        const projectService = session.getProjectService();
+        return {
+            host,
+            projectService,
+            session,
+        };
+    }
+}

From 6cc27a06d1466bdde8159e59755c3899a04f9961 Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Thu, 27 May 2021 15:33:31 -0700
Subject: [PATCH 07/11] Fix test, fails for right reasons now

---
 src/testRunner/unittests/tsserver/symlinkCache.ts | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/src/testRunner/unittests/tsserver/symlinkCache.ts b/src/testRunner/unittests/tsserver/symlinkCache.ts
index 80d996eee4ff2..b0c4f06bba805 100644
--- a/src/testRunner/unittests/tsserver/symlinkCache.ts
+++ b/src/testRunner/unittests/tsserver/symlinkCache.ts
@@ -15,7 +15,7 @@ namespace ts.projectSystem {
 
     const appSrcIndexTs: File = {
         path: "/packages/app/src/index.ts",
-        content: `import "dep";`
+        content: `import "dep/does/not/exist";`
     };
 
     const depPackageJson: File = {
@@ -43,19 +43,18 @@ namespace ts.projectSystem {
     };
 
     const link: SymLink = {
-        path: "/packages/dep",
-        symLink: "/packages/app/node_modules/dep",
+        path: "/packages/app/node_modules/dep",
+        symLink: "../../dep",
     };
 
-
     describe("unittests:: tsserver:: symlinkCache", () => {
         it("contains symlinks discovered by project references resolution after program creation", () => {
             const { session, projectService } = setup();
             openFilesForSession([appSrcIndexTs], session);
             const project = projectService.configuredProjects.get(appTsconfigJson.path)!;
             assert.deepEqual(
-                project.getSymlinkCache()?.getSymlinkedDirectories()?.get(link.symLink as Path),
-                { real: link.path, realPath: link.path as Path }
+                project.getSymlinkCache()?.getSymlinkedDirectories()?.get(link.path + "/" as Path),
+                { real: "/packages/dep", realPath: "/packages/dep" as Path }
             );
         });
     });

From e221f9ecb4aebf42f21ba2d91ed07ddcd570f742 Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Thu, 27 May 2021 16:00:18 -0700
Subject: [PATCH 08/11] Preserve cache built up during program creation, then
 fill in with program resolutions

---
 src/compiler/checker.ts                       |  2 --
 src/compiler/moduleSpecifiers.ts              |  7 +---
 src/compiler/program.ts                       | 16 +++++----
 src/compiler/types.ts                         |  6 ++--
 src/compiler/utilities.ts                     | 36 ++++++++++++-------
 src/server/project.ts                         | 16 +++++----
 src/services/types.ts                         |  2 +-
 src/services/utilities.ts                     |  2 --
 .../unittests/tsserver/symlinkCache.ts        |  2 +-
 9 files changed, 49 insertions(+), 40 deletions(-)

diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 1e24daf252b14..92cc28d328668 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -4482,10 +4482,8 @@ namespace ts {
                     // If no full tracker is provided, fake up a dummy one with a basic limited-functionality moduleResolverHost
                     tracker: tracker && tracker.trackSymbol ? tracker : { trackSymbol: () => false, moduleResolverHost: flags! & NodeBuilderFlags.DoNotIncludeSymbolChain ? {
                         getCommonSourceDirectory: !!(host as Program).getCommonSourceDirectory ? () => (host as Program).getCommonSourceDirectory() : () => "",
-                        getSourceFiles: () => host.getSourceFiles(),
                         getCurrentDirectory: () => host.getCurrentDirectory(),
                         getSymlinkCache: maybeBind(host, host.getSymlinkCache),
-                        getResolvedTypeReferenceDirectives: maybeBind(host, host.getResolvedTypeReferenceDirectives),
                         useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames),
                         redirectTargetsMap: host.redirectTargetsMap,
                         getProjectReferenceRedirect: fileName => host.getProjectReferenceRedirect(fileName),
diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts
index bb9d04333e58a..6e53588e56258 100644
--- a/src/compiler/moduleSpecifiers.ts
+++ b/src/compiler/moduleSpecifiers.ts
@@ -304,12 +304,7 @@ namespace ts.moduleSpecifiers {
             if (result) return result;
         }
 
-        const links = host.getSymlinkCache?.() || discoverProbableSymlinks(
-            host.getSourceFiles(),
-            host.getResolvedTypeReferenceDirectives ? arrayFrom(host.getResolvedTypeReferenceDirectives?.().values()) : emptyArray,
-            getCanonicalFileName,
-            cwd);
-        const symlinkedDirectories = links.getSymlinkedDirectoriesByRealpath();
+        const symlinkedDirectories = host.getSymlinkCache?.().getSymlinkedDirectoriesByRealpath();
         const fullImportedFileName = getNormalizedAbsolutePath(importedFileName, cwd);
         const result = symlinkedDirectories && forEachAncestorDirectory(getDirectoryPath(fullImportedFileName), realPathDirectory => {
             const symlinkDirectories = symlinkedDirectories.get(ensureTrailingDirectorySeparator(toPath(realPathDirectory, cwd, getCanonicalFileName)));
diff --git a/src/compiler/program.ts b/src/compiler/program.ts
index 4dff0ffd8eed7..6b13178589653 100644
--- a/src/compiler/program.ts
+++ b/src/compiler/program.ts
@@ -1650,7 +1650,6 @@ namespace ts {
                 getLibFileFromReference: program.getLibFileFromReference,
                 isSourceFileFromExternalLibrary,
                 getResolvedProjectReferenceToRedirect,
-                getResolvedTypeReferenceDirectives: program.getResolvedTypeReferenceDirectives,
                 getProjectReferenceRedirect,
                 isSourceOfProjectReferenceRedirect,
                 getSymlinkCache,
@@ -3661,11 +3660,16 @@ namespace ts {
         }
 
         function getSymlinkCache(): SymlinkCache {
-            return host.getSymlinkCache?.() || (symlinks ||= discoverProbableSymlinks(
-                files,
-                arrayFrom(resolvedTypeReferenceDirectives.values()),
-                getCanonicalFileName,
-                host.getCurrentDirectory()));
+            if (host.getSymlinkCache) {
+                return host.getSymlinkCache();
+            }
+            if (!symlinks) {
+                symlinks = createSymlinkCache(currentDirectory, getCanonicalFileName);
+            }
+            if (files && resolvedTypeReferenceDirectives && !symlinks.hasProcessedResolutions()) {
+                symlinks.setSymlinksFromResolutions(files, resolvedTypeReferenceDirectives);
+            }
+            return symlinks;
         }
     }
 
diff --git a/src/compiler/types.ts b/src/compiler/types.ts
index daf7ccdc90dac..11e7cecd225c2 100644
--- a/src/compiler/types.ts
+++ b/src/compiler/types.ts
@@ -6559,7 +6559,7 @@ namespace ts {
 
         // TODO: later handle this in better way in builder host instead once the api for tsbuild finalizes and doesn't use compilerHost as base
         /*@internal*/createDirectory?(directory: string): void;
-        /*@internal*/getSymlinkCache?(): SymlinkCache | undefined;
+        /*@internal*/getSymlinkCache?(): SymlinkCache;
 
         // For testing:
         /*@internal*/ disableUseFileVersionAsSignature?: boolean;
@@ -8132,14 +8132,12 @@ namespace ts {
         directoryExists?(path: string): boolean;
         readFile?(path: string): string | undefined;
         realpath?(path: string): string;
-        getSymlinkCache?(): SymlinkCache | undefined;
-        getResolvedTypeReferenceDirectives?(): ReadonlyESMap<string, ResolvedTypeReferenceDirective | undefined>;
+        getSymlinkCache?(): SymlinkCache;
         getModuleSpecifierCache?(): ModuleSpecifierCache;
         getGlobalTypingsCacheLocation?(): string | undefined;
         getNearestAncestorDirectoryWithPackageJson?(fileName: string, rootDir?: string): string | undefined;
         preferFailureToResultsWithIgnoredPaths?(): boolean;
 
-        getSourceFiles(): readonly SourceFile[];
         readonly redirectTargetsMap: RedirectTargetsMap;
         getProjectReferenceRedirect(fileName: string): string | undefined;
         isSourceOfProjectReferenceRedirect(fileName: string): boolean;
diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts
index dd497f68a6353..c3a7cb9318622 100644
--- a/src/compiler/utilities.ts
+++ b/src/compiler/utilities.ts
@@ -6190,12 +6190,25 @@ namespace ts {
         setSymlinkedFile(symlinkPath: Path, real: string): void;
         /*@internal*/
         setSymlinkedDirectoryFromSymlinkedFile(symlink: string, real: string): void;
+        /**
+         * @internal
+         * Uses resolvedTypeReferenceDirectives from program instead of from files, since files
+         * don't include automatic type reference directives. Must be called only when
+         * `hasProcessedResolutions` returns false (once per cache instance).
+         */
+        setSymlinksFromResolutions(files: readonly SourceFile[], typeReferenceDirectives: ReadonlyESMap<string, ResolvedTypeReferenceDirective | undefined> | undefined): void;
+        /**
+         * @internal
+         * Whether `setSymlinksFromResolutions` has already been called.
+         */
+        hasProcessedResolutions(): boolean;
     }
 
     export function createSymlinkCache(cwd: string, getCanonicalFileName: GetCanonicalFileName): SymlinkCache {
         let symlinkedDirectories: ESMap<Path, SymlinkedDirectory | false> | undefined;
         let symlinkedDirectoriesByRealpath: MultiMap<Path, string> | undefined;
         let symlinkedFiles: ESMap<Path, string> | undefined;
+        let hasProcessedResolutions = false;
         return {
             getSymlinkedFiles: () => symlinkedFiles,
             getSymlinkedDirectories: () => symlinkedDirectories,
@@ -6224,19 +6237,19 @@ namespace ts {
                     });
                 }
             },
+            setSymlinksFromResolutions(files, typeReferenceDirectives) {
+                Debug.assert(!hasProcessedResolutions);
+                hasProcessedResolutions = true;
+                for (const file of files) {
+                    file.resolvedModules?.forEach(resolution => processResolution(this, resolution));
+                }
+                typeReferenceDirectives?.forEach(resolution => processResolution(this, resolution));
+            },
+            hasProcessedResolutions: () => hasProcessedResolutions,
         };
-    }
-
-    export function discoverProbableSymlinks(files: readonly SourceFile[], typeReferenceDirectives: readonly (ResolvedTypeReferenceDirective | undefined)[], getCanonicalFileName: GetCanonicalFileName, cwd: string): SymlinkCache {
-        const cache = createSymlinkCache(cwd, getCanonicalFileName);
-        const symlinksFromFiles: readonly (ResolvedModuleFull | ResolvedTypeReferenceDirective | undefined)[] = flatMap(files, sf => {
-            return sf.resolvedModules && arrayFrom(mapDefinedIterator(sf.resolvedModules.values(), res =>
-                res?.originalPath ? res : undefined));
-        });
 
-        const resolutions = symlinksFromFiles.concat(typeReferenceDirectives);
-        for (const resolution of resolutions) {
-            if (!resolution || !resolution.originalPath || !resolution.resolvedFileName) continue;
+        function processResolution(cache: SymlinkCache, resolution: ResolvedModuleFull | ResolvedTypeReferenceDirective | undefined) {
+            if (!resolution || !resolution.originalPath || !resolution.resolvedFileName) return;
             const { resolvedFileName, originalPath } = resolution;
             cache.setSymlinkedFile(toPath(originalPath, cwd, getCanonicalFileName), resolvedFileName);
             const [commonResolved, commonOriginal] = guessDirectorySymlink(resolvedFileName, originalPath, cwd, getCanonicalFileName) || emptyArray;
@@ -6246,7 +6259,6 @@ namespace ts {
                     { real: commonResolved, realPath: toPath(commonResolved, cwd, getCanonicalFileName) });
             }
         }
-        return cache;
     }
 
     function guessDirectorySymlink(a: string, b: string, cwd: string, getCanonicalFileName: GetCanonicalFileName): [string, string] | undefined {
diff --git a/src/server/project.ts b/src/server/project.ts
index 6457c0ec75283..12377af72d91b 100644
--- a/src/server/project.ts
+++ b/src/server/project.ts
@@ -355,12 +355,16 @@ namespace ts.server {
          * @internal
          * Returns undefined prior to program creation.
          */
-        getSymlinkCache(): SymlinkCache | undefined {
-            return this.symlinks || this.program && (this.symlinks = discoverProbableSymlinks(
-                this.program.getSourceFiles() || emptyArray,
-                arrayFrom(this.program.getResolvedTypeReferenceDirectives().values()),
-                this.getCanonicalFileName,
-                this.getCurrentDirectory()));
+        getSymlinkCache(): SymlinkCache {
+            if (!this.symlinks) {
+                this.symlinks = createSymlinkCache(this.getCurrentDirectory(), this.getCanonicalFileName);
+            }
+            if (this.program && !this.symlinks.hasProcessedResolutions()) {
+                this.symlinks.setSymlinksFromResolutions(
+                    this.program.getSourceFiles(),
+                    this.program.getResolvedTypeReferenceDirectives());
+            }
+            return this.symlinks;
         }
 
         // Method of LanguageServiceHost
diff --git a/src/services/types.ts b/src/services/types.ts
index 75c696d35137a..be1d09ff69c57 100644
--- a/src/services/types.ts
+++ b/src/services/types.ts
@@ -277,7 +277,7 @@ namespace ts {
         /* @internal */
         getGlobalTypingsCacheLocation?(): string | undefined;
         /* @internal */
-        getSymlinkCache?(files?: readonly SourceFile[]): SymlinkCache | undefined;
+        getSymlinkCache?(files?: readonly SourceFile[]): SymlinkCache;
 
         /*
          * Required for full import and type reference completions.
diff --git a/src/services/utilities.ts b/src/services/utilities.ts
index 0f9dd7f202ff8..9b4395db32c64 100644
--- a/src/services/utilities.ts
+++ b/src/services/utilities.ts
@@ -1845,9 +1845,7 @@ namespace ts {
             getSymlinkCache: maybeBind(host, host.getSymlinkCache) || program.getSymlinkCache,
             getModuleSpecifierCache: maybeBind(host, host.getModuleSpecifierCache),
             getGlobalTypingsCacheLocation: maybeBind(host, host.getGlobalTypingsCacheLocation),
-            getSourceFiles: () => program.getSourceFiles(),
             redirectTargetsMap: program.redirectTargetsMap,
-            getResolvedTypeReferenceDirectives: program.getResolvedTypeReferenceDirectives,
             getProjectReferenceRedirect: fileName => program.getProjectReferenceRedirect(fileName),
             isSourceOfProjectReferenceRedirect: fileName => program.isSourceOfProjectReferenceRedirect(fileName),
             getNearestAncestorDirectoryWithPackageJson: maybeBind(host, host.getNearestAncestorDirectoryWithPackageJson),
diff --git a/src/testRunner/unittests/tsserver/symlinkCache.ts b/src/testRunner/unittests/tsserver/symlinkCache.ts
index b0c4f06bba805..39160c5399f23 100644
--- a/src/testRunner/unittests/tsserver/symlinkCache.ts
+++ b/src/testRunner/unittests/tsserver/symlinkCache.ts
@@ -54,7 +54,7 @@ namespace ts.projectSystem {
             const project = projectService.configuredProjects.get(appTsconfigJson.path)!;
             assert.deepEqual(
                 project.getSymlinkCache()?.getSymlinkedDirectories()?.get(link.path + "/" as Path),
-                { real: "/packages/dep", realPath: "/packages/dep" as Path }
+                { real: "/packages/dep/", realPath: "/packages/dep/" as Path }
             );
         });
     });

From 287dfe3821a9c7b8d5e43f702f40b4cb56b7bbbc Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Thu, 27 May 2021 16:02:54 -0700
Subject: [PATCH 09/11] Remove obsolete comment

---
 src/server/project.ts | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/src/server/project.ts b/src/server/project.ts
index 12377af72d91b..8f76436838b7a 100644
--- a/src/server/project.ts
+++ b/src/server/project.ts
@@ -351,10 +351,7 @@ namespace ts.server {
             return this.projectService.typingsCache;
         }
 
-        /**
-         * @internal
-         * Returns undefined prior to program creation.
-         */
+        /*@internal*/
         getSymlinkCache(): SymlinkCache {
             if (!this.symlinks) {
                 this.symlinks = createSymlinkCache(this.getCurrentDirectory(), this.getCanonicalFileName);

From db6508441a1e56f13a6a5d8ff1dd1d0ea1232e12 Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Thu, 27 May 2021 16:03:31 -0700
Subject: [PATCH 10/11] Remove obsolete type assertion

---
 src/server/project.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/server/project.ts b/src/server/project.ts
index 8f76436838b7a..e61bbe4582536 100644
--- a/src/server/project.ts
+++ b/src/server/project.ts
@@ -1907,7 +1907,7 @@ namespace ts.server {
                     moduleResolutionHost));
 
                 const program = hostProject.getCurrentProgram()!;
-                const symlinkCache = hostProject.getSymlinkCache()!; // Guaranteed to be set when program is set
+                const symlinkCache = hostProject.getSymlinkCache();
                 for (const resolution of resolutions) {
                     if (!resolution.resolvedTypeReferenceDirective?.resolvedFileName) continue;
                     const { resolvedFileName, originalPath } = resolution.resolvedTypeReferenceDirective;

From 8c439fab4b3c1274ed931f4edf84accc5507518a Mon Sep 17 00:00:00 2001
From: Andrew Branch <andrew@wheream.io>
Date: Tue, 8 Jun 2021 10:33:31 -0500
Subject: [PATCH 11/11] Revert fully filtering out ignored paths

---
 src/compiler/moduleSpecifiers.ts              | 13 ++-------
 src/compiler/types.ts                         |  1 -
 src/services/utilities.ts                     |  1 -
 .../importStatementCompletions_pnpm2.ts       | 29 -------------------
 4 files changed, 2 insertions(+), 42 deletions(-)
 delete mode 100644 tests/cases/fourslash/server/importStatementCompletions_pnpm2.ts

diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts
index 6e53588e56258..4aa77d573863d 100644
--- a/src/compiler/moduleSpecifiers.ts
+++ b/src/compiler/moduleSpecifiers.ts
@@ -163,16 +163,7 @@ namespace ts.moduleSpecifiers {
 
         return pathsSpecifiers?.length ? pathsSpecifiers :
             nodeModulesSpecifiers?.length ? nodeModulesSpecifiers :
-            fallback();
-
-        // Unless 'preferFailureToResultsWithIgnoredPaths' is set, we *must* generate a relative path fallback.
-        // (The declaration emitter must write something, but auto-imports may exclude bad suggestions.)
-        function fallback() {
-            if (host.preferFailureToResultsWithIgnoredPaths?.()) {
-                return relativeSpecifiers || emptyArray;
-            }
-            return Debug.checkDefined(relativeSpecifiers);
-        }
+            Debug.checkDefined(relativeSpecifiers);
     }
 
     interface Info {
@@ -295,7 +286,7 @@ namespace ts.moduleSpecifiers {
         const redirects = host.redirectTargetsMap.get(importedPath) || emptyArray;
         const importedFileNames = [...(referenceRedirect ? [referenceRedirect] : emptyArray), importedFileName, ...redirects];
         const targets = importedFileNames.map(f => getNormalizedAbsolutePath(f, cwd));
-        let shouldFilterIgnoredPaths = host.preferFailureToResultsWithIgnoredPaths?.() || !every(targets, containsIgnoredPath);
+        let shouldFilterIgnoredPaths = !every(targets, containsIgnoredPath);
 
         if (!preferSymlinks) {
             // Symlinks inside ignored paths are already filtered out of the symlink cache,
diff --git a/src/compiler/types.ts b/src/compiler/types.ts
index 11e7cecd225c2..d575631ca8b01 100644
--- a/src/compiler/types.ts
+++ b/src/compiler/types.ts
@@ -8136,7 +8136,6 @@ namespace ts {
         getModuleSpecifierCache?(): ModuleSpecifierCache;
         getGlobalTypingsCacheLocation?(): string | undefined;
         getNearestAncestorDirectoryWithPackageJson?(fileName: string, rootDir?: string): string | undefined;
-        preferFailureToResultsWithIgnoredPaths?(): boolean;
 
         readonly redirectTargetsMap: RedirectTargetsMap;
         getProjectReferenceRedirect(fileName: string): string | undefined;
diff --git a/src/services/utilities.ts b/src/services/utilities.ts
index 9b4395db32c64..2468ff0836fe5 100644
--- a/src/services/utilities.ts
+++ b/src/services/utilities.ts
@@ -1850,7 +1850,6 @@ namespace ts {
             isSourceOfProjectReferenceRedirect: fileName => program.isSourceOfProjectReferenceRedirect(fileName),
             getNearestAncestorDirectoryWithPackageJson: maybeBind(host, host.getNearestAncestorDirectoryWithPackageJson),
             getFileIncludeReasons: () => program.getFileIncludeReasons(),
-            preferFailureToResultsWithIgnoredPaths: returnTrue,
         };
     }
 
diff --git a/tests/cases/fourslash/server/importStatementCompletions_pnpm2.ts b/tests/cases/fourslash/server/importStatementCompletions_pnpm2.ts
deleted file mode 100644
index 5649c2447686b..0000000000000
--- a/tests/cases/fourslash/server/importStatementCompletions_pnpm2.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/// <reference path="../fourslash.ts" />
-
-// @Filename: /project/tsconfig.json
-//// { "compilerOptions": { "module": "commonjs" } }
-
-// @Filename: /project/node_modules/.pnpm/@types+react@17.0.7/node_modules/@types/react/index.d.ts
-//// import "csstype";
-//// export declare function Component(): void;
-
-// @Filename: /project/node_modules/.pnpm/csstype@3.0.8/node_modules/csstype/index.d.ts
-//// export interface SvgProperties {}
-
-// @Filename: /project/index.ts
-//// [|import SvgProp/**/|]
-
-// @link: /project/node_modules/.pnpm/@types+react@17.0.7/node_modules/@types/react -> /project/node_modules/@types/react
-// @link: /project/node_modules/.pnpm/csstype@3.0.8/node_modules/csstype -> /project/node_modules/.pnpm/@types+react@17.0.7/node_modules/csstype
-
-goTo.marker("");
-verify.completions({
-  isNewIdentifierLocation: true,
-  marker: "",
-  exact: [],
-  preferences: {
-    includeCompletionsForImportStatements: true,
-    includeCompletionsWithInsertText: true,
-    includeCompletionsWithSnippetText: true,
-  }
-});