Repository: webpro/knip
Pull request #85 (2023-03-13 05:26:24)

feat: add new plugin ava
https://github.com/avajs/ava

webpro: Thanks, @RebeccaStevens. Please see `_getReferencesFromScripts`, the `github-actions` and `husky` plugins are example plugins using this helper to extract binaries and entry files from scripts, also those referenced from the CLI arguments I see in the Ava config. See [the tests](https://github.com/webpro/knip/blob/main/test/tests/getReferencesFromScripts.test.ts) for what it supports. Maybe you need to prepend `node ` to each of `nodeArguments`, or feed it something like `node ${nodeArguments.join(' ')}`.
RebeccaStevens: Change made :)

Patch:
diff --git a/README.md b/README.md
index baf560dc..ddd12662 100644
--- a/README.md
+++ b/README.md
@@ -271,40 +271,41 @@ Here's some example output when running Knip in a workspace:
 
 Knip contains a growing list of plugins:
 
-- [Babel][13]
-- [Capacitor][14]
-- [Changesets][15]
-- [commitlint][16]
-- [cspell][17]
-- [Cypress][18]
-- [ESLint][19]
-- [Gatsby][20]
-- [GitHub Actions][21]
-- [husky][22]
-- [Jest][23]
-- [Lefthook][24]
-- [lint-staged][25]
-- [markdownlint][26]
-- [Mocha][27]
-- [Next.js][28]
-- [npm-package-json-lint][29]
-- [Nx][30]
-- [nyc][31]
-- [Playwright][32]
-- [PostCSS][33]
-- [Prettier][34]
-- [Release It][35]
-- [Remark][36]
-- [Remix][37]
-- [Rollup][38]
-- [Semantic Release][39]
-- [Sentry][40]
-- [Storybook][41]
-- [Stryker][42]
-- [TypeDoc][43]
-- [TypeScript][44]
-- [Vitest][45]
-- [Webpack][46]
+- [Ava][13]
+- [Babel][14]
+- [Capacitor][15]
+- [Changesets][16]
+- [commitlint][17]
+- [cspell][18]
+- [Cypress][19]
+- [ESLint][20]
+- [Gatsby][21]
+- [GitHub Actions][22]
+- [husky][23]
+- [Jest][24]
+- [Lefthook][25]
+- [lint-staged][26]
+- [markdownlint][27]
+- [Mocha][28]
+- [Next.js][29]
+- [npm-package-json-lint][30]
+- [Nx][31]
+- [nyc][32]
+- [Playwright][33]
+- [PostCSS][34]
+- [Prettier][35]
+- [Release It][36]
+- [Remark][37]
+- [Remix][38]
+- [Rollup][39]
+- [Semantic Release][40]
+- [Sentry][41]
+- [Storybook][42]
+- [Stryker][43]
+- [TypeDoc][44]
+- [TypeScript][45]
+- [Vitest][46]
+- [Webpack][47]
 
 Plugins are automatically activated. Each plugin is automatically enabled based on simple heuristics. Most of them check
 whether one or one of a few (dev) dependencies are listed in `package.json`. Once enabled, they add a set of
@@ -312,7 +313,7 @@ configuration and/or entry files for Knip to analyze. These defaults can be over
 
 Most plugins use one or both of the following file types:
 
-- `config` - custom dependency resolvers are applied to the [config files][47]
+- `config` - custom dependency resolvers are applied to the [config files][48]
 - `entry` - files to include with the analysis of the rest of the source code
 
 See each plugin's documentation for its default values.
@@ -338,7 +339,7 @@ Other configuration files use `require` or `import` statements to use dependenci
 rest of the source files. These configuration files are also considered `entry` files.
 
 For plugins related to test files, it's good to know that the following glob patterns are always included by default
-(see [TEST_FILE_PATTERNS in constants.ts][48]):
+(see [TEST_FILE_PATTERNS in constants.ts][49]):
 
 - `**/*.{test,spec}.{js,jsx,ts,tsx,mjs,cjs}`
 - `**/__tests__/**/*.{js,jsx,ts,tsx,mjs,cjs}`
@@ -420,10 +421,10 @@ Each workspace can also have its own `paths` configured. Note that Knip `paths`
 
 Knip provides the following built-in reporters:
 
-- [`codeowners`][49]
-- [`compact`][50]
-- [`json`][51]
-- [`symbol`][52] (default)
+- [`codeowners`][50]
+- [`compact`][51]
+- [`json`][52]
+- [`symbol`][53] (default)
 
 The `compact` reporter shows the sorted files first, and then a list of symbols:
 
