Skip to content

Commit 98e72b0

Browse files
committed
Handle peer dependencies in pnpm layout
1 parent f5d0ef0 commit 98e72b0

File tree

25 files changed

+239
-35
lines changed

25 files changed

+239
-35
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5447,6 +5447,22 @@
54475447
"category": "Message",
54485448
"code": 6278
54495449
},
5450+
"'package.json' has a 'peerDependencies' field.": {
5451+
"category": "Message",
5452+
"code": 6279
5453+
},
5454+
"Package is not installed using pnpm, skipping peerDependencies version resolution.": {
5455+
"category": "Message",
5456+
"code": 6280
5457+
},
5458+
"Found peerDependency '{0}' with '{1}' version.": {
5459+
"category": "Message",
5460+
"code": 6281
5461+
},
5462+
"Failed to find peerDependency '{0}'.": {
5463+
"category": "Message",
5464+
"code": 6282
5465+
},
54505466

54515467
"Enable project compilation": {
54525468
"category": "Message",

src/compiler/moduleNameResolver.ts

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export function isTraceEnabled(compilerOptions: CompilerOptions, host: ModuleRes
121121
return !!compilerOptions.traceResolution && host.trace !== undefined;
122122
}
123123

124-
function withPackageId(packageInfo: PackageJsonInfo | undefined, r: PathAndExtension | undefined): Resolved | undefined {
124+
function withPackageId(packageInfo: PackageJsonInfo | undefined, r: PathAndExtension | undefined, state: ModuleResolutionState): Resolved | undefined {
125125
let packageId: PackageId | undefined;
126126
if (r && packageInfo) {
127127
const packageJsonContent = packageInfo.contents.packageJsonContent as PackageJson;
@@ -130,14 +130,15 @@ function withPackageId(packageInfo: PackageJsonInfo | undefined, r: PathAndExten
130130
name: packageJsonContent.name,
131131
subModuleName: r.path.slice(packageInfo.packageDirectory.length + directorySeparator.length),
132132
version: packageJsonContent.version,
133+
peerDependencies: getPeerDependenciesOfPackageJsonInfo(packageInfo, state),
133134
};
134135
}
135136
}
136137
return r && { path: r.path, extension: r.ext, packageId, resolvedUsingTsExtension: r.resolvedUsingTsExtension };
137138
}
138139

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

143144
function removeIgnoredPackageId(r: Resolved | undefined): PathAndExtension | undefined {
@@ -346,6 +347,7 @@ export interface PackageJsonPathFields {
346347
interface PackageJson extends PackageJsonPathFields {
347348
name?: string;
348349
version?: string;
350+
peerDependencies?: MapLike<string>;
349351
}
350352

351353
function readPackageJsonField<TMatch, K extends MatchingKeys<PackageJson, string | undefined>>(jsonContent: PackageJson, fieldName: K, typeOfTag: "string", state: ModuleResolutionState): PackageJson[K] | undefined;
@@ -668,7 +670,7 @@ export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string
668670
if (resolvedFromFile) {
669671
const packageDirectory = parseNodeModuleFromPath(resolvedFromFile.path);
670672
const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, moduleResolutionState) : undefined;
671-
return resolvedTypeScriptOnly(withPackageId(packageInfo, resolvedFromFile));
673+
return resolvedTypeScriptOnly(withPackageId(packageInfo, resolvedFromFile, moduleResolutionState));
672674
}
673675
}
674676
return resolvedTypeScriptOnly(
@@ -1977,7 +1979,7 @@ function nodeLoadModuleByRelativeName(extensions: Extensions, candidate: string,
19771979
if (resolvedFromFile) {
19781980
const packageDirectory = considerPackageJson ? parseNodeModuleFromPath(resolvedFromFile.path) : undefined;
19791981
const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, state) : undefined;
1980-
return withPackageId(packageInfo, resolvedFromFile);
1982+
return withPackageId(packageInfo, resolvedFromFile, state);
19811983
}
19821984
}
19831985
if (!onlyRecordFailures) {
@@ -2194,7 +2196,7 @@ function loadNodeModuleFromDirectory(extensions: Extensions, candidate: string,
21942196
const packageInfo = considerPackageJson ? getPackageJsonInfo(candidate, onlyRecordFailures, state) : undefined;
21952197
const packageJsonContent = packageInfo && packageInfo.contents.packageJsonContent;
21962198
const versionPaths = packageInfo && getVersionPathsOfPackageJsonInfo(packageInfo, state);
2197-
return withPackageId(packageInfo, loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths));
2199+
return withPackageId(packageInfo, loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths), state);
21982200
}
21992201

