Skip to content

Commit

Permalink
Handle peer dependencies in pnpm layout
Browse files Browse the repository at this point in the history
  • Loading branch information
sheetalkamat committed Jan 11, 2024
1 parent f5d0ef0 commit 98e72b0
Show file tree
Hide file tree
Showing 25 changed files with 239 additions and 35 deletions.
16 changes: 16 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5447,6 +5447,22 @@
"category": "Message",
"code": 6278
},
"'package.json' has a 'peerDependencies' field.": {
"category": "Message",
"code": 6279
},
"Package is not installed using pnpm, skipping peerDependencies version resolution.": {
"category": "Message",
"code": 6280
},
"Found peerDependency '{0}' with '{1}' version.": {
"category": "Message",
"code": 6281
},
"Failed to find peerDependency '{0}'.": {
"category": "Message",
"code": 6282
},

"Enable project compilation": {
"category": "Message",
Expand Down
64 changes: 53 additions & 11 deletions src/compiler/moduleNameResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export function isTraceEnabled(compilerOptions: CompilerOptions, host: ModuleRes
return !!compilerOptions.traceResolution && host.trace !== undefined;
}

function withPackageId(packageInfo: PackageJsonInfo | undefined, r: PathAndExtension | undefined): Resolved | undefined {
function withPackageId(packageInfo: PackageJsonInfo | undefined, r: PathAndExtension | undefined, state: ModuleResolutionState): Resolved | undefined {
let packageId: PackageId | undefined;
if (r && packageInfo) {
const packageJsonContent = packageInfo.contents.packageJsonContent as PackageJson;
Expand All @@ -130,14 +130,15 @@ function withPackageId(packageInfo: PackageJsonInfo | undefined, r: PathAndExten
name: packageJsonContent.name,
subModuleName: r.path.slice(packageInfo.packageDirectory.length + directorySeparator.length),
version: packageJsonContent.version,
peerDependencies: getPeerDependenciesOfPackageJsonInfo(packageInfo, state),
};
}
}
return r && { path: r.path, extension: r.ext, packageId, resolvedUsingTsExtension: r.resolvedUsingTsExtension };
}

function noPackageId(r: PathAndExtension | undefined): Resolved | undefined {
return withPackageId(/*packageInfo*/ undefined, r);
return withPackageId(/*packageInfo*/ undefined, r, /*state*/ undefined!); // State will not be used so no need to pass
}

function removeIgnoredPackageId(r: Resolved | undefined): PathAndExtension | undefined {
Expand Down Expand Up @@ -346,6 +347,7 @@ export interface PackageJsonPathFields {
interface PackageJson extends PackageJsonPathFields {
name?: string;
version?: string;
peerDependencies?: MapLike<string>;
}

function readPackageJsonField<TMatch, K extends MatchingKeys<PackageJson, string | undefined>>(jsonContent: PackageJson, fieldName: K, typeOfTag: "string", state: ModuleResolutionState): PackageJson[K] | undefined;
Expand Down Expand Up @@ -668,7 +670,7 @@ export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string
if (resolvedFromFile) {
const packageDirectory = parseNodeModuleFromPath(resolvedFromFile.path);
const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, moduleResolutionState) : undefined;
return resolvedTypeScriptOnly(withPackageId(packageInfo, resolvedFromFile));
return resolvedTypeScriptOnly(withPackageId(packageInfo, resolvedFromFile, moduleResolutionState));
}
}
return resolvedTypeScriptOnly(
Expand Down Expand Up @@ -1977,7 +1979,7 @@ function nodeLoadModuleByRelativeName(extensions: Extensions, candidate: string,
if (resolvedFromFile) {
const packageDirectory = considerPackageJson ? parseNodeModuleFromPath(resolvedFromFile.path) : undefined;
const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, state) : undefined;
return withPackageId(packageInfo, resolvedFromFile);
return withPackageId(packageInfo, resolvedFromFile, state);
}
}
if (!onlyRecordFailures) {
Expand Down Expand Up @@ -2194,7 +2196,7 @@ function loadNodeModuleFromDirectory(extensions: Extensions, candidate: string,
const packageInfo = considerPackageJson ? getPackageJsonInfo(candidate, onlyRecordFailures, state) : undefined;
const packageJsonContent = packageInfo && packageInfo.contents.packageJsonContent;
const versionPaths = packageInfo && getVersionPathsOfPackageJsonInfo(packageInfo, state);
return withPackageId(packageInfo, loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths));
return withPackageId(packageInfo, loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths), state);
}

