From a53fb52211826fc30c925208e42cb5c04ecd6ce2 Mon Sep 17 00:00:00 2001 From: drewmt Date: Tue, 1 Sep 2026 09:16:02 +0300 Subject: [PATCH] fix(arborist): avoid repeated peer placement checks --- workspaces/arborist/lib/can-place-dep.js | 20 +++++++++++++ .../test/can-place-dep.js.test.cjs | 4 +++ workspaces/arborist/test/can-place-dep.js | 30 +++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/workspaces/arborist/lib/can-place-dep.js b/workspaces/arborist/lib/can-place-dep.js index 1069fee8d65ab..ab0f508792bb7 100644 --- a/workspaces/arborist/lib/can-place-dep.js +++ b/workspaces/arborist/lib/can-place-dep.js @@ -63,6 +63,7 @@ class CanPlaceDep { preferDedupe, parent = null, peerPath = [], + peerChecks = new Map(), explicitRequest = false, auditReport = null, } = options @@ -99,6 +100,7 @@ class CanPlaceDep { // preventing cycles when we check peer sets this.peerPath = peerPath + this.peerChecks = peerChecks // we always prefer to dedupe peers, because they are trying // a bit harder to be singletons. this.preferDedupe = !!preferDedupe || edge.peer @@ -387,12 +389,30 @@ class CanPlaceDep { // of that dep needs to be placed shallower, because the target has // a peer dep on the peer as well. const target = deepestNestingTarget(this.target, peer.name) + const deepestTarget = deepestNestingTarget(this.deepestNestingTarget, peer.name) + // Dense peer graphs can reach the same placement through many paths. + // Each edge only needs one check for a given pair of placement targets. + let targetChecks = this.peerChecks.get(peerEdge) + if (!targetChecks) { + targetChecks = new Map() + this.peerChecks.set(peerEdge, targetChecks) + } + let deepestChecks = targetChecks.get(target) + if (!deepestChecks) { + deepestChecks = new Set() + targetChecks.set(target, deepestChecks) + } + if (deepestChecks.has(deepestTarget)) { + continue + } + deepestChecks.add(deepestTarget) const cpp = new CanPlaceDep({ dep: peer, target, parent: this, edge: peerEdge, peerPath, + peerChecks: this.peerChecks, auditReport: this.auditReport, // always place peers in preferDedupe mode preferDedupe: true, diff --git a/workspaces/arborist/tap-snapshots/test/can-place-dep.js.test.cjs b/workspaces/arborist/tap-snapshots/test/can-place-dep.js.test.cjs index 2c77b0e89d792..f8f8a6c06afaf 100644 --- a/workspaces/arborist/tap-snapshots/test/can-place-dep.js.test.cjs +++ b/workspaces/arborist/tap-snapshots/test/can-place-dep.js.test.cjs @@ -79,6 +79,10 @@ exports[`test/can-place-dep.js TAP basic placement check tests cycle of peers ha Array [] ` +exports[`test/can-place-dep.js TAP basic placement check tests dense peer graph > conflict children 1`] = ` +Array [] +` + exports[`test/can-place-dep.js TAP basic placement check tests do not keep existing dep that matches, but does not satisfy > conflict children 1`] = ` Array [] ` diff --git a/workspaces/arborist/test/can-place-dep.js b/workspaces/arborist/test/can-place-dep.js index 26f83aca8c991..f7fb00fe2e823 100644 --- a/workspaces/arborist/test/can-place-dep.js +++ b/workspaces/arborist/test/can-place-dep.js @@ -37,6 +37,8 @@ t.test('basic placement check tests', t => { explicitRequest, // an audit report, telling us which nodes are vulnerable auditReport, + // upper bound for the number of peer placement checks + maxChecks, }) => { const target = tree.inventory.get(targetLoc) const node = tree.inventory.get(nodeLoc) @@ -87,6 +89,9 @@ t.test('basic placement check tests', t => { if (expectSelf) { t.equal(cpd.canPlaceSelf, expectSelf, msg) } + if (maxChecks) { + t.ok(cpd.allChildren.length <= maxChecks, 'avoids duplicate peer placement checks') + } t.equal(cpd.description, cpd.canPlace.description || cpd.canPlace) t.matchSnapshot([...cpd.conflictChildren].map(c => ({ dep: [c.dep.name, c.dep.version], @@ -621,6 +626,31 @@ t.test('basic placement check tests', t => { ], }) + const densePeerNames = Array.from({ length: 8 }, (_, index) => `peer-${index}`) + const densePeerPackage = name => ({ + name, + version: '1.0.0', + peerDependencies: Object.fromEntries( + densePeerNames.filter(peer => peer !== name).map(peer => [peer, '1']) + ), + }) + runTest('dense peer graph', { + tree: new Node({ + path, + pkg: { + name: 'project', + version: '1.2.3', + dependencies: { [densePeerNames[0]]: '1' }, + }, + }), + targetLoc: '', + nodeLoc: '', + dep: new Node({ pkg: densePeerPackage(densePeerNames[0]) }), + expect: OK, + peerSet: densePeerNames.slice(1).map(name => ({ pkg: densePeerPackage(name) })), + maxChecks: densePeerNames.length ** 2, + }) + runTest('peers with peerConflicted edges in peerSet', { tree: new Node({ path,