22002202
/** @internal */
@@ -2365,6 +2367,8 @@ export interface PackageJsonInfoContents {
23652367
versionPaths: VersionPaths | false | undefined;
23662368
/** false: resolved to nothing. undefined: not yet resolved */
23672369
resolvedEntrypoints: string[] | false | undefined;
2370+
/** false: peerDependencies are not present. undefined: not yet resolved */
2371+
peerDependencies: string | false | undefined;
23682372
}
23692373

23702374
/**
@@ -2392,6 +2396,44 @@ function getVersionPathsOfPackageJsonInfo(packageJsonInfo: PackageJsonInfo, stat
23922396
return packageJsonInfo.contents.versionPaths || undefined;
23932397
}
23942398

2399+
function getPeerDependenciesOfPackageJsonInfo(packageJsonInfo: PackageJsonInfo, state: ModuleResolutionState): string | undefined {
2400+
if (packageJsonInfo.contents.peerDependencies === undefined) {
2401+
packageJsonInfo.contents.peerDependencies = readPackageJsonPeerDependencies(packageJsonInfo, state) || false;
2402+
}
2403+
return packageJsonInfo.contents.peerDependencies || undefined;
2404+
}
2405+
2406+
function readPackageJsonPeerDependencies(packageJsonInfo: PackageJsonInfo, state: ModuleResolutionState): string | undefined {
2407+
const peerDependencies = readPackageJsonField(packageJsonInfo.contents.packageJsonContent, "peerDependencies", "object", state);
2408+
if (peerDependencies === undefined) return undefined;
2409+
if (state.traceEnabled) trace(state.host, Diagnostics.package_json_has_a_peerDependencies_field);
2410+
const packageDirectory = realPath(packageJsonInfo.packageDirectory, state.host, state.traceEnabled);
2411+
if (packageDirectory.indexOf(".pnpm") === -1) {
2412+
// For now skip extra work for anything else
2413+
if (state.traceEnabled) trace(state.host, Diagnostics.Package_is_not_installed_using_pnpm_skipping_peerDependencies_version_resolution);
2414+
return undefined;
2415+
}
2416+
const nodeModules = packageDirectory.substring(0, packageDirectory.lastIndexOf("node_modules") + "node_modules".length) + directorySeparator;
2417+
let result = "";
2418+
if (state.traceEnabled) {
2419+
for (const key in peerDependencies) {
2420+
if (hasProperty(peerDependencies, key)) {
2421+
const peerPackageJson = getPackageJsonInfo(nodeModules + key, /*onlyRecordFailures*/ false, state);
2422+
if (peerPackageJson) {
2423+
const version = (peerPackageJson.contents.packageJsonContent as PackageJson).version;
2424+
result += `+${key}@${version}`;
2425+
if (state.traceEnabled) trace(state.host, Diagnostics.Found_peerDependency_0_with_1_version, key, version);
2426+
}
2427+
else {
2428+
// Read the dependency version
2429+
if (state.traceEnabled) trace(state.host, Diagnostics.Failed_to_find_peerDependency_0, key);
2430+
}
2431+
}
2432+
}
2433+
}
2434+
return result;
2435+
}
2436+
23952437
/** @internal */
23962438
export function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined {
23972439
const { host, traceEnabled } = state;
@@ -2422,7 +2464,7 @@ export function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures:
24222464
if (traceEnabled) {
24232465
trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath);
24242466
}
2425-
const result: PackageJsonInfo = { packageDirectory, contents: { packageJsonContent, versionPaths: undefined, resolvedEntrypoints: undefined } };
2467+
const result: PackageJsonInfo = { packageDirectory, contents: { packageJsonContent, versionPaths: undefined, resolvedEntrypoints: undefined, peerDependencies: undefined } };
24262468
if (state.packageJsonInfoCache && !state.packageJsonInfoCache.isReadonly) state.packageJsonInfoCache.setPackageJsonInfo(packageJsonPath, result);
24272469
state.affectingLocations?.push(packageJsonPath);
24282470
return result;
@@ -2760,7 +2802,7 @@ function getLoadModuleFromTargetImportOrExport(extensions: Extensions, state: Mo
27602802
const finalPath = toAbsolutePath(pattern ? resolvedTarget.replace(/\*/g, subpath) : resolvedTarget + subpath);
27612803
const inputLink = tryLoadInputFileForPath(finalPath, subpath, combinePaths(scope.packageDirectory, "package.json"), isImports);
27622804
if (inputLink) return inputLink;
2763-
return toSearchResult(withPackageId(scope, loadFileNameFromPackageJsonField(extensions, finalPath, /*onlyRecordFailures*/ false, state)));
2805+
return toSearchResult(withPackageId(scope, loadFileNameFromPackageJsonField(extensions, finalPath, /*onlyRecordFailures*/ false, state), state));
27642806
}
27652807
else if (typeof target === "object" && target !== null) { // eslint-disable-line no-null/no-null
27662808
if (!Array.isArray(target)) {
@@ -2906,7 +2948,7 @@ function getLoadModuleFromTargetImportOrExport(extensions: Extensions, state: Mo
29062948
if (!extensionIsOk(extensions, possibleExt)) continue;
29072949
const possibleInputWithInputExtension = changeAnyExtension(possibleInputBase, possibleExt, ext, !useCaseSensitiveFileNames(state));
29082950
if (state.host.fileExists(possibleInputWithInputExtension)) {
2909-
return toSearchResult(withPackageId(scope, loadFileNameFromPackageJsonField(extensions, possibleInputWithInputExtension, /*onlyRecordFailures*/ false, state)));
2951+
return toSearchResult(withPackageId(scope, loadFileNameFromPackageJsonField(extensions, possibleInputWithInputExtension, /*onlyRecordFailures*/ false, state), state));
29102952
}
29112953
}
29122954
}
@@ -3042,7 +3084,7 @@ function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, modu
30423084
packageInfo.contents.packageJsonContent,
30433085
getVersionPathsOfPackageJsonInfo(packageInfo, state),
30443086
);
3045-
return withPackageId(packageInfo, fromDirectory);
3087+
return withPackageId(packageInfo, fromDirectory, state);
30463088
}
30473089

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