/** @internal */
Expand Down Expand Up @@ -2365,6 +2367,8 @@ export interface PackageJsonInfoContents {
versionPaths: VersionPaths | false | undefined;
/** false: resolved to nothing. undefined: not yet resolved */
resolvedEntrypoints: string[] | false | undefined;
/** false: peerDependencies are not present. undefined: not yet resolved */
peerDependencies: string | false | undefined;
}

/**
Expand Down Expand Up @@ -2392,6 +2396,44 @@ function getVersionPathsOfPackageJsonInfo(packageJsonInfo: PackageJsonInfo, stat
return packageJsonInfo.contents.versionPaths || undefined;
}

function getPeerDependenciesOfPackageJsonInfo(packageJsonInfo: PackageJsonInfo, state: ModuleResolutionState): string | undefined {
if (packageJsonInfo.contents.peerDependencies === undefined) {
packageJsonInfo.contents.peerDependencies = readPackageJsonPeerDependencies(packageJsonInfo, state) || false;
}
return packageJsonInfo.contents.peerDependencies || undefined;
}

function readPackageJsonPeerDependencies(packageJsonInfo: PackageJsonInfo, state: ModuleResolutionState): string | undefined {
const peerDependencies = readPackageJsonField(packageJsonInfo.contents.packageJsonContent, "peerDependencies", "object", state);
if (peerDependencies === undefined) return undefined;
if (state.traceEnabled) trace(state.host, Diagnostics.package_json_has_a_peerDependencies_field);
const packageDirectory = realPath(packageJsonInfo.packageDirectory, state.host, state.traceEnabled);
if (packageDirectory.indexOf(".pnpm") === -1) {
// For now skip extra work for anything else
if (state.traceEnabled) trace(state.host, Diagnostics.Package_is_not_installed_using_pnpm_skipping_peerDependencies_version_resolution);
return undefined;
}
const nodeModules = packageDirectory.substring(0, packageDirectory.lastIndexOf("node_modules") + "node_modules".length) + directorySeparator;
let result = "";
if (state.traceEnabled) {
for (const key in peerDependencies) {
if (hasProperty(peerDependencies, key)) {
const peerPackageJson = getPackageJsonInfo(nodeModules + key, /*onlyRecordFailures*/ false, state);
if (peerPackageJson) {
const version = (peerPackageJson.contents.packageJsonContent as PackageJson).version;
result += `+${key}@${version}`;
if (state.traceEnabled) trace(state.host, Diagnostics.Found_peerDependency_0_with_1_version, key, version);
}
else {
// Read the dependency version
if (state.traceEnabled) trace(state.host, Diagnostics.Failed_to_find_peerDependency_0, key);
}
}
}
}
return result;
}

/** @internal */
export function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined {
const { host, traceEnabled } = state;
Expand Down Expand Up @@ -2422,7 +2464,7 @@ export function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures:
if (traceEnabled) {
trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath);
}
const result: PackageJsonInfo = { packageDirectory, contents: { packageJsonContent, versionPaths: undefined, resolvedEntrypoints: undefined } };
const result: PackageJsonInfo = { packageDirectory, contents: { packageJsonContent, versionPaths: undefined, resolvedEntrypoints: undefined, peerDependencies: undefined } };
if (state.packageJsonInfoCache && !state.packageJsonInfoCache.isReadonly) state.packageJsonInfoCache.setPackageJsonInfo(packageJsonPath, result);
state.affectingLocations?.push(packageJsonPath);
return result;
Expand Down Expand Up @@ -2760,7 +2802,7 @@ function getLoadModuleFromTargetImportOrExport(extensions: Extensions, state: Mo
const finalPath = toAbsolutePath(pattern ? resolvedTarget.replace(/\*/g, subpath) : resolvedTarget + subpath);
const inputLink = tryLoadInputFileForPath(finalPath, subpath, combinePaths(scope.packageDirectory, "package.json"), isImports);
if (inputLink) return inputLink;
return toSearchResult(withPackageId(scope, loadFileNameFromPackageJsonField(extensions, finalPath, /*onlyRecordFailures*/ false, state)));
return toSearchResult(withPackageId(scope, loadFileNameFromPackageJsonField(extensions, finalPath, /*onlyRecordFailures*/ false, state), state));
}
else if (typeof target === "object" && target !== null) { // eslint-disable-line no-null/no-null
if (!Array.isArray(target)) {
Expand Down Expand Up @@ -2906,7 +2948,7 @@ function getLoadModuleFromTargetImportOrExport(extensions: Extensions, state: Mo
if (!extensionIsOk(extensions, possibleExt)) continue;
const possibleInputWithInputExtension = changeAnyExtension(possibleInputBase, possibleExt, ext, !useCaseSensitiveFileNames(state));
if (state.host.fileExists(possibleInputWithInputExtension)) {
return toSearchResult(withPackageId(scope, loadFileNameFromPackageJsonField(extensions, possibleInputWithInputExtension, /*onlyRecordFailures*/ false, state)));
return toSearchResult(withPackageId(scope, loadFileNameFromPackageJsonField(extensions, possibleInputWithInputExtension, /*onlyRecordFailures*/ false, state), state));
}
}
}
Expand Down Expand Up @@ -3042,7 +3084,7 @@ function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, modu
packageInfo.contents.packageJsonContent,
getVersionPathsOfPackageJsonInfo(packageInfo, state),
);
return withPackageId(packageInfo, fromDirectory);
return withPackageId(packageInfo, fromDirectory, state);
}

