Repository: webpro/knip
Pull request #80 (2023-03-12 08:24:39)

feat: add new plugin cspell
https://www.npmjs.com/package/cspell


Patch:
diff --git a/README.md b/README.md
index 0d48758f..94856626 100644
--- a/README.md
+++ b/README.md
@@ -275,34 +275,35 @@ Knip contains a growing list of plugins:
 - [Capacitor][14]
 - [Changesets][15]
 - [commitlint][16]
-- [Cypress][17]
-- [ESLint][18]
-- [Gatsby][19]
-- [GitHub Actions][20]
-- [husky][21]
-- [Jest][22]
-- [Lefthook][23]
-- [lint-staged][24]
-- [markdownlint][25]
-- [Mocha][26]
-- [Next.js][27]
-- [npm-package-json-lint][28]
-- [Nx][29]
-- [nyc][30]
-- [Playwright][31]
-- [PostCSS][32]
-- [Prettier][33]
-- [Release It][34]
-- [Remark][35]
-- [Remix][36]
-- [Rollup][37]
-- [Sentry][38]
-- [Storybook][39]
-- [Stryker][40]
-- [TypeDoc][41]
-- [TypeScript][42]
-- [Vitest][43]
-- [Webpack][44]
+- [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]
+- [Sentry][39]
+- [Storybook][40]
+- [Stryker][41]
+- [TypeDoc][42]
+- [TypeScript][43]
+- [Vitest][44]
+- [Webpack][45]
 
 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
@@ -310,7 +311,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][45]
+- `config` - custom dependency resolvers are applied to the [config files][46]
 - `entry` - files to include with the analysis of the rest of the source code
 
 See each plugin's documentation for its default values.
@@ -336,7 +337,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][46]):
+(see [TEST_FILE_PATTERNS in constants.ts][47]):
 
 - `**/*.{test,spec}.{js,jsx,ts,tsx,mjs,cjs}`
 - `**/__tests__/**/*.{js,jsx,ts,tsx,mjs,cjs}`
@@ -418,10 +419,10 @@ Each workspace can also have its own `paths` configured. Note that Knip `paths`
 
 Knip provides the following built-in reporters:
 
-- [`codeowners`][47]
-- [`compact`][48]
-- [`json`][49]
-- [`symbol`][50] (default)
+- [`codeowners`][48]
+- [`compact`][49]
+- [`json`][50]
+- [`symbol`][51] (default)
 
 The `compact` reporter shows the sorted files first, and then a list of symbols:
 
@@ -448,7 +449,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][51].
+Find more details and ideas in [custom reporters][52].
 
 ## Libraries and "unused" exports
 
@@ -506,7 +507,7 @@ When unused dependencies are related to dependencies having a Knip [plugin][1],
 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][52].
+When the dependencies don't have a Knip plugin yet, please file an issue or [create a new plugin][53].
 
 #### Too many unused exports
 
@@ -514,7 +515,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][53].
+2.  The exported values or types can be marked [using the JSDoc `@public` tag][54].
 
 ### How to start using Knip in CI while having too many issues to sort out?
 
@@ -534,7 +535,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][54] | [unimported][55] | [ts-unused-exports][56] | [ts-prune][57] |
+| Feature                            | **knip** | [depcheck][55] | [unimported][56] | [ts-unused-exports][57] | [ts-prune][58] |
 | :--------------------------------- | :------: | :------------: | :--------------: | :---------------------: | :------------: |
 | Unused files                       |    ✅    |       -        |        ✅        |            -            |       -        |
 | Unused dependencies                |    ✅    |       ✅       |        ✅        |            -            |       -        |
@@ -569,7 +570,7 @@ The following commands are similar:
     unimported
     knip --production --dependencies --include files
 
-Also see [production mode][58].
+Also see [production mode][59].
 
 ### ts-unused-exports
 
@@ -615,45 +616,46 @@ for the job. I'm motivated to make knip perfectly suited for the job of cutting
 [14]: ./src/plugins/capacitor
 [15]: ./src/plugins/changesets
 [16]: ./src/plugins/commitlint
