mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 08:11:30 -06:00
Optimize import fixes for projects with many symlinks (#42150)
* Create symlink cache when a pnpm module is found * Keep pnpm-internal symlinks out of the symlink cache * Filter out pnpm path from realpath module specifier too * Optimize symlink module specifier generation * Add trailing directory separators * Remove unneeded change * Fix paths losing case in cache * Fix missing absolutification
This commit is contained in:
parent
9d286a21ae
commit
0383b5cb4c
@ -286,7 +286,8 @@ namespace ts.moduleSpecifiers {
|
||||
const getCanonicalFileName = hostGetCanonicalFileName(host);
|
||||
const cwd = host.getCurrentDirectory();
|
||||
const referenceRedirect = host.isSourceOfProjectReferenceRedirect(importedFileName) ? host.getProjectReferenceRedirect(importedFileName) : undefined;
|
||||
const redirects = host.redirectTargetsMap.get(toPath(importedFileName, cwd, getCanonicalFileName)) || emptyArray;
|
||||
const importedPath = toPath(importedFileName, cwd, getCanonicalFileName);
|
||||
const redirects = host.redirectTargetsMap.get(importedPath) || emptyArray;
|
||||
const importedFileNames = [...(referenceRedirect ? [referenceRedirect] : emptyArray), importedFileName, ...redirects];
|
||||
const targets = importedFileNames.map(f => getNormalizedAbsolutePath(f, cwd));
|
||||
if (!preferSymlinks) {
|
||||
@ -299,22 +300,25 @@ namespace ts.moduleSpecifiers {
|
||||
? host.getSymlinkCache()
|
||||
: discoverProbableSymlinks(host.getSourceFiles(), getCanonicalFileName, cwd);
|
||||
|
||||
const symlinkedDirectories = links.getSymlinkedDirectories();
|
||||
const useCaseSensitiveFileNames = !host.useCaseSensitiveFileNames || host.useCaseSensitiveFileNames();
|
||||
const result = symlinkedDirectories && forEachEntry(symlinkedDirectories, (resolved, path) => {
|
||||
if (resolved === false) return undefined;
|
||||
if (startsWithDirectory(importingFileName, resolved.realPath, getCanonicalFileName)) {
|
||||
return undefined; // Don't want to a package to globally import from itself
|
||||
const symlinkedDirectories = links.getSymlinkedDirectoriesByRealpath();
|
||||
const fullImportedFileName = getNormalizedAbsolutePath(importedFileName, cwd);
|
||||
const result = symlinkedDirectories && forEachAncestorDirectory(getDirectoryPath(fullImportedFileName), realPathDirectory => {
|
||||
const symlinkDirectories = symlinkedDirectories.get(ensureTrailingDirectorySeparator(toPath(realPathDirectory, cwd, getCanonicalFileName)));
|
||||
if (!symlinkDirectories) return undefined; // Continue to ancestor directory
|
||||
|
||||
// Don't want to a package to globally import from itself (importNameCodeFix_symlink_own_package.ts)
|
||||
if (startsWithDirectory(importingFileName, realPathDirectory, getCanonicalFileName)) {
|
||||
return false; // Stop search, each ancestor directory will also hit this condition
|
||||
}
|
||||
|
||||
return forEach(targets, target => {
|
||||
if (!containsPath(resolved.real, target, !useCaseSensitiveFileNames)) {
|
||||
if (!startsWithDirectory(target, realPathDirectory, getCanonicalFileName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const relative = getRelativePathFromDirectory(resolved.real, target, getCanonicalFileName);
|
||||
const option = resolvePath(path, relative);
|
||||
if (!host.fileExists || host.fileExists(option)) {
|
||||
const relative = getRelativePathFromDirectory(realPathDirectory, target, getCanonicalFileName);
|
||||
for (const symlinkDirectory of symlinkDirectories) {
|
||||
const option = resolvePath(symlinkDirectory, relative);
|
||||
const result = cb(option, target === referenceRedirect);
|
||||
if (result) return result;
|
||||
}
|
||||
|
||||
@ -762,7 +762,7 @@ namespace ts {
|
||||
* Determines whether `fileName` starts with the specified `directoryName` using the provided path canonicalization callback.
|
||||
* Comparison is case-sensitive between the canonical paths.
|
||||
*
|
||||
* @deprecated Use `containsPath` if possible.
|
||||
* Use `containsPath` if file names are not already reduced and absolute.
|
||||
*/
|
||||
export function startsWithDirectory(fileName: string, directoryName: string, getCanonicalFileName: GetCanonicalFileName): boolean {
|
||||
const canonicalFileName = getCanonicalFileName(fileName);
|
||||
|
||||
@ -3777,7 +3777,7 @@ namespace ts {
|
||||
return;
|
||||
}
|
||||
|
||||
symlinkCache.setSymlinkedDirectory(directoryPath, {
|
||||
symlinkCache.setSymlinkedDirectory(directory, {
|
||||
real: ensureTrailingDirectorySeparator(real),
|
||||
realPath
|
||||
});
|
||||
|
||||
@ -6083,32 +6083,43 @@ namespace ts {
|
||||
}
|
||||
|
||||
export interface SymlinkCache {
|
||||
/** Gets a map from symlink to realpath. Keys have trailing directory separators. */
|
||||
getSymlinkedDirectories(): ReadonlyESMap<Path, SymlinkedDirectory | false> | undefined;
|
||||
/** Gets a map from realpath to symlinks. Keys have trailing directory separators. */
|
||||
getSymlinkedDirectoriesByRealpath(): MultiMap<Path, string> | undefined;
|
||||
/** Gets a map from symlink to realpath */
|
||||
getSymlinkedFiles(): ReadonlyESMap<Path, string> | undefined;
|
||||
setSymlinkedDirectory(path: Path, directory: SymlinkedDirectory | false): void;
|
||||
setSymlinkedFile(path: Path, real: string): void;
|
||||
setSymlinkedDirectory(symlink: string, real: SymlinkedDirectory | false): void;
|
||||
setSymlinkedFile(symlinkPath: Path, real: string): void;
|
||||
}
|
||||
|
||||
export function createSymlinkCache(): SymlinkCache {
|
||||
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;
|
||||
return {
|
||||
getSymlinkedFiles: () => symlinkedFiles,
|
||||
getSymlinkedDirectories: () => symlinkedDirectories,
|
||||
getSymlinkedDirectoriesByRealpath: () => symlinkedDirectoriesByRealpath,
|
||||
setSymlinkedFile: (path, real) => (symlinkedFiles || (symlinkedFiles = new Map())).set(path, real),
|
||||
setSymlinkedDirectory: (path, directory) => {
|
||||
setSymlinkedDirectory: (symlink, real) => {
|
||||
// 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);
|
||||
let symlinkPath = toPath(symlink, cwd, getCanonicalFileName);
|
||||
if (!containsIgnoredPath(symlinkPath)) {
|
||||
symlinkPath = ensureTrailingDirectorySeparator(symlinkPath);
|
||||
if (real !== false && !symlinkedDirectories?.has(symlinkPath)) {
|
||||
(symlinkedDirectoriesByRealpath ||= createMultiMap()).add(ensureTrailingDirectorySeparator(real.realPath), symlink);
|
||||
}
|
||||
(symlinkedDirectories || (symlinkedDirectories = new Map())).set(symlinkPath, real);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function discoverProbableSymlinks(files: readonly SourceFile[], getCanonicalFileName: GetCanonicalFileName, cwd: string): SymlinkCache {
|
||||
const cache = createSymlinkCache();
|
||||
const cache = createSymlinkCache(cwd, getCanonicalFileName);
|
||||
const symlinks = flatten<readonly [string, string]>(mapDefined(files, sf =>
|
||||
sf.resolvedModules && compact(arrayFrom(mapIterator(sf.resolvedModules.values(), res =>
|
||||
res && res.originalPath && res.resolvedFileName !== res.originalPath ? [res.resolvedFileName, res.originalPath] as const : undefined)))));
|
||||
@ -6116,7 +6127,7 @@ namespace ts {
|
||||
const [commonResolved, commonOriginal] = guessDirectorySymlink(resolvedPath, originalPath, cwd, getCanonicalFileName) || emptyArray;
|
||||
if (commonResolved && commonOriginal) {
|
||||
cache.setSymlinkedDirectory(
|
||||
toPath(commonOriginal, cwd, getCanonicalFileName),
|
||||
commonOriginal,
|
||||
{ real: commonResolved, realPath: toPath(commonResolved, cwd, getCanonicalFileName) });
|
||||
}
|
||||
}
|
||||
@ -6124,8 +6135,8 @@ namespace ts {
|
||||
}
|
||||
|
||||
function guessDirectorySymlink(a: string, b: string, cwd: string, getCanonicalFileName: GetCanonicalFileName): [string, string] | undefined {
|
||||
const aParts = getPathComponents(toPath(a, cwd, getCanonicalFileName));
|
||||
const bParts = getPathComponents(toPath(b, cwd, getCanonicalFileName));
|
||||
const aParts = getPathComponents(getNormalizedAbsolutePath(a, cwd));
|
||||
const bParts = getPathComponents(getNormalizedAbsolutePath(b, cwd));
|
||||
let isDirectory = false;
|
||||
while (!isNodeModulesOrScopedPackageDirectory(aParts[aParts.length - 2], getCanonicalFileName) &&
|
||||
!isNodeModulesOrScopedPackageDirectory(bParts[bParts.length - 2], getCanonicalFileName) &&
|
||||
|
||||
21
tests/cases/fourslash/autoImportSymlinkCaseSensitive.ts
Normal file
21
tests/cases/fourslash/autoImportSymlinkCaseSensitive.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: /tsconfig.json
|
||||
//// { "compilerOptions": { "module": "commonjs" } }
|
||||
|
||||
// @Filename: /node_modules/.pnpm/mobx@6.0.4/node_modules/MobX/Foo.d.ts
|
||||
//// export declare function autorun(): void;
|
||||
|
||||
// @Filename: /index.ts
|
||||
//// autorun/**/
|
||||
|
||||
// @Filename: /utils.ts
|
||||
//// import "MobX/Foo";
|
||||
|
||||
// @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/Foo";
|
||||
|
||||
autorun`]);
|
||||
Loading…
x
Reference in New Issue
Block a user