From a1ed19982b5b851935823914289eca1958f6fd4d Mon Sep 17 00:00:00 2001 From: Gar Date: Mon, 18 Jul 2022 10:00:45 -0700 Subject: [PATCH] fix: implement deduped --- .../content/using-npm/dependency-selectors.md | 8 +- lib/commands/query.js | 12 +-- .../test/lib/commands/query.js.test.cjs | 83 +++++++++++++++++-- test/lib/commands/query.js | 31 +++++++ workspaces/arborist/lib/query-selector-all.js | 4 + .../arborist/test/query-selector-all.js | 5 ++ 6 files changed, 124 insertions(+), 19 deletions(-) diff --git a/docs/content/using-npm/dependency-selectors.md b/docs/content/using-npm/dependency-selectors.md index d2cc73be290f1..9d65baf631a7e 100644 --- a/docs/content/using-npm/dependency-selectors.md +++ b/docs/content/using-npm/dependency-selectors.md @@ -41,7 +41,7 @@ The [`npm query`](/commands/npm-query) commmand exposes a new dependency selecto - `.dev` dependency found in the `devDependencies` section of `package.json`, or is a child of said dependency - `.optional` dependency found in the `optionalDependencies` section of `package.json`, or has `"optional": true` set in its entry in the `peerDependenciesMeta` section of `package.json`, or a child of said dependency - `.peer` dependency found in the `peerDependencies` section of `package.json` -- `.workspace` dependency found in the `workspaces` section of `package.json` +- `.workspace` dependency found in the [`workspaces`](https://docs.npmjs.com/cli/v8/using-npm/workspaces) section of `package.json` - `.bundled` dependency found in the `bundleDependencies` section of `package.json`, or is a child of said dependency #### Pseudo Selectors @@ -52,9 +52,9 @@ The [`npm query`](/commands/npm-query) commmand exposes a new dependency selecto - [`:scope`](https://developer.mozilla.org/en-US/docs/Web/CSS/:scope) matches node/dependency it was queried against - [`:empty`](https://developer.mozilla.org/en-US/docs/Web/CSS/:empty) when a dependency has no dependencies - [`:private`](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#private) when a dependency is private -- `:link` when a dependency is linked -- `:deduped` when a dependency has been deduped -- `:override` when a dependency is an override +- `:link` when a dependency is linked (for instance, workspaces or packages manually [`linked`](https://docs.npmjs.com/cli/v8/commands/npm-link) +- `:deduped` when a dependency has been deduped (note that this does *not* always mean the dependency has been hoisted to the root of node_modules) +- `:override` when a dependency is an override (not implemented yet) - `:extraneous` when a dependency exists but is not defined as a dependency of any node - `:invalid` when a dependency version is out of its ancestors specified range - `:missing` when a dependency is not found on disk diff --git a/lib/commands/query.js b/lib/commands/query.js index 5dba4b2bea3a0..5cd28e98dfbc6 100644 --- a/lib/commands/query.js +++ b/lib/commands/query.js @@ -19,11 +19,14 @@ class QuerySelectorItem { this.to = [] this.dev = node.target.dev this.inBundle = node.target.inBundle + this.deduped = this.from.length > 1 for (const edge of node.target.edgesIn) { this.from.push(edge.from.location) } for (const [, edge] of node.target.edgesOut) { - this.to.push(edge.to.location) + if (edge.to) { + this.to.push(edge.to.location) + } } } } @@ -89,11 +92,8 @@ class Query extends BaseCommand { // builds a normalized inventory buildResponse (items) { for (const node of items) { - if (!this.#seen.has(node.target.location)) { - const item = new QuerySelectorItem(node) - this.#response.push(item) - this.#seen.add(item.realpath) - } + const item = new QuerySelectorItem(node) + this.#response.push(item) } } } diff --git a/tap-snapshots/test/lib/commands/query.js.test.cjs b/tap-snapshots/test/lib/commands/query.js.test.cjs index ef5574ee2ccac..45a42d03a20c9 100644 --- a/tap-snapshots/test/lib/commands/query.js.test.cjs +++ b/tap-snapshots/test/lib/commands/query.js.test.cjs @@ -21,7 +21,8 @@ exports[`test/lib/commands/query.js TAP global > should return global package 1` ], "to": [], "dev": false, - "inBundle": false + "inBundle": false, + "deduped": false } ] ` @@ -49,7 +50,8 @@ exports[`test/lib/commands/query.js TAP include-workspace-root > should return w "node_modules/b" ], "dev": false, - "inBundle": false + "inBundle": false, + "deduped": false }, { "name": "c", @@ -63,7 +65,8 @@ exports[`test/lib/commands/query.js TAP include-workspace-root > should return w "from": [], "to": [], "dev": false, - "inBundle": false + "inBundle": false, + "deduped": false } ] ` @@ -82,7 +85,8 @@ exports[`test/lib/commands/query.js TAP linked node > should return linked node "from": [], "to": [], "dev": false, - "inBundle": false + "inBundle": false, + "deduped": false }, { "name": "a", @@ -96,7 +100,61 @@ exports[`test/lib/commands/query.js TAP linked node > should return linked node "from": [], "to": [], "dev": false, - "inBundle": false + "inBundle": false, + "deduped": false + } +] +` + +exports[`test/lib/commands/query.js TAP recursive tree > should return everything in the tree, accounting for recursion 1`] = ` +[ + { + "name": "project", + "dependencies": { + "a": "^1.0.0", + "b": "^1.0.0" + }, + "pkgid": "project@", + "location": "", + "path": "{CWD}/test/lib/commands/tap-testdir-query-recursive-tree/prefix", + "realpath": "{CWD}/test/lib/commands/tap-testdir-query-recursive-tree/prefix", + "resolved": null, + "from": [], + "to": [ + "node_modules/a", + "node_modules/b" + ], + "dev": false, + "inBundle": false, + "deduped": false + }, + { + "pkgid": "a@", + "location": "node_modules/a", + "path": "{CWD}/test/lib/commands/tap-testdir-query-recursive-tree/prefix/node_modules/a", + "realpath": "{CWD}/test/lib/commands/tap-testdir-query-recursive-tree/prefix/node_modules/a", + "resolved": null, + "from": [ + "" + ], + "to": [], + "dev": false, + "inBundle": false, + "deduped": false + }, + { + "pkgid": "b@", + "location": "node_modules/b", + "path": "{CWD}/test/lib/commands/tap-testdir-query-recursive-tree/prefix/node_modules/b", + "realpath": "{CWD}/test/lib/commands/tap-testdir-query-recursive-tree/prefix/node_modules/b", + "resolved": null, + "from": [ + "" + ], + "to": [], + "dev": false, + "inBundle": false, + "deduped": false } ] ` @@ -109,6 +167,9 @@ exports[`test/lib/commands/query.js TAP simple query > should return root object "a": "^1.0.0", "b": "^1.0.0" }, + "peerDependencies": { + "c": "1.0.0" + }, "pkgid": "project@", "location": "", "path": "{CWD}/test/lib/commands/tap-testdir-query-simple-query/prefix", @@ -120,7 +181,8 @@ exports[`test/lib/commands/query.js TAP simple query > should return root object "node_modules/b" ], "dev": false, - "inBundle": false + "inBundle": false, + "deduped": false }, { "pkgid": "a@", @@ -133,7 +195,8 @@ exports[`test/lib/commands/query.js TAP simple query > should return root object ], "to": [], "dev": false, - "inBundle": false + "inBundle": false, + "deduped": false }, { "pkgid": "b@", @@ -146,7 +209,8 @@ exports[`test/lib/commands/query.js TAP simple query > should return root object ], "to": [], "dev": false, - "inBundle": false + "inBundle": false, + "deduped": false } ] ` @@ -165,7 +229,8 @@ exports[`test/lib/commands/query.js TAP workspace query > should return workspac "from": [], "to": [], "dev": false, - "inBundle": false + "inBundle": false, + "deduped": false } ] ` diff --git a/test/lib/commands/query.js b/test/lib/commands/query.js index f13777f9cd94f..fb5b4843c34ee 100644 --- a/test/lib/commands/query.js +++ b/test/lib/commands/query.js @@ -30,6 +30,9 @@ t.test('simple query', async t => { a: '^1.0.0', b: '^1.0.0', }, + peerDependencies: { + c: '1.0.0', + }, }), }, }) @@ -37,6 +40,34 @@ t.test('simple query', async t => { t.matchSnapshot(joinedOutput(), 'should return root object and direct children') }) +t.test('recursive tree', async t => { + const { npm, joinedOutput } = await loadMockNpm(t, { + prefixDir: { + node_modules: { + a: { + name: 'a', + version: '1.0.0', + }, + b: { + name: 'b', + version: '^2.0.0', + dependencies: { + a: '1.0.0', + }, + }, + }, + 'package.json': JSON.stringify({ + name: 'project', + dependencies: { + a: '^1.0.0', + b: '^1.0.0', + }, + }), + }, + }) + await npm.exec('query', ['*']) + t.matchSnapshot(joinedOutput(), 'should return everything in the tree, accounting for recursion') +}) t.test('workspace query', async t => { const { npm, joinedOutput } = await loadMockNpm(t, { config: { diff --git a/workspaces/arborist/lib/query-selector-all.js b/workspaces/arborist/lib/query-selector-all.js index 857c82a2acb33..86ec06e23ca25 100644 --- a/workspaces/arborist/lib/query-selector-all.js +++ b/workspaces/arborist/lib/query-selector-all.js @@ -310,6 +310,10 @@ class Results { return found }) } + + dedupedPseudo () { + return this.initialItems.filter(node => node.target.edgesIn.size > 1) + } } // operators for attribute selectors diff --git a/workspaces/arborist/test/query-selector-all.js b/workspaces/arborist/test/query-selector-all.js index 425db952ad7fd..ec8c116122099 100644 --- a/workspaces/arborist/test/query-selector-all.js +++ b/workspaces/arborist/test/query-selector-all.js @@ -358,6 +358,11 @@ t.test('query-selector-all', async t => { [':invalid', ['lorem@1.0.0']], [':link', ['a@1.0.0', 'b@1.0.0']], [':link', ['a@1.0.0', 'b@1.0.0']], + [':deduped', [ + 'bar@2.0.0', + 'moo@3.0.0', + 'recur@1.0.0', + ]], [':missing', ['missing-dep@^1.0.0']], [':private', ['b@1.0.0']],