diff --git a/workspaces/arborist/lib/node.js b/workspaces/arborist/lib/node.js index 13370a50ab475..cf7d06f5f9f13 100644 --- a/workspaces/arborist/lib/node.js +++ b/workspaces/arborist/lib/node.js @@ -1190,7 +1190,16 @@ class Node { // if they're links, they match if the targets match if (this.isLink) { - return node.isLink && this.target.matches(node.target) + if (!node.isLink) { + return false + } + // reassigning a target's root clears `target` on every link pointing at + // it, but the link still knows the realpath it pointed at, so fall back + // to that rather than dereferencing null. + if (!this.target || !node.target) { + return !!this.realpath && this.realpath === node.realpath + } + return this.target.matches(node.target) } // if they're two project root nodes, they're different if the paths differ diff --git a/workspaces/arborist/test/node.js b/workspaces/arborist/test/node.js index ec474e0b7d75e..8b10e2019292b 100644 --- a/workspaces/arborist/test/node.js +++ b/workspaces/arborist/test/node.js @@ -1449,6 +1449,34 @@ t.test('detect that two nodes are the same thing', async t => { check(a, b, true, 'links match if targets match') } + { + const root = new Node({ path: '/root', pkg: { name: 'root', version: '1.0.0' } }) + const target = new Node({ root, path: '/root/packages/x', pkg: { name: 'x', version: '1.2.3' } }) + const a = new Link({ root, parent: root, name: 'x', target }) + const b = new Node({ root, parent: root, name: 'b', pkg: { name: 'b', version: '1.0.0' } }) + // Nested link to the same target + const bx = new Link({ root, parent: b, name: 'x', target }) + target.root = new Node({ path: '/other-root' }) + t.equal(a.target, null, 'target was cleared') + t.equal(bx.target, null, 'nested link target was cleared') + check(a, bx, true, 'links with cleared targets match on realpath') + } + + { + const root = new Node({ path: '/root', pkg: { name: 'root', version: '1.0.0' } }) + const x = new Node({ root, path: '/root/packages/x', pkg: { name: 'x', version: '1.2.3' } }) + const y = new Node({ root, path: '/root/packages/y', pkg: { name: 'x', version: '1.2.3' } }) + const a = new Link({ root, parent: root, name: 'x', target: x }) + const b = new Node({ root, parent: root, name: 'b', pkg: { name: 'b', version: '1.0.0' } }) + // Nested link to a different target of the same name + const by = new Link({ root, parent: b, name: 'x', target: y }) + x.root = new Node({ path: '/other-root' }) + y.root = new Node({ path: '/another-root' }) + t.equal(a.target, null, 'target was cleared') + t.equal(by.target, null, 'nested link target was cleared') + check(a, by, false, 'cleared links with different realpaths do not match') + } + { const a = new Node({ path: '/foo', pkg: { name: 'x', version: '1.2.3' } }) const b = new Node({ path: '/foo', pkg: { name: 'x', version: '1.2.3' } })