30713113
if (rest !== "") {
@@ -3261,7 +3303,7 @@ function resolveFromTypeRoot(moduleName: string, state: ModuleResolutionState) {
32613303
if (resolvedFromFile) {
32623304
const packageDirectory = parseNodeModuleFromPath(resolvedFromFile.path);
32633305
const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, state) : undefined;
3264-
return toSearchResult(withPackageId(packageInfo, resolvedFromFile));
3306+
return toSearchResult(withPackageId(packageInfo, resolvedFromFile, state));
32653307
}
32663308
const resolved = loadNodeModuleFromDirectory(Extensions.Declaration, candidate, !directoryExists, state);
32673309
if (resolved) return toSearchResult(resolved);

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7724,6 +7724,7 @@ export interface PackageId {
77247724
subModuleName: string;
77257725
/** Version of the package, e.g. "1.2.3" */
77267726
version: string;
7727+
peerDependencies?: string;
77277728
}
77287729

77297730
export const enum Extension {

src/compiler/utilities.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -805,7 +805,7 @@ export function createModuleNotFoundChain(sourceFile: SourceFile, host: TypeChec
805805
}
806806

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

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

816816
/** @internal */
817817
export function packageIdToString(packageId: PackageId): string {
818-
return `${packageIdToPackageName(packageId)}@${packageId.version}`;
818+
return `${packageIdToPackageName(packageId)}@${packageId.version}${packageId.peerDependencies ?? ""}`;
819819
}
820820

821821
/** @internal */

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7791,6 +7791,7 @@ declare namespace ts {
77917791
subModuleName: string;
77927792
/** Version of the package, e.g. "1.2.3" */
77937793
version: string;
7794+
peerDependencies?: string;
77947795
}
77957796
enum Extension {
77967797
Ts = ".ts",

tests/baselines/reference/tsbuild/moduleResolution/resolves-specifier-in-output-declaration-file-from-referenced-project-correctly-with-preserveSymlinks.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ File name '/user/username/projects/myproject/node_modules/pkg2/build/index.js' h
9797
File '/user/username/projects/myproject/node_modules/pkg2/build/index.ts' does not exist.
9898
File '/user/username/projects/myproject/node_modules/pkg2/build/index.tsx' does not exist.
9999
File '/user/username/projects/myproject/node_modules/pkg2/build/index.d.ts' exists - use it as a name resolution result.
100+
'package.json' does not have a 'peerDependencies' field.
100101
======== Module name 'pkg2' was successfully resolved to '/user/username/projects/myproject/node_modules/pkg2/build/index.d.ts' with Package ID 'pkg2/build/[email protected]'. ========
101102
======== Resolving module 'const' from '/user/username/projects/myproject/node_modules/pkg2/build/index.d.ts'. ========
102103
Using compiler options of project reference redirect '/user/username/projects/myproject/packages/pkg2/tsconfig.json'.

tests/baselines/reference/tsbuild/moduleResolution/resolves-specifier-in-output-declaration-file-from-referenced-project-correctly.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ File name '/user/username/projects/myproject/node_modules/pkg2/build/index.js' h
9595
File '/user/username/projects/myproject/node_modules/pkg2/build/index.ts' does not exist.
9696
File '/user/username/projects/myproject/node_modules/pkg2/build/index.tsx' does not exist.
9797
File '/user/username/projects/myproject/node_modules/pkg2/build/index.d.ts' exists - use it as a name resolution result.
98+
'package.json' does not have a 'peerDependencies' field.
9899
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'.
99100
======== Module name 'pkg2' was successfully resolved to '/user/username/projects/myproject/packages/pkg2/build/index.d.ts' with Package ID 'pkg2/build/[email protected]'. ========
100101
======== Resolving module 'const' from '/user/username/projects/myproject/packages/pkg2/build/index.d.ts'. ========

tests/baselines/reference/tsbuildWatch/moduleResolution/build-mode-watches-for-changes-to-package-json-main-fields.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ File name '/user/username/projects/myproject/node_modules/pkg2/build/index.js' h
107107
File '/user/username/projects/myproject/node_modules/pkg2/build/index.ts' does not exist.
108108
File '/user/username/projects/myproject/node_modules/pkg2/build/index.tsx' does not exist.
109109
File '/user/username/projects/myproject/node_modules/pkg2/build/index.d.ts' exists - use it as a name resolution result.
110+
'package.json' does not have a 'peerDependencies' field.
110111
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'.
111112
======== Module name 'pkg2' was successfully resolved to '/user/username/projects/myproject/packages/pkg2/build/index.d.ts' with Package ID 'pkg2/build/[email protected]'. ========
112113
======== Resolving module './const.js' from '/user/username/projects/myproject/packages/pkg2/build/index.d.ts'. ========
@@ -379,6 +380,7 @@ File name '/user/username/projects/myproject/node_modules/pkg2/build/other.js' h
379380
File '/user/username/projects/myproject/node_modules/pkg2/build/other.ts' does not exist.
380381
File '/user/username/projects/myproject/node_modules/pkg2/build/other.tsx' does not exist.
381382
File '/user/username/projects/myproject/node_modules/pkg2/build/other.d.ts' exists - use it as a name resolution result.
383+
'package.json' does not have a 'peerDependencies' field.
382384
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'.
383385
======== Module name 'pkg2' was successfully resolved to '/user/username/projects/myproject/packages/pkg2/build/other.d.ts' with Package ID 'pkg2/build/[email protected]'. ========
384386
packages/pkg1/index.ts:1:15 - error TS2305: Module '"pkg2"' has no exported member 'TheNum'.
@@ -464,6 +466,7 @@ File name '/user/username/projects/myproject/node_modules/pkg2/build/index.js' h
464466
File '/user/username/projects/myproject/node_modules/pkg2/build/index.ts' does not exist.
465467
File '/user/username/projects/myproject/node_modules/pkg2/build/index.tsx' does not exist.
466468
File '/user/username/projects/myproject/node_modules/pkg2/build/index.d.ts' exists - use it as a name resolution result.
469+
'package.json' does not have a 'peerDependencies' field.
467470
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'.
468471
======== Module name 'pkg2' was successfully resolved to '/user/username/projects/myproject/packages/pkg2/build/index.d.ts' with Package ID 'pkg2/build/[email protected]'. ========
469472
======== Resolving module './const.js' from '/user/username/projects/myproject/packages/pkg2/build/index.d.ts'. ========

0 commit comments

Comments
 (0)