diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index d2fc45ee9913d..45f2edf3df988 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -278,7 +278,9 @@ namespace ts.moduleSpecifiers { const importedFileNames = [...(referenceRedirect ? [referenceRedirect] : emptyArray), importedFileName, ...redirects]; const targets = importedFileNames.map(f => getNormalizedAbsolutePath(f, cwd)); if (!preferSymlinks) { - const result = forEach(targets, p => cb(p, referenceRedirect === p)); + // Symlinks inside ignored paths are already filtered out of the symlink cache, + // so we only need to remove them from the realpath filenames. + const result = forEach(targets, p => !containsIgnoredPath(p) && cb(p, referenceRedirect === p)); if (result) return result; } const links = host.getSymlinkCache @@ -306,8 +308,9 @@ namespace ts.moduleSpecifiers { } }); }); - return result || - (preferSymlinks ? forEach(targets, p => cb(p, p === referenceRedirect)) : undefined); + return result || (preferSymlinks + ? forEach(targets, p => containsIgnoredPath(p) ? undefined : cb(p, p === referenceRedirect)) + : undefined); } interface ModulePath { diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 7b71f011700c1..45c541945487a 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -3760,7 +3760,7 @@ namespace ts { } function handleDirectoryCouldBeSymlink(directory: string) { - if (!host.getResolvedProjectReferences()) return; + if (!host.getResolvedProjectReferences() || containsIgnoredPath(directory)) return; // Because we already watch node_modules, handle symlinks in there if (!originalRealpath || !stringContains(directory, nodeModulesPathPart)) return; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 12b815995cfa7..cc383919dc306 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -6104,7 +6104,14 @@ namespace ts { getSymlinkedFiles: () => symlinkedFiles, getSymlinkedDirectories: () => symlinkedDirectories, setSymlinkedFile: (path, real) => (symlinkedFiles || (symlinkedFiles = new Map())).set(path, real), - setSymlinkedDirectory: (path, directory) => (symlinkedDirectories || (symlinkedDirectories = new Map())).set(path, directory), + setSymlinkedDirectory: (path, directory) => { + // Large, interconnected dependency graphs in pnpm will have a huge number of symlinks + // where both the realpath and the symlink path are inside node_modules/.pnpm. Since + // this path is never a candidate for a module specifier, we can ignore it entirely. + if (!containsIgnoredPath(path)) { + (symlinkedDirectories || (symlinkedDirectories = new Map())).set(path, directory); + } + } }; } @@ -7067,4 +7074,8 @@ namespace ts { return false; } } + + export function containsIgnoredPath(path: string) { + return some(ignoredPaths, p => stringContains(path, p)); + } } diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 551ac9aef1889..48be5004d3cb2 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -3022,7 +3022,7 @@ namespace FourSlash { this.editScriptAndUpdateMarkers(fileName, span.start, span.start + insertedText.length, deletedText); } if (expectedTextArray.length !== actualTextArray.length) { - this.raiseError(`Expected ${expectedTextArray.length} import fixes, got ${actualTextArray.length}`); + this.raiseError(`Expected ${expectedTextArray.length} import fixes, got ${actualTextArray.length}:\n\n${actualTextArray.join("\n\n" + "-".repeat(20) + "\n\n")}`); } ts.zipWith(expectedTextArray, actualTextArray, (expected, actual, index) => { if (expected !== actual) { diff --git a/tests/cases/fourslash/autoImportPnpm.ts b/tests/cases/fourslash/autoImportPnpm.ts new file mode 100644 index 0000000000000..91d52ab798406 --- /dev/null +++ b/tests/cases/fourslash/autoImportPnpm.ts @@ -0,0 +1,24 @@ +/// + +// @Filename: /tsconfig.json +//// { "compilerOptions": { "module": "commonjs" } } + +// @Filename: /node_modules/.pnpm/mobx@6.0.4/node_modules/mobx/package.json +//// { "types": "dist/mobx.d.ts" } + +// @Filename: /node_modules/.pnpm/mobx@6.0.4/node_modules/mobx/dist/mobx.d.ts +//// export declare function autorun(): void; + +// @Filename: /index.ts +//// autorun/**/ + +// @Filename: /utils.ts +//// import "mobx"; + +// @link: /node_modules/.pnpm/mobx@6.0.4/node_modules/mobx -> /node_modules/mobx +// @link: /node_modules/.pnpm/mobx@6.0.4/node_modules/mobx -> /node_modules/.pnpm/cool-mobx-dependent@1.2.3/node_modules/mobx + +goTo.marker(""); +verify.importFixAtPosition([`import { autorun } from "mobx"; + +autorun`]);