Skip to content

Fix package.json auto imports for pnpm without project references #43892

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 6, 2021
17 changes: 12 additions & 5 deletions src/compiler/moduleNameResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,12 @@ namespace ts {
packageId: PackageId | undefined;
/**
* When the resolved is not created from cache, the value is
* - string if original Path if it is symbolic link to the resolved path
* - undefined if path is not a symbolic link
* - string if it is symbolic link to the resolved `path`
* - undefined if `path` is not a symbolic link
* When the resolved is created using value from cache of ResolvedModuleWithFailedLookupLocations, the value is:
* - string if original Path if it is symbolic link to the resolved path
* - true if path is not a symbolic link - this indicates that the originalPath calculation is already done and needs to be skipped
* - string if it is symbolic link to the resolved `path`
* - true if `path` is not a symbolic link - this indicates that the `originalPath` calculation is already done and needs to be skipped
* Note: This is a file name with preserved original casing, not a normalized `Path`.
*/
originalPath?: string | true;
}
Expand Down Expand Up @@ -339,7 +340,13 @@ namespace ts {
if (resolved) {
const { fileName, packageId } = resolved;
const resolvedFileName = options.preserveSymlinks ? fileName : realPath(fileName, host, traceEnabled);
resolvedTypeReferenceDirective = { primary, resolvedFileName, packageId, isExternalLibraryImport: pathContainsNodeModules(fileName) };
resolvedTypeReferenceDirective = {
primary,
resolvedFileName,
originalPath: fileName === resolvedFileName ? undefined : fileName,
packageId,
isExternalLibraryImport: pathContainsNodeModules(fileName),
};
}
result = { resolvedTypeReferenceDirective, failedLookupLocations };
perFolderCache?.set(typeReferenceDirectiveName, result);
Expand Down
11 changes: 10 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6407,7 +6407,10 @@ namespace ts {
* If changing this, remember to change `moduleResolutionIsEqualTo`.
*/
export interface ResolvedModuleFull extends ResolvedModule {
/* @internal */
/**
* @internal
* This is a file name with preserved original casing, not a normalized `Path`.
*/
readonly originalPath?: string;
/**
* Extension of resolvedFileName. This must match what's at the end of resolvedFileName.
Expand Down Expand Up @@ -6458,6 +6461,12 @@ namespace ts {
primary: boolean;
// The location of the .d.ts file we located, or undefined if resolution failed
resolvedFileName: string | undefined;
/**
* @internal
* The location of the symlink to the .d.ts file we found, if `resolvedFileName` was the realpath.
* This is a file name with preserved original casing, not a normalized `Path`.
*/
originalPath?: string;
packageId?: PackageId;
/** True if `resolvedFileName` comes from `node_modules`. */
isExternalLibraryImport?: boolean;
Expand Down
29 changes: 24 additions & 5 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,9 @@ namespace ts {
}

export function typeDirectiveIsEqualTo(oldResolution: ResolvedTypeReferenceDirective, newResolution: ResolvedTypeReferenceDirective): boolean {
return oldResolution.resolvedFileName === newResolution.resolvedFileName && oldResolution.primary === newResolution.primary;
return oldResolution.resolvedFileName === newResolution.resolvedFileName
&& oldResolution.primary === newResolution.primary
&& oldResolution.originalPath === newResolution.originalPath;
}

export function hasChangesInResolutions<T>(
Expand Down Expand Up @@ -6145,6 +6147,8 @@ namespace ts {
getSymlinkedFiles(): ReadonlyESMap<Path, string> | undefined;
setSymlinkedDirectory(symlink: string, real: SymlinkedDirectory | false): void;
setSymlinkedFile(symlinkPath: Path, real: string): void;
/*@internal*/
setSymlinkedDirectoryFromSymlinkedFile(symlink: string, real: string): void;
}

export function createSymlinkCache(cwd: string, getCanonicalFileName: GetCanonicalFileName): SymlinkCache {
Expand All @@ -6168,16 +6172,31 @@ namespace ts {
}
(symlinkedDirectories || (symlinkedDirectories = new Map())).set(symlinkPath, real);
}
}
},
setSymlinkedDirectoryFromSymlinkedFile(symlink, real) {
this.setSymlinkedFile(toPath(symlink, cwd, getCanonicalFileName), real);
const [commonResolved, commonOriginal] = guessDirectorySymlink(real, symlink, cwd, getCanonicalFileName) || emptyArray;
if (commonResolved && commonOriginal) {
this.setSymlinkedDirectory(commonOriginal, {
real: commonResolved,
realPath: toPath(commonResolved, cwd, getCanonicalFileName),
});
}
},
};
}

export function discoverProbableSymlinks(files: readonly SourceFile[], getCanonicalFileName: GetCanonicalFileName, cwd: string): SymlinkCache {
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)))));
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)));
});

for (const [resolvedPath, originalPath] of symlinks) {
cache.setSymlinkedFile(toPath(originalPath, cwd, getCanonicalFileName), resolvedPath);
const [commonResolved, commonOriginal] = guessDirectorySymlink(resolvedPath, originalPath, cwd, getCanonicalFileName) || emptyArray;
if (commonResolved && commonOriginal) {
cache.setSymlinkedDirectory(
Expand Down
12 changes: 8 additions & 4 deletions src/server/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1901,16 +1901,20 @@ namespace ts.server {
compilerOptions,
moduleResolutionHost));

const program = hostProject.getCurrentProgram()!;
const symlinkCache = hostProject.getSymlinkCache();
for (const resolution of resolutions) {
if (!resolution.resolvedTypeReferenceDirective?.resolvedFileName) continue;
const { resolvedFileName } = resolution.resolvedTypeReferenceDirective;
const fileName = moduleResolutionHost.realpath?.(resolvedFileName) || resolvedFileName;
if (!hostProject.getCurrentProgram()!.getSourceFile(fileName) && !hostProject.getCurrentProgram()!.getSourceFile(resolvedFileName)) {
rootNames = append(rootNames, fileName);
const { resolvedFileName, originalPath } = resolution.resolvedTypeReferenceDirective;
if (!program.getSourceFile(resolvedFileName) && (!originalPath || !program.getSourceFile(originalPath))) {
rootNames = append(rootNames, resolvedFileName);
// Avoid creating a large project that would significantly slow down time to editor interactivity
if (dependencySelection === PackageJsonAutoImportPreference.Auto && rootNames.length > this.maxDependencies) {
return ts.emptyArray;
}
if (originalPath) {
symlinkCache.setSymlinkedDirectoryFromSymlinkedFile(originalPath, resolvedFileName);
}
}
}
}
Expand Down
21 changes: 21 additions & 0 deletions tests/cases/fourslash/server/autoImportProvider_pnpm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/// <reference path="../fourslash.ts" />

// @Filename: /tsconfig.json
//// { "compilerOptions": { "module": "commonjs" } }

// @Filename: /package.json
//// { "dependencies": { "mobx": "*" } }

// @Filename: /node_modules/.pnpm/[email protected]/node_modules/mobx/package.json
//// { "types": "dist/mobx.d.ts" }

// @Filename: /node_modules/.pnpm/[email protected]/node_modules/mobx/dist/mobx.d.ts
//// export declare function autorun(): void;

// @Filename: /index.ts
//// autorun/**/

// @link: /node_modules/.pnpm/[email protected]/node_modules/mobx -> /node_modules/mobx

goTo.marker("");
verify.importFixAtPosition([`import { autorun } from "mobx";\r\n\r\nautorun`]);