@@ -450,7 +451,7 @@ type ReporterOptions = {
 
 The data can then be used to write issues to `stdout`, a JSON or CSV file, or sent to a service.
 
-Find more details and ideas in [custom reporters][53].
+Find more details and ideas in [custom reporters][54].
 
 ## Libraries and "unused" exports
 
@@ -508,7 +509,7 @@ When unused dependencies are related to dependencies having a Knip [plugin][2],
 for that dependency are at custom locations. The default values are at the plugin's documentation, and can be overridden
 to match the custom location(s).
 
-When the dependencies don't have a Knip plugin yet, please file an issue or [create a new plugin][54].
+When the dependencies don't have a Knip plugin yet, please file an issue or [create a new plugin][55].
 
 #### Too many unused exports
 
@@ -516,7 +517,7 @@ When the project is a library and the exports are meant to be used by consumers
 
 1.  By default, unused exports of `entry` files are not reported. You could re-export from an existing entry file, or
     add the containing file to the `entry` array in the configuration.
-2.  The exported values or types can be marked [using the JSDoc `@public` tag][55].
+2.  The exported values or types can be marked [using the JSDoc `@public` tag][56].
 
 ### How to start using Knip in CI while having too many issues to sort out?
 
@@ -536,7 +537,7 @@ All of this is hiding problems, so please make sure to plan for fixing them and/
 
 This table is an ongoing comparison. Based on their docs (please report any mistakes):
 
-| Feature                           | **knip** | [depcheck][56] | [unimported][57] | [ts-unused-exports][58] | [ts-prune][59] |
+| Feature                           | **knip** | [depcheck][57] | [unimported][58] | [ts-unused-exports][59] | [ts-prune][60] |
 | :-------------------------------- | :------: | :------------: | :--------------: | :---------------------: | :------------: |
 | Unused files                      |    ✅    |       -        |        ✅        |            -            |       -        |
 | Unused dependencies               |    ✅    |       ✅       |        ✅        |            -            |       -        |
@@ -571,7 +572,7 @@ The following commands are similar:
     unimported
     knip --production --dependencies --include files
 
-Also see [production mode][60].
+Also see [production mode][61].
 
 ### ts-unused-exports
 
@@ -605,7 +606,7 @@ for the job. I'm motivated to make knip perfectly suited for the job of cutting
 
 Special thanks to the wonderful people who have contributed to this project:
 
-[![Contributors][62]][61]
+[![Contributors][63]][62]
 
 [1]: #workspaces--monorepos
 [2]: #plugins
@@ -619,53 +620,54 @@ Special thanks to the wonderful people who have contributed to this project:
 [10]: https://github.com/webpro/knip/issues
 [11]: #faq
 [12]: #ignore
-[13]: ./src/plugins/babel
-[14]: ./src/plugins/capacitor
-[15]: ./src/plugins/changesets
-[16]: ./src/plugins/commitlint
-[17]: ./src/plugins/cspell
-[18]: ./src/plugins/cypress
-[19]: ./src/plugins/eslint
-[20]: ./src/plugins/gatsby
-[21]: ./src/plugins/github-actions
-[22]: ./src/plugins/husky
-[23]: ./src/plugins/jest
-[24]: ./src/plugins/lefthook
-[25]: ./src/plugins/lint-staged
-[26]: ./src/plugins/markdownlint
-[27]: ./src/plugins/mocha
-[28]: ./src/plugins/next
-[29]: ./src/plugins/npm-package-json-lint
-[30]: ./src/plugins/nx
-[31]: ./src/plugins/nyc
-[32]: ./src/plugins/playwright
-[33]: ./src/plugins/postcss
-[34]: ./src/plugins/prettier
-[35]: ./src/plugins/release-it
-[36]: ./src/plugins/remark
-[37]: ./src/plugins/remix
-[38]: ./src/plugins/rollup
-[39]: ./src/plugins/semantic-release
-[40]: ./src/plugins/sentry
-[41]: ./src/plugins/storybook
-[42]: ./src/plugins/stryker
-[43]: ./src/plugins/typedoc
-[44]: ./src/plugins/typescript
-[45]: ./src/plugins/vitest
-[46]: ./src/plugins/webpack
-[47]: #config
-[48]: https://github.com/webpro/knip/blob/main/src/constants.ts
-[49]: #code-owners
-[50]: #compact
-[51]: #json
-[52]: #symbol-default
-[53]: ./docs/custom-reporters.md
-[54]: #create-a-new-plugin
-[55]: #libraries-and-unused-exports
-[56]: https://github.com/depcheck/depcheck
-[57]: https://github.com/smeijer/unimported
-[58]: https://github.com/pzavolinsky/ts-unused-exports
-[59]: https://github.com/nadeesha/ts-prune
-[60]: #production-mode
-[61]: https://github.com/webpro/knip/graphs/contributors
-[62]: https://contrib.rocks/image?repo=webpro/knip
+[13]: ./src/plugins/ava
+[14]: ./src/plugins/babel
+[15]: ./src/plugins/capacitor
+[16]: ./src/plugins/changesets
+[17]: ./src/plugins/commitlint
+[18]: ./src/plugins/cspell
+[19]: ./src/plugins/cypress
+[20]: ./src/plugins/eslint
+[21]: ./src/plugins/gatsby
+[22]: ./src/plugins/github-actions
+[23]: ./src/plugins/husky
+[24]: ./src/plugins/jest
+[25]: ./src/plugins/lefthook
+[26]: ./src/plugins/lint-staged
+[27]: ./src/plugins/markdownlint
+[28]: ./src/plugins/mocha
+[29]: ./src/plugins/next
+[30]: ./src/plugins/npm-package-json-lint
+[31]: ./src/plugins/nx
+[32]: ./src/plugins/nyc
+[33]: ./src/plugins/playwright
+[34]: ./src/plugins/postcss
+[35]: ./src/plugins/prettier
+[36]: ./src/plugins/release-it
+[37]: ./src/plugins/remark
+[38]: ./src/plugins/remix
+[39]: ./src/plugins/rollup
+[40]: ./src/plugins/semantic-release
+[41]: ./src/plugins/sentry
+[42]: ./src/plugins/storybook
+[43]: ./src/plugins/stryker
+[44]: ./src/plugins/typedoc
+[45]: ./src/plugins/typescript
+[46]: ./src/plugins/vitest
+[47]: ./src/plugins/webpack
+[48]: #config
+[49]: https://github.com/webpro/knip/blob/main/src/constants.ts
+[50]: #code-owners
+[51]: #compact
+[52]: #json
+[53]: #symbol-default
+[54]: ./docs/custom-reporters.md
+[55]: #create-a-new-plugin
+[56]: #libraries-and-unused-exports
+[57]: https://github.com/depcheck/depcheck
+[58]: https://github.com/smeijer/unimported
+[59]: https://github.com/pzavolinsky/ts-unused-exports
+[60]: https://github.com/nadeesha/ts-prune
+[61]: #production-mode
+[62]: https://github.com/webpro/knip/graphs/contributors
+[63]: https://contrib.rocks/image?repo=webpro/knip
diff --git a/schema.json b/schema.json
index 10896e50..91263dc3 100644
--- a/schema.json
+++ b/schema.json
@@ -164,6 +164,10 @@
     },
     "plugins": {
       "properties": {
+        "ava": {
+          "title": "ava plugin configuration (https://github.com/webpro/knip/blob/main/src/plugins/ava/README.md)",
+          "$ref": "#/definitions/plugin"
+        },
         "babel": {
           "title": "Babel plugin configuration (https://github.com/webpro/knip/blob/main/src/plugins/babel/README.md)",
           "$ref": "#/definitions/plugin"
diff --git a/src/plugins/ava/README.md b/src/plugins/ava/README.md
new file mode 100644
index 00000000..9802a583
--- /dev/null
+++ b/src/plugins/ava/README.md
@@ -0,0 +1,22 @@
+# Ava
+
+## Enabled
+
+This plugin is enabled when any of the following package names and/or regular expressions has a match in `dependencies`
+or `devDependencies`:
+
+- `ava`
+
+## Default configuration
+
+```json
+{
+  "ava": {
+    "config": ["ava.config.{js,cjs,mjs}", "package.json"]
+  }
+}
+```
+
+Also see [Knip plugins][1] for more information about plugins.
+
+[1]: https://github.com/webpro/knip/blob/main/README.md#plugins
diff --git a/src/plugins/ava/index.ts b/src/plugins/ava/index.ts
new file mode 100644
index 00000000..7fe752fa
--- /dev/null
+++ b/src/plugins/ava/index.ts
@@ -0,0 +1,36 @@
+import { _getReferencesFromScripts } from '../../util/binaries/index.js';
+import { _load } from '../../util/loader.js';
+import { timerify } from '../../util/performance.js';
+import { hasDependency } from '../../util/plugin.js';
+import type { PluginConfig } from './types.js';
+import type { IsPluginEnabledCallback, GenericPluginCallback } from '../../types/plugins.js';
+
+// https://github.com/avajs/ava/blob/main/docs/06-configuration.md
+
+export const NAME = 'Ava';
+
+/** @public */
+export const ENABLERS = ['ava'];
+
+export const isEnabled: IsPluginEnabledCallback = ({ dependencies }) => hasDependency(dependencies, ENABLERS);
+
+export const CONFIG_FILE_PATTERNS = ['ava.config.{js,cjs,mjs}', 'package.json'];
+
+const findPluginDependencies: GenericPluginCallback = async (configFilePath, { cwd, manifest, rootConfig }) => {
+  const config: PluginConfig = configFilePath.endsWith('package.json') ? manifest.ava : await _load(configFilePath);
+
+  const requireArgs = (config?.require ?? []).map(require => `--require ${require}`);
+  const otherArgs = config?.nodeArguments ?? [];
+
+  const cmd = `node ${otherArgs.join(' ') + ' '}${requireArgs.join(' ')}`;
+  const { binaries } = _getReferencesFromScripts([cmd], {
+    cwd,
+    manifest,
+    ignore: rootConfig.ignoreBinaries,
+    knownGlobalsOnly: true,
+  });
+
+  return binaries;
+};
+
+export const findDependencies = timerify(findPluginDependencies);
diff --git a/src/plugins/ava/types.ts b/src/plugins/ava/types.ts
new file mode 100644
index 00000000..a65e8869
--- /dev/null
+++ b/src/plugins/ava/types.ts
@@ -0,0 +1,4 @@
+export type PluginConfig = {
+  require?: string[];
+  nodeArguments?: string[];
+};
diff --git a/src/plugins/index.ts b/src/plugins/index.ts
index 67ba616f..1606fad2 100644
--- a/src/plugins/index.ts
+++ b/src/plugins/index.ts
@@ -1,3 +1,4 @@
+export * as ava from './ava/index.js';
 export * as babel from './babel/index.js';
 export * as capacitor from './capacitor/index.js';
 export * as changesets from './changesets/index.js';


Test patch:
diff --git a/test/fixtures/plugins/ava/ava.config.mjs b/test/fixtures/plugins/ava/ava.config.mjs
new file mode 100644
index 00000000..a90ef266
--- /dev/null
+++ b/test/fixtures/plugins/ava/ava.config.mjs
@@ -0,0 +1,14 @@
+const avaConfig = {
+  files: ["**/*.test.*"],
+  extensions: ["js"],
+  require: ["tsconfig-paths/register"],
+  typescript: {
+    rewritePaths: {
+      "src/": "build/src/",
+      "tests/": "build/tests/",
+    },
+    compile: false,
+  },
+};
+
+export default avaConfig;
diff --git a/test/fixtures/plugins/ava/package.json b/test/fixtures/plugins/ava/package.json
new file mode 100644
index 00000000..9251aca9
--- /dev/null
+++ b/test/fixtures/plugins/ava/package.json
@@ -0,0 +1,14 @@
+{
+  "ava": {
+    "files": [
+      "**/*.test.*"
+    ],
+    "extensions": {
+      "ts": "module"
+    },
+    "nodeArguments": [
+      "--loader=ts-node/esm/transpile-only",
+      "--experimental-specifier-resolution=node"
+    ]
+  }
+}
diff --git a/test/tests/plugins/ava.test.ts b/test/tests/plugins/ava.test.ts
new file mode 100644
index 00000000..31e06ccf
--- /dev/null
+++ b/test/tests/plugins/ava.test.ts
@@ -0,0 +1,21 @@
+import assert from 'node:assert/strict';
+import path from 'node:path';
+import test from 'node:test';
+import * as ava from '../../../src/plugins/ava/index.js';
+import { getManifest } from '../../helpers/index.js';
+
+const cwd = path.resolve('test/fixtures/plugins/ava');
+const manifest = getManifest(cwd);
+const rootConfig = { ignoreBinaries: ['knip'] };
+
+test('Find dependencies in ava configuration (package.json)', async () => {
+  const configFilePath = path.join(cwd, 'package.json');
+  const dependencies = await ava.findDependencies(configFilePath, { cwd, manifest, rootConfig });
+  assert.deepEqual(dependencies, ['ts-node']);
+});
+
+test('Find dependencies in ava configuration (ava.config.mjs)', async () => {
+  const configFilePath = path.join(cwd, 'ava.config.mjs');
+  const dependencies = await ava.findDependencies(configFilePath, { cwd, manifest, rootConfig });
+  assert.deepEqual(dependencies, ['tsconfig-paths']);
+});