const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => {
Expand All @@ -3065,7 +3107,7 @@ function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, modu
// a default `index.js` entrypoint if no `main` or `exports` are present
pathAndExtension = loadModuleFromFile(extensions, combinePaths(candidate, "index.js"), onlyRecordFailures, state);
}
return withPackageId(packageInfo, pathAndExtension);
return withPackageId(packageInfo, pathAndExtension, state);
};

if (rest !== "") {
Expand Down Expand Up @@ -3261,7 +3303,7 @@ function resolveFromTypeRoot(moduleName: string, state: ModuleResolutionState) {
if (resolvedFromFile) {
const packageDirectory = parseNodeModuleFromPath(resolvedFromFile.path);
const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, state) : undefined;
return toSearchResult(withPackageId(packageInfo, resolvedFromFile));
return toSearchResult(withPackageId(packageInfo, resolvedFromFile, state));
}
const resolved = loadNodeModuleFromDirectory(Extensions.Declaration, candidate, !directoryExists, state);
if (resolved) return toSearchResult(resolved);
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7724,6 +7724,7 @@ export interface PackageId {
subModuleName: string;
/** Version of the package, e.g. "1.2.3" */
version: string;
peerDependencies?: string;
}

export const enum Extension {
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,7 @@ export function createModuleNotFoundChain(sourceFile: SourceFile, host: TypeChec
}

function packageIdIsEqual(a: PackageId | undefined, b: PackageId | undefined): boolean {
return a === b || !!a && !!b && a.name === b.name && a.subModuleName === b.subModuleName && a.version === b.version;
return a === b || !!a && !!b && a.name === b.name && a.subModuleName === b.subModuleName && a.version === b.version && a.peerDependencies === b.peerDependencies;
}

/** @internal */
Expand All @@ -815,7 +815,7 @@ export function packageIdToPackageName({ name, subModuleName }: PackageId): stri

/** @internal */
export function packageIdToString(packageId: PackageId): string {
return `${packageIdToPackageName(packageId)}@${packageId.version}`;
return `${packageIdToPackageName(packageId)}@${packageId.version}${packageId.peerDependencies ?? ""}`;
}

/** @internal */
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7791,6 +7791,7 @@ declare namespace ts {
subModuleName: string;
/** Version of the package, e.g. "1.2.3" */
version: string;
peerDependencies?: string;
}
enum Extension {
Ts = ".ts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ File name '/user/username/projects/myproject/node_modules/pkg2/build/index.js' h
File '/user/username/projects/myproject/node_modules/pkg2/build/index.ts' does not exist.
File '/user/username/projects/myproject/node_modules/pkg2/build/index.tsx' does not exist.
File '/user/username/projects/myproject/node_modules/pkg2/build/index.d.ts' exists - use it as a name resolution result.
'package.json' does not have a 'peerDependencies' field.
======== Module name 'pkg2' was successfully resolved to '/user/username/projects/myproject/node_modules/pkg2/build/index.d.ts' with Package ID 'pkg2/build/index.d.ts@1.0.0'. ========
======== Resolving module 'const' from '/user/username/projects/myproject/node_modules/pkg2/build/index.d.ts'. ========
Using compiler options of project reference redirect '/user/username/projects/myproject/packages/pkg2/tsconfig.json'.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ File name '/user/username/projects/myproject/node_modules/pkg2/build/index.js' h
File '/user/username/projects/myproject/node_modules/pkg2/build/index.ts' does not exist.
File '/user/username/projects/myproject/node_modules/pkg2/build/index.tsx' does not exist.
File '/user/username/projects/myproject/node_modules/pkg2/build/index.d.ts' exists - use it as a name resolution result.
'package.json' does not have a 'peerDependencies' field.
Resolving real path for '/user/username/projects/myproject/node_modules/pkg2/build/index.d.ts', result '/user/username/projects/myproject/packages/pkg2/build/index.d.ts'.
======== Module name 'pkg2' was successfully resolved to '/user/username/projects/myproject/packages/pkg2/build/index.d.ts' with Package ID 'pkg2/build/index.d.ts@1.0.0'. ========
======== Resolving module 'const' from '/user/username/projects/myproject/packages/pkg2/build/index.d.ts'. ========
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ File name '/user/username/projects/myproject/node_modules/pkg2/build/index.js' h
File '/user/username/projects/myproject/node_modules/pkg2/build/index.ts' does not exist.
File '/user/username/projects/myproject/node_modules/pkg2/build/index.tsx' does not exist.
File '/user/username/projects/myproject/node_modules/pkg2/build/index.d.ts' exists - use it as a name resolution result.
'package.json' does not have a 'peerDependencies' field.
Resolving real path for '/user/username/projects/myproject/node_modules/pkg2/build/index.d.ts', result '/user/username/projects/myproject/packages/pkg2/build/index.d.ts'.
======== Module name 'pkg2' was successfully resolved to '/user/username/projects/myproject/packages/pkg2/build/index.d.ts' with Package ID 'pkg2/build/index.d.ts@1.0.0'. ========
======== Resolving module './const.js' from '/user/username/projects/myproject/packages/pkg2/build/index.d.ts'. ========
Expand Down Expand Up @@ -379,6 +380,7 @@ File name '/user/username/projects/myproject/node_modules/pkg2/build/other.js' h
File '/user/username/projects/myproject/node_modules/pkg2/build/other.ts' does not exist.
File '/user/username/projects/myproject/node_modules/pkg2/build/other.tsx' does not exist.
File '/user/username/projects/myproject/node_modules/pkg2/build/other.d.ts' exists - use it as a name resolution result.
'package.json' does not have a 'peerDependencies' field.
Resolving real path for '/user/username/projects/myproject/node_modules/pkg2/build/other.d.ts', result '/user/username/projects/myproject/packages/pkg2/build/other.d.ts'.
======== Module name 'pkg2' was successfully resolved to '/user/username/projects/myproject/packages/pkg2/build/other.d.ts' with Package ID 'pkg2/build/other.d.ts@1.0.0'. ========
packages/pkg1/index.ts:1:15 - error TS2305: Module '"pkg2"' has no exported member 'TheNum'.
Expand Down Expand Up @@ -464,6 +466,7 @@ File name '/user/username/projects/myproject/node_modules/pkg2/build/index.js' h
File '/user/username/projects/myproject/node_modules/pkg2/build/index.ts' does not exist.
File '/user/username/projects/myproject/node_modules/pkg2/build/index.tsx' does not exist.
File '/user/username/projects/myproject/node_modules/pkg2/build/index.d.ts' exists - use it as a name resolution result.
'package.json' does not have a 'peerDependencies' field.
Resolving real path for '/user/username/projects/myproject/node_modules/pkg2/build/index.d.ts', result '/user/username/projects/myproject/packages/pkg2/build/index.d.ts'.
======== Module name 'pkg2' was successfully resolved to '/user/username/projects/myproject/packages/pkg2/build/index.d.ts' with Package ID 'pkg2/build/index.d.ts@1.0.0'. ========
======== Resolving module './const.js' from '/user/username/projects/myproject/packages/pkg2/build/index.d.ts'. ========
Expand Down

0 comments on commit 98e72b0

Please sign in to comment.