-[17]: ./src/plugins/cypress
-[18]: ./src/plugins/eslint
-[19]: ./src/plugins/gatsby
-[20]: ./src/plugins/github-actions
-[21]: ./src/plugins/husky
-[22]: ./src/plugins/jest
-[23]: ./src/plugins/lefthook
-[24]: ./src/plugins/lint-staged
-[25]: ./src/plugins/markdownlint
-[26]: ./src/plugins/mocha
-[27]: ./src/plugins/next
-[28]: ./src/plugins/npm-package-json-lint
-[29]: ./src/plugins/nx
-[30]: ./src/plugins/nyc
-[31]: ./src/plugins/playwright
-[32]: ./src/plugins/postcss
-[33]: ./src/plugins/prettier
-[34]: ./src/plugins/release-it
-[35]: ./src/plugins/remark
-[36]: ./src/plugins/remix
-[37]: ./src/plugins/rollup
-[38]: ./src/plugins/sentry
-[39]: ./src/plugins/storybook
-[40]: ./src/plugins/stryker
-[41]: ./src/plugins/typedoc
-[42]: ./src/plugins/typescript
-[43]: ./src/plugins/vitest
-[44]: ./src/plugins/webpack
-[45]: #config
-[46]: https://github.com/webpro/knip/blob/main/src/constants.ts
-[47]: #code-owners
-[48]: #compact
-[49]: #json
-[50]: #symbol-default
-[51]: ./docs/custom-reporters.md
-[52]: #create-a-new-plugin
-[53]: #libraries-and-unused-exports
-[54]: https://github.com/depcheck/depcheck
-[55]: https://github.com/smeijer/unimported
-[56]: https://github.com/pzavolinsky/ts-unused-exports
-[57]: https://github.com/nadeesha/ts-prune
-[58]: #production-mode
+[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/sentry
+[40]: ./src/plugins/storybook
+[41]: ./src/plugins/stryker
+[42]: ./src/plugins/typedoc
+[43]: ./src/plugins/typescript
+[44]: ./src/plugins/vitest
+[45]: ./src/plugins/webpack
+[46]: #config
+[47]: https://github.com/webpro/knip/blob/main/src/constants.ts
+[48]: #code-owners
+[49]: #compact
+[50]: #json
+[51]: #symbol-default
+[52]: ./docs/custom-reporters.md
+[53]: #create-a-new-plugin
+[54]: #libraries-and-unused-exports
+[55]: https://github.com/depcheck/depcheck
+[56]: https://github.com/smeijer/unimported
+[57]: https://github.com/pzavolinsky/ts-unused-exports
+[58]: https://github.com/nadeesha/ts-prune
+[59]: #production-mode
diff --git a/schema.json b/schema.json
index 341968ac..a98e78f1 100644
--- a/schema.json
+++ b/schema.json
@@ -180,6 +180,10 @@
           "title": "commitlint plugin configuration (https://github.com/webpro/knip/blob/main/src/plugins/commitlint/README.md)",
           "$ref": "#/definitions/plugin"
         },
