From 70e368193f9527ff83ceb8e881e646c6a37fc16d Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Thu, 15 Oct 2026 01:45:19 -0700 Subject: [PATCH] fix(arborist): skip named-edge refresh work for empty leaves Return before looking up named edges when a node has no outgoing dependencies or children. Keep descendant traversal and later graph mutations intact, with regression coverage for both package and filesystem children. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- workspaces/arborist/lib/node.js | 4 ++++ workspaces/arborist/test/node.js | 40 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/workspaces/arborist/lib/node.js b/workspaces/arborist/lib/node.js index 13370a50ab475..a0ee56affc6e6 100644 --- a/workspaces/arborist/lib/node.js +++ b/workspaces/arborist/lib/node.js @@ -1505,6 +1505,10 @@ class Node { } [_reloadNamedEdges] (name, rootLoc = this.location) { + if (!this.edgesOut.size && !this.children.size && !this.fsChildren.size) { + return + } + const edge = this.edgesOut.get(name) // if we don't have an edge, do nothing, but keep descending const rootLocResolved = edge && edge.to && diff --git a/workspaces/arborist/test/node.js b/workspaces/arborist/test/node.js index ec474e0b7d75e..cc1fd7b6845fc 100644 --- a/workspaces/arborist/test/node.js +++ b/workspaces/arborist/test/node.js @@ -1332,6 +1332,46 @@ t.test('reparenting keeps children in root inventory', async t => { t.equal(root.inventory.has(fsNested), true) }) +t.test('reloading named edges skips empty leaves without caching their state', t => { + const root = new Node({ path: '/some/path' }) + const leaf = new Node({ parent: root, pkg: { name: 'leaf' } }) + const lookups = [] + const get = leaf.edgesOut.get + leaf.edgesOut.get = function (name) { + lookups.push(name) + return get.call(this, name) + } + + const foo = new Node({ parent: root, pkg: { name: 'foo', version: '1.0.0' } }) + t.same(lookups, [], 'no lookup in an empty leaf during the refresh') + delete leaf.edgesOut.get + + leaf.package = { name: 'leaf', dependencies: { foo: '1' } } + t.equal(leaf.edgesOut.get('foo').to, foo) + foo.parent = null + t.equal(leaf.edgesOut.get('foo').to, null, 'the newly added edge still refreshes') + t.end() +}) + +t.test('reloading traverses dependency-free parents with children', t => { + for (const kind of ['parent', 'fsParent']) { + const root = new Node({ path: '/some/path' }) + const parent = new Node({ parent: root, pkg: { name: 'parent' } }) + const child = new Node({ + [kind]: parent, + path: kind === 'fsParent' ? `${parent.path}/child` : undefined, + pkg: { name: 'child', dependencies: { foo: '1' } }, + }) + t.equal(parent.edgesOut.size, 0) + t.equal(child.edgesOut.get('foo').to, null) + const foo = new Node({ parent: root, pkg: { name: 'foo', version: '1.0.0' } }) + t.equal(child.edgesOut.get('foo').to, foo, `${kind} descendants are refreshed`) + foo.parent = null + t.equal(child.edgesOut.get('foo').to, null, `${kind} descendants track removals`) + } + t.end() +}) + t.test('reloading named edges should refresh edgesIn', t => { // pathological dep nesting scenario: //