+        "cspell": {
+          "title": "cspell plugin configuration (https://github.com/webpro/knip/blob/main/src/plugins/cspell/README.md)",
+          "$ref": "#/definitions/plugin"
+        },
         "cypress": {
           "title": "Cypress plugin configuration (https://github.com/webpro/knip/blob/main/src/plugins/cypress/README.md)",
           "$ref": "#/definitions/plugin"
diff --git a/src/plugins/commitlint/README.md b/src/plugins/commitlint/README.md
index bc228d2a..87a2d284 100644
--- a/src/plugins/commitlint/README.md
+++ b/src/plugins/commitlint/README.md
@@ -12,7 +12,12 @@ or `devDependencies`:
 ```json
 {
   "commitlint": {
-    "config": ["commitlint.config.{js,ts}"]
+    "config": [
+      ".commitlintrc",
+      ".commitlintrc.{json,yaml,yml,js,cjs,ts,cts}",
+      "commitlint.config.{js,cjs,ts,cts}",
+      "package.json"
+    ]
   }
 }
 ```
diff --git a/src/plugins/commitlint/index.ts b/src/plugins/commitlint/index.ts
index d4371097..91758d2a 100644
--- a/src/plugins/commitlint/index.ts
+++ b/src/plugins/commitlint/index.ts
@@ -4,6 +4,7 @@ import { hasDependency } from '../../util/plugin.js';
 import type { IsPluginEnabledCallback, GenericPluginCallback } from '../../types/plugins.js';
 
 // https://commitlint.js.org
+// https://github.com/conventional-changelog/commitlint#config
 
 type CommitLintConfig = {
   extends: string[];
@@ -16,10 +17,17 @@ export const ENABLERS = ['@commitlint/cli'];
 
 export const isEnabled: IsPluginEnabledCallback = ({ dependencies }) => hasDependency(dependencies, ENABLERS);
 
-export const CONFIG_FILE_PATTERNS = ['commitlint.config.{js,ts}'];
-
-const findCommitLintDependencies: GenericPluginCallback = async configFilePath => {
-  const config: CommitLintConfig = await _load(configFilePath);
+export const CONFIG_FILE_PATTERNS = [
+  '.commitlintrc',
+  '.commitlintrc.{json,yaml,yml,js,cjs,ts,cts}',
+  'commitlint.config.{js,cjs,ts,cts}',
+  'package.json',
+];
+
+const findCommitLintDependencies: GenericPluginCallback = async (configFilePath, { manifest }) => {
+  const config: CommitLintConfig = configFilePath.endsWith('package.json')
+    ? manifest.commitlint
+    : await _load(configFilePath);
   return config?.extends ? [config.extends].flat() : [];
 };
 
diff --git a/src/plugins/cspell/README.md b/src/plugins/cspell/README.md
new file mode 100644
index 00000000..c74dc2e4
--- /dev/null
+++ b/src/plugins/cspell/README.md
@@ -0,0 +1,22 @@
+# cspell
+
+## Enabled
+
+This plugin is enabled when any of the following package names and/or regular expressions has a match in `dependencies`
+or `devDependencies`:
+
+- `cspell`
+
+## Default configuration
+
+```json
+{
+  "cspell": {
+    "config": ["cspell.config.{js,cjs,json,yaml,yml}", "cspell.{json,yaml,yml}", ".c{s,S}pell.json", "cSpell.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/cspell/index.ts b/src/plugins/cspell/index.ts
new file mode 100644
index 00000000..092be29b
--- /dev/null
+++ b/src/plugins/cspell/index.ts
@@ -0,0 +1,30 @@
+import { _load } from '../../util/loader.js';
+import { getPackageNameFromModuleSpecifier } from '../../util/modules.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/streetsidesoftware/cspell/tree/main/packages/cspell#customization
+
+export const NAME = 'cspell';
+
+/** @public */
+export const ENABLERS = ['cspell'];
+
+export const isEnabled: IsPluginEnabledCallback = ({ dependencies }) => hasDependency(dependencies, ENABLERS);
+
+export const CONFIG_FILE_PATTERNS = [
+  'cspell.config.{js,cjs,json,yaml,yml}',
+  'cspell.{json,yaml,yml}',
+  '.c{s,S}pell.json',
+  'cSpell.json',
+];
+
+const findPluginDependencies: GenericPluginCallback = async configFilePath => {
+  const config: PluginConfig = await _load(configFilePath);
+  const imports = config?.import ?? [];
+  return imports.map(getPackageNameFromModuleSpecifier);
+};
+
+export const findDependencies = timerify(findPluginDependencies);
diff --git a/src/plugins/cspell/types.ts b/src/plugins/cspell/types.ts
new file mode 100644
index 00000000..b15817e1
--- /dev/null
+++ b/src/plugins/cspell/types.ts
@@ -0,0 +1,3 @@
+export type PluginConfig = {
+  import?: string[];
+};
diff --git a/src/plugins/index.ts b/src/plugins/index.ts
index cf53f877..5998b82d 100644
--- a/src/plugins/index.ts
+++ b/src/plugins/index.ts
@@ -2,6 +2,7 @@ export * as babel from './babel/index.js';
 export * as capacitor from './capacitor/index.js';
 export * as changesets from './changesets/index.js';
 export * as commitlint from './commitlint/index.js';
+export * as cspell from './cspell/index.js';
 export * as cypress from './cypress/index.js';
 export * as eslint from './eslint/index.js';
 export * as gatsby from './gatsby/index.js';


Test patch:
diff --git a/test/fixtures/plugins/commitlint/.commitlintrc.json b/test/fixtures/plugins/commitlint/.commitlintrc.json
new file mode 100644
index 00000000..d3d7f0cd
--- /dev/null
+++ b/test/fixtures/plugins/commitlint/.commitlintrc.json
@@ -0,0 +1,1 @@
+{ "extends": ["@commitlint/config-conventional"] }
diff --git a/test/fixtures/plugins/commitlint/package.json b/test/fixtures/plugins/commitlint/package.json
new file mode 100644
index 00000000..1acf59e1
--- /dev/null
+++ b/test/fixtures/plugins/commitlint/package.json
@@ -0,0 +1,7 @@
+{
+  "commitlint": {
+    "extends": [
+      "@commitlint/config-conventional"
+    ]
+  }
+}
diff --git a/test/fixtures/plugins/cspell/.cspell.json b/test/fixtures/plugins/cspell/.cspell.json
new file mode 100644
index 00000000..7287cea0
--- /dev/null
+++ b/test/fixtures/plugins/cspell/.cspell.json
@@ -0,0 +1,6 @@
+{
+  "version": "0.2",
+  "import": [
+    "@cspell/dict-cryptocurrencies/cspell-ext.json"
+  ]
+}
diff --git a/test/tests/plugins/commitlint.test.ts b/test/tests/plugins/commitlint.test.ts
index 77745f2d..a0d205a1 100644
--- a/test/tests/plugins/commitlint.test.ts
+++ b/test/tests/plugins/commitlint.test.ts
@@ -2,11 +2,25 @@ import assert from 'node:assert/strict';
 import path from 'node:path';
 import test from 'node:test';
 import * as commitlint from '../../../src/plugins/commitlint/index.js';
+import { getManifest } from '../../helpers/index.js';
 
 const cwd = path.resolve('test/fixtures/plugins/commitlint');
+const manifest = getManifest(cwd);
 
-test('Find dependencies in commitlint configuration', async () => {
+test('Find dependencies in commitlint configuration (js)', async () => {
   const configFilePath = path.join(cwd, 'commitlint.config.js');
-  const dependencies = await commitlint.findDependencies(configFilePath);
+  const dependencies = await commitlint.findDependencies(configFilePath, {});
+  assert.deepEqual(dependencies, ['@commitlint/config-conventional']);
+});
+
+test('Find dependencies in commitlint configuration (json)', async () => {
+  const configFilePath = path.join(cwd, '.commitlintrc.json');
+  const dependencies = await commitlint.findDependencies(configFilePath, {});
+  assert.deepEqual(dependencies, ['@commitlint/config-conventional']);
+});
+
+test('Find dependencies in commitlint configuration (package.json)', async () => {
+  const configFilePath = path.join(cwd, 'package.json');
+  const dependencies = await commitlint.findDependencies(configFilePath, { manifest });
   assert.deepEqual(dependencies, ['@commitlint/config-conventional']);
 });
diff --git a/test/tests/plugins/cspell.test.ts b/test/tests/plugins/cspell.test.ts
new file mode 100644
index 00000000..504058c4
--- /dev/null
+++ b/test/tests/plugins/cspell.test.ts
@@ -0,0 +1,12 @@
+import assert from 'node:assert/strict';
+import path from 'node:path';
+import test from 'node:test';
+import * as cspell from '../../../src/plugins/cspell/index.js';
+
+const cwd = path.resolve('test/fixtures/plugins/cspell');
+
+test('Find dependencies in cspell configuration (json)', async () => {
+  const configFilePath = path.join(cwd, '.cspell.json');
+  const dependencies = await cspell.findDependencies(configFilePath, {});
+  assert.deepEqual(dependencies, ['@cspell/dict-cryptocurrencies']);
+});