knip 0.13.1 → 1.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -148
- package/dist/cli.js +11 -21
- package/dist/configuration-chief.d.ts +36 -0
- package/dist/configuration-chief.js +190 -0
- package/dist/dependency-deputy.d.ts +59 -0
- package/dist/dependency-deputy.js +134 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.js +223 -103
- package/dist/issue-collector.d.ts +34 -0
- package/dist/issue-collector.js +92 -0
- package/dist/issues/initializers.d.ts +4 -0
- package/dist/issues/initializers.js +41 -0
- package/dist/npm-scripts/helpers.d.ts +2 -0
- package/dist/npm-scripts/helpers.js +54 -0
- package/dist/npm-scripts/index.d.ts +5 -0
- package/dist/npm-scripts/index.js +39 -0
- package/dist/plugins/babel/helpers.d.ts +2 -0
- package/dist/plugins/babel/helpers.js +22 -0
- package/dist/plugins/babel/index.d.ts +4 -0
- package/dist/plugins/babel/index.js +41 -0
- package/dist/plugins/babel/types.d.ts +4 -0
- package/dist/{types.js → plugins/babel/types.js} +0 -0
- package/dist/plugins/capacitor/index.d.ts +4 -0
- package/dist/plugins/capacitor/index.js +11 -0
- package/dist/plugins/capacitor/types.d.ts +3 -0
- package/dist/plugins/capacitor/types.js +1 -0
- package/dist/plugins/changesets/index.d.ts +4 -0
- package/dist/plugins/changesets/index.js +13 -0
- package/dist/plugins/cypress/index.d.ts +4 -0
- package/dist/plugins/cypress/index.js +7 -0
- package/dist/plugins/eslint/helpers.d.ts +2 -0
- package/dist/plugins/eslint/helpers.js +17 -0
- package/dist/plugins/eslint/index.d.ts +4 -0
- package/dist/plugins/eslint/index.js +39 -0
- package/dist/plugins/eslint/types.d.ts +10 -0
- package/dist/plugins/eslint/types.js +1 -0
- package/dist/plugins/gatsby/index.d.ts +5 -0
- package/dist/plugins/gatsby/index.js +30 -0
- package/dist/plugins/gatsby/types.d.ts +15 -0
- package/dist/plugins/gatsby/types.js +1 -0
- package/dist/plugins/index.d.ts +15 -0
- package/dist/plugins/index.js +15 -0
- package/dist/plugins/jest/index.d.ts +5 -0
- package/dist/plugins/jest/index.js +40 -0
- package/dist/plugins/next/index.d.ts +5 -0
- package/dist/plugins/next/index.js +6 -0
- package/dist/plugins/nx/index.d.ts +4 -0
- package/dist/plugins/nx/index.js +14 -0
- package/dist/plugins/playwright/index.d.ts +4 -0
- package/dist/plugins/playwright/index.js +3 -0
- package/dist/plugins/postcss/index.d.ts +5 -0
- package/dist/plugins/postcss/index.js +15 -0
- package/dist/plugins/postcss/types.d.ts +3 -0
- package/dist/plugins/postcss/types.js +1 -0
- package/dist/plugins/remark/index.d.ts +4 -0
- package/dist/plugins/remark/index.js +10 -0
- package/dist/plugins/remix/index.d.ts +5 -0
- package/dist/plugins/remix/index.js +11 -0
- package/dist/plugins/rollup/index.d.ts +4 -0
- package/dist/plugins/rollup/index.js +3 -0
- package/dist/plugins/storybook/index.d.ts +6 -0
- package/dist/plugins/storybook/index.js +23 -0
- package/dist/plugins/storybook/types.d.ts +6 -0
- package/dist/plugins/storybook/types.js +1 -0
- package/dist/project-principal.d.ts +16 -0
- package/dist/project-principal.js +70 -0
- package/dist/reporters/codeowners.d.ts +1 -1
- package/dist/reporters/compact.d.ts +1 -1
- package/dist/reporters/index.d.ts +4 -4
- package/dist/reporters/index.js +2 -2
- package/dist/reporters/json.d.ts +1 -1
- package/dist/reporters/json.js +1 -1
- package/dist/reporters/symbols.d.ts +1 -1
- package/dist/reporters/symbols.js +2 -3
- package/dist/source-lab.d.ts +22 -0
- package/dist/source-lab.js +156 -0
- package/dist/types/cli.d.ts +7 -0
- package/dist/types/cli.js +1 -0
- package/dist/types/config.d.ts +41 -0
- package/dist/types/config.js +1 -0
- package/dist/types/issues.d.ts +39 -0
- package/dist/types/issues.js +1 -0
- package/dist/types/plugins.d.ts +11 -0
- package/dist/types/plugins.js +1 -0
- package/dist/types/validate.d.ts +1081 -0
- package/dist/types/validate.js +54 -0
- package/dist/types/workspace.d.ts +13 -0
- package/dist/types/workspace.js +1 -0
- package/dist/util/array.d.ts +2 -0
- package/dist/util/array.js +2 -0
- package/dist/util/constants.d.ts +4 -0
- package/dist/util/constants.js +4 -0
- package/dist/util/debug.d.ts +5 -8
- package/dist/util/debug.js +48 -17
- package/dist/util/externalImports.d.ts +2 -0
- package/dist/util/externalImports.js +24 -0
- package/dist/util/fs.d.ts +2 -2
- package/dist/util/fs.js +9 -11
- package/dist/util/glob.d.ts +13 -2
- package/dist/util/glob.js +26 -3
- package/dist/{help.d.ts → util/help.d.ts} +0 -0
- package/dist/{help.js → util/help.js} +7 -10
- package/dist/util/loader.d.ts +2 -2
- package/dist/util/loader.js +7 -5
- package/dist/{log.d.ts → util/log.d.ts} +0 -0
- package/dist/{log.js → util/log.js} +0 -0
- package/dist/util/members.d.ts +1 -1
- package/dist/util/members.js +1 -1
- package/dist/util/modules.d.ts +1 -0
- package/dist/util/modules.js +10 -0
- package/dist/util/parseArgs.d.ts +6 -3
- package/dist/util/parseArgs.js +6 -3
- package/dist/util/path.js +1 -1
- package/dist/util/performance.d.ts +1 -1
- package/dist/util/performance.js +1 -1
- package/dist/util/project.d.ts +1 -1
- package/dist/util/project.js +10 -10
- package/dist/util/resolveIncludedIssueTypes.d.ts +9 -0
- package/dist/util/resolveIncludedIssueTypes.js +35 -0
- package/dist/util/sort.d.ts +1 -0
- package/dist/util/sort.js +9 -0
- package/dist/util/tsconfig-loader.d.ts +2 -0
- package/dist/util/tsconfig-loader.js +10 -0
- package/dist/workspace-worker.d.ts +70 -0
- package/dist/workspace-worker.js +226 -0
- package/package.json +21 -9
- package/dist/progress.d.ts +0 -5
- package/dist/progress.js +0 -35
- package/dist/runner.d.ts +0 -5
- package/dist/runner.js +0 -190
- package/dist/types.d.ts +0 -87
- package/dist/util/config.d.ts +0 -6
- package/dist/util/config.js +0 -49
- package/dist/util/dependencies.d.ts +0 -7
- package/dist/util/dependencies.js +0 -50
package/README.md
CHANGED
|
@@ -19,7 +19,9 @@ The dots don't connect themselves. This is where Knip comes in:
|
|
|
19
19
|
- [x] Finds **unused files, dependencies and exports**.
|
|
20
20
|
- [x] Finds used dependencies not listed in `package.json`.
|
|
21
21
|
- [x] Finds duplicate exports.
|
|
22
|
-
- [x]
|
|
22
|
+
- [x] Finds unused members of classes and enums
|
|
23
|
+
- [x] Supports workspaces/monorepos
|
|
24
|
+
- [x] Growing list of plugins (= less to configure and custom dependency resolvers)
|
|
23
25
|
- [x] Supports JavaScript (without `tsconfig.json`, or TypeScript `allowJs: true`).
|
|
24
26
|
- [x] Features multiple [reporters][1] and supports [custom reporters][2] (think JSON and `CODEOWNERS`)
|
|
25
27
|
- [x] Run Knip as part of your CI environment to detect issues and prevent regressions.
|
|
@@ -40,10 +42,7 @@ over new features:
|
|
|
40
42
|
|
|
41
43
|
### Upcoming Features
|
|
42
44
|
|
|
43
|
-
- [ ] Custom dependency resolvers: find dependencies used in npm scripts.
|
|
44
|
-
- [ ] Custom dependency resolvers: find unused and unlisted plugins for Webpack, ESLint & Babel, etc. (#7)
|
|
45
45
|
- [ ] Smart default configurations and more fine-grained configuration options.
|
|
46
|
-
- [ ] Full support for monorepos (partial [monorepos support][7] with `--dir` exists).
|
|
47
46
|
- [ ] Fix issues: remove `export` keyword, uninstall unused dependencies, delete files (like `--fix` of ESLint).
|
|
48
47
|
- [ ] Add more reporters and report customization options (#3).
|
|
49
48
|
|
|
@@ -60,12 +59,12 @@ Create a configuration file, let's give it the default name `knip.json` with the
|
|
|
60
59
|
```json
|
|
61
60
|
{
|
|
62
61
|
"entryFiles": ["src/index.ts"],
|
|
63
|
-
"projectFiles": ["src/**/*.ts"
|
|
62
|
+
"projectFiles": ["src/**/*.ts"]
|
|
64
63
|
}
|
|
65
64
|
```
|
|
66
65
|
|
|
67
|
-
The `entryFiles` target the starting point(s) to resolve
|
|
68
|
-
|
|
66
|
+
The `entryFiles` target the starting point(s) to resolve code dependencies. The `projectFiles` should contain all files
|
|
67
|
+
it should match them against, including potentially unused files.
|
|
69
68
|
|
|
70
69
|
Then run the checks:
|
|
71
70
|
|
|
@@ -77,10 +76,10 @@ This will analyze the project and output unused files, exports, types and duplic
|
|
|
77
76
|
|
|
78
77
|
Knip works by creating two sets of files:
|
|
79
78
|
|
|
80
|
-
1.
|
|
81
|
-
2.
|
|
82
|
-
3. The
|
|
83
|
-
4. Then
|
|
79
|
+
1. The set of files resolved from the `entryFiles`. In other words, the files that the entry files depend upon.
|
|
80
|
+
2. The set of `projectFiles`.
|
|
81
|
+
3. The project files that are part of entry files and its dependencies will be reported as unused files (in red).
|
|
82
|
+
4. Then everything else (in blue) will be analyzed for unused exports and dependencies.
|
|
84
83
|
|
|
85
84
|
![How it works][8]
|
|
86
85
|
|
|
@@ -90,15 +89,12 @@ Knip works by creating two sets of files:
|
|
|
90
89
|
knip [options]
|
|
91
90
|
|
|
92
91
|
Options:
|
|
93
|
-
-c/--config [file] Configuration file path (default: ./knip.json or package.json#knip)
|
|
92
|
+
-c/--config [file] Configuration file path (default: ./knip.json, knip.jsonc or package.json#knip)
|
|
94
93
|
-t/--tsConfig [file] TypeScript configuration path (default: ./tsconfig.json)
|
|
95
|
-
--
|
|
94
|
+
--production Analyze only production source files (e.g. no tests, devDependencies, exported types)
|
|
95
|
+
--workspace Analyze a single workspace (default: analyze all configured workspaces)
|
|
96
96
|
--include Report only listed issue type(s), can be repeated
|
|
97
97
|
--exclude Exclude issue type(s) from report, can be repeated
|
|
98
|
-
--ignore Ignore files matching this glob pattern, can be repeated
|
|
99
|
-
--no-gitignore Don't use .gitignore
|
|
100
|
-
--dev Include `devDependencies` in report(s)
|
|
101
|
-
--include-entry-files Report unused exports and types for entry files
|
|
102
98
|
--no-progress Don't show dynamic progress updates
|
|
103
99
|
--max-issues Maximum number of issues before non-zero exit code (default: 0)
|
|
104
100
|
--reporter Select reporter: symbols, compact, codeowners, json (default: symbols)
|
|
@@ -112,9 +108,9 @@ Knip works by creating two sets of files:
|
|
|
112
108
|
Examples:
|
|
113
109
|
|
|
114
110
|
$ knip
|
|
115
|
-
$ knip --
|
|
111
|
+
$ knip --production
|
|
112
|
+
$ knip --workspace packages/client --include files
|
|
116
113
|
$ knip -c ./knip.js --reporter compact
|
|
117
|
-
$ knip --ignore 'lib/**/*.ts' --ignore build
|
|
118
114
|
$ knip --reporter codeowners --reporter-options '{"path":".github/CODEOWNERS"}'
|
|
119
115
|
|
|
120
116
|
More info: https://github.com/webpro/knip
|
|
@@ -180,6 +176,68 @@ Use `--exclude` to ignore reports you're not interested in:
|
|
|
180
176
|
|
|
181
177
|
Use `--performance` to see where most of the time is spent.
|
|
182
178
|
|
|
179
|
+
## Workspaces & Monorepos
|
|
180
|
+
|
|
181
|
+
Workspaces and monorepos are handled out-of-the-box by Knip. Every workspace that is part of the Knip configuration will
|
|
182
|
+
be part of the analysis. Here's a simple example:
|
|
183
|
+
|
|
184
|
+
```jsonc
|
|
185
|
+
{
|
|
186
|
+
"ignoreFiles": "**/fixtures/**",
|
|
187
|
+
"ignoreBinaries": ["deno", "git"],
|
|
188
|
+
"ignoreWorkspaces": ["packages/ignore-me"],
|
|
189
|
+
"workspaces": {
|
|
190
|
+
"packages/*": {
|
|
191
|
+
"entryFiles": "{index,cli}.ts!",
|
|
192
|
+
"projectFiles": "**/*.ts"
|
|
193
|
+
},
|
|
194
|
+
"packages/exception": {
|
|
195
|
+
"entryFiles": "something/different.js"
|
|
196
|
+
},
|
|
197
|
+
"not-a-workspace/in-package.json/but-has-package.json": {
|
|
198
|
+
"entryFiles": ["src/index.ts"],
|
|
199
|
+
"projectFiles": "src/**/*.ts"
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
All `workspaces` or `workspaces.packages` in `package.json` with a match in `workspaces` of `Knip.json` are part of the
|
|
206
|
+
analysis.
|
|
207
|
+
|
|
208
|
+
Extra "workspaces" not cnfigured as a workspace in the root `package.json` can be configured as well, Knip is happy to
|
|
209
|
+
analyze unused dependencies and exports from any directory with a `package.json`.
|
|
210
|
+
|
|
211
|
+
## Plugins
|
|
212
|
+
|
|
213
|
+
Knip contains a growing list of plugins:
|
|
214
|
+
|
|
215
|
+
- Babel
|
|
216
|
+
- Capacitor
|
|
217
|
+
- Changesets
|
|
218
|
+
- Cypress
|
|
219
|
+
- ESLint
|
|
220
|
+
- Gatsby
|
|
221
|
+
- Jest
|
|
222
|
+
- Next.js
|
|
223
|
+
- Nx
|
|
224
|
+
- Playwright
|
|
225
|
+
- PostCSS
|
|
226
|
+
- Remark
|
|
227
|
+
- Remix
|
|
228
|
+
- Rollup
|
|
229
|
+
- Storybook
|
|
230
|
+
|
|
231
|
+
Plugins are automatically activated, no need to enable anything. Each plugin is automatically enabled based on simple
|
|
232
|
+
heuristics. Most of them check whether one or one of a few (dev) dependencies are listed in `package.json`. Once
|
|
233
|
+
enabled, they add the right set of configuration and entry files for Knip to analyze.
|
|
234
|
+
|
|
235
|
+
Most configuration files are easy to find dependencies in. Yet some of the plugins include custom dependency resolvers.
|
|
236
|
+
For instance, the `eslint` plugin tells Knip that an `"import"` entry in the array of plugins means that the
|
|
237
|
+
`eslint-plugin-import` dependency should be installed. Or the `storybook` plugin understands that
|
|
238
|
+
`core.builder: 'webpack5'` in `main.js` means that the `@storybook/builder-webpack5` and `@storybook/manager-webpack5`
|
|
239
|
+
dependencies are required.
|
|
240
|
+
|
|
183
241
|
## Configuration
|
|
184
242
|
|
|
185
243
|
### Libraries versus Applications
|
|
@@ -200,128 +258,31 @@ export const merge = function () {};
|
|
|
200
258
|
|
|
201
259
|
Knip does not report public exports and types as unused.
|
|
202
260
|
|
|
203
|
-
### Production
|
|
204
|
-
|
|
205
|
-
Feels like you're getting too many false positives? Let's talk about `entryFiles` and `projectFiles`.
|
|
206
|
-
|
|
207
|
-
#### Production code
|
|
208
|
-
|
|
209
|
-
The default configuration for Knip is very strict and targets production code. Non-production files such as tests should
|
|
210
|
-
not be part of the `entryFiles` and `projectFiles`. Here's why: test and other non-production files often import
|
|
211
|
-
production files, which will prevent the production files from being reported as unused. For best results:
|
|
212
|
-
|
|
213
|
-
- Include only production entry files to the `entryFiles`.
|
|
214
|
-
- Include only and all production files to the `projectFiles`.
|
|
215
|
-
- If necessary, add globs to exclude non-production files from the `projectFiles` (using negation pattern).
|
|
261
|
+
### Production mode
|
|
216
262
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
#### Non-production code
|
|
263
|
+
The default configuration for Knip is loose and targets all project code. Non-production files such as tests often
|
|
264
|
+
import production files, which will prevent the production files from being reported as unused.
|
|
220
265
|
|
|
221
266
|
Non-production code includes files such as unit tests, end-to-end tests, tooling, scripts, Storybook stories, etc. Think
|
|
222
267
|
of it the same way as the convention to split `dependencies` and `devDependencies` in `package.json`.
|
|
223
268
|
|
|
224
|
-
To
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
- Include all production files to the `projectFiles`.
|
|
228
|
-
- If necessary, add globs for non-production files to the `projectFiles`.
|
|
229
|
-
- Set `dev: true` in the configuration or add `--dev` as a command line flag (to add `devDependencies`).
|
|
230
|
-
|
|
231
|
-
Here's an example:
|
|
269
|
+
To exclude non-production code such as configuration files and tests, use production mode with `--production`. This will
|
|
270
|
+
only include the files meant for production use. Add an exclamation mark behind the patterns that are meant for
|
|
271
|
+
production like so:
|
|
232
272
|
|
|
233
273
|
```json
|
|
234
274
|
{
|
|
235
|
-
"
|
|
236
|
-
"entryFiles": ["src/index.ts", "src/**/*.spec.ts", "src/**/*.e2e.ts"],
|
|
275
|
+
"entryFiles": ["build/script.js", "src/index.ts!"],
|
|
237
276
|
"projectFiles": ["src/**/*.ts"]
|
|
238
277
|
}
|
|
239
278
|
```
|
|
240
279
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
An alternative way to store `dev` configuration is in this example `package.json`:
|
|
244
|
-
|
|
245
|
-
```json
|
|
246
|
-
{
|
|
247
|
-
"name": "my-package",
|
|
248
|
-
"scripts": {
|
|
249
|
-
"knip": "knip"
|
|
250
|
-
},
|
|
251
|
-
"knip": {
|
|
252
|
-
"entryFiles": ["src/index.ts"],
|
|
253
|
-
"projectFiles": ["src/**/*.ts", "!**/*.spec.ts"],
|
|
254
|
-
"dev": {
|
|
255
|
-
"entryFiles": ["src/index.ts", "src/**/*.spec.ts", "src/**/*.e2e.ts"],
|
|
256
|
-
"projectFiles": ["src/**/*.ts"]
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
Using the `--dev` flag will now switch to the non-production analysis.
|
|
263
|
-
|
|
264
|
-
Depending on the complexity of the project, be aware that it might require some fine-tuning on your end.
|
|
280
|
+
Configuration, build and test files are not included. Knip finds unused files and export values in production code only.
|
|
265
281
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
## More configuration examples
|
|
272
|
-
|
|
273
|
-
### Monorepos
|
|
274
|
-
|
|
275
|
-
#### Separate packages
|
|
276
|
-
|
|
277
|
-
In repos with multiple (publishable) packages, the `--dir` option comes in handy. With similar package structures, the
|
|
278
|
-
packages can be configured using globs:
|
|
279
|
-
|
|
280
|
-
```json
|
|
281
|
-
{
|
|
282
|
-
"packages/*": {
|
|
283
|
-
"entryFiles": ["src/index.ts"],
|
|
284
|
-
"projectFiles": ["src/**/*.{ts,tsx}", "!**/*.spec.{ts,tsx}"]
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
Packages can also be explicitly configured per package directory.
|
|
290
|
-
|
|
291
|
-
To analyze the packages separately, using the matching pattern from the configuration file:
|
|
292
|
-
|
|
293
|
-
knip --dir packages/client
|
|
294
|
-
knip --dir packages/services
|
|
295
|
-
|
|
296
|
-
#### Connected projects
|
|
297
|
-
|
|
298
|
-
Let's take another example Nx project configuration using Next.js, Jest and Storybook, which has multiple apps and libs.
|
|
299
|
-
They are not published separately and don't have their own `package.json`.
|
|
300
|
-
|
|
301
|
-
This configuration file can also be a JavaScript file, which allows to add logic and/or comments (e.g. `knip.js`):
|
|
302
|
-
|
|
303
|
-
```js
|
|
304
|
-
const entryFiles = ['apps/**/pages/**/*.{js,ts,tsx}'];
|
|
305
|
-
|
|
306
|
-
const projectFiles = [
|
|
307
|
-
'{apps,libs}/**/*.{ts,tsx}',
|
|
308
|
-
// Next.js
|
|
309
|
-
'!**/next.config.js',
|
|
310
|
-
'!**/apps/**/public/**',
|
|
311
|
-
'!**/apps/**/next-env.d.ts'
|
|
312
|
-
// Jest
|
|
313
|
-
'!**/jest.config.ts',
|
|
314
|
-
'!**/*.spec.{ts,tsx}',
|
|
315
|
-
// Storybook
|
|
316
|
-
'!**/.storybook/**',
|
|
317
|
-
'!**/*.stories.tsx',
|
|
318
|
-
];
|
|
319
|
-
|
|
320
|
-
module.exports = { entryFiles, projectFiles };
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
This should give good results about unused files, dependencies and exports for the monorepo. After the first run, the
|
|
324
|
-
configuration can be tweaked further to the project structure.
|
|
282
|
+
Plugins also have this distinction. For instance, Next.js entry files for pages (`pages/**/*.tsx`) and Remix routes
|
|
283
|
+
(`app/routes/**/*.tsx`) are production code, while Jest and Playwright entry files (e.g. `*.spec.ts`) are not. All of
|
|
284
|
+
this is handled automatically by Knip and its plugins. You only need to point Knip to additional files or custom file
|
|
285
|
+
locations. The more plugins Knip will have, the more projects can be analyzed out of the box!
|
|
325
286
|
|
|
326
287
|
## Reporters
|
|
327
288
|
|
|
@@ -346,7 +307,7 @@ type ReporterOptions = {
|
|
|
346
307
|
issues: Issues;
|
|
347
308
|
cwd: string;
|
|
348
309
|
workingDir: string;
|
|
349
|
-
|
|
310
|
+
isProduction: boolean;
|
|
350
311
|
options: string;
|
|
351
312
|
};
|
|
352
313
|
```
|
|
@@ -511,7 +472,7 @@ This table is a work in progress, but here's a first impression. Based on their
|
|
|
511
472
|
| Unused files | ✅ | - | ✅ | - | - | - |
|
|
512
473
|
| Unused dependencies | ✅ | ✅ | ✅ | - | - | - |
|
|
513
474
|
| Unlisted dependencies | ✅ | ✅ | ✅ | - | - | - |
|
|
514
|
-
| [Custom dependency resolvers][21] |
|
|
475
|
+
| [Custom dependency resolvers][21] | ✅ | ✅ | ❌ | - | - | - |
|
|
515
476
|
| Unused exports | ✅ | - | - | ✅ | ✅ | ✅ |
|
|
516
477
|
| Unused class members | ✅ | - | - | - | - | - |
|
|
517
478
|
| Unused enum members | ✅ | - | - | - | - | - |
|
|
@@ -520,28 +481,11 @@ This table is a work in progress, but here's a first impression. Based on their
|
|
|
520
481
|
| Custom reporters | ✅ | - | - | - | - | - |
|
|
521
482
|
| JavaScript support | ✅ | ✅ | ✅ | - | - | ✅ |
|
|
522
483
|
| Configure entry files | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
523
|
-
| [Support monorepos][22] |
|
|
484
|
+
| [Support monorepos][22] | ✅ | - | - | - | - | - |
|
|
524
485
|
| ESLint plugin available | - | - | - | ✅ | - | - |
|
|
525
486
|
|
|
526
487
|
✅ = Supported, ❌ = Not supported, - = Out of scope
|
|
527
488
|
|
|
528
|
-
## Monorepos
|
|
529
|
-
|
|
530
|
-
Knip wants to [support monorepos][14] properly, the first steps in this direction are implemented.
|
|
531
|
-
|
|
532
|
-
## Custom dependency resolvers
|
|
533
|
-
|
|
534
|
-
Using a string like `"plugin:cypress/recommended"` in the `extends` property of a `.eslintrc.json` in a package
|
|
535
|
-
directory of a monorepo is nice for DX. But Knip will need some help to find it and to understand this resolves to the
|
|
536
|
-
`eslint-plugin-cypress` dependency. Or see it is not listed in `package.json`. Or that the dependency is still listed,
|
|
537
|
-
but no longer in use. Many popular projects reference plugins in similar ways, such as Babel, Webpack and Storybook.
|
|
538
|
-
|
|
539
|
-
Big compliments to [depcheck][23] which already does this! They call this "specials". This is on [Knip's roadmap][24],
|
|
540
|
-
as well, with the additional ambition to also find used dependencies that are not listed in `package.json`.
|
|
541
|
-
|
|
542
|
-
unimported is strict in this regard and works based on production files and `dependencies`, so does not have custom
|
|
543
|
-
dependency resolvers which are usually only needed for `devDependencies`.
|
|
544
|
-
|
|
545
489
|
## TypeScript language services
|
|
546
490
|
|
|
547
491
|
TypeScript language services could play a major role in most of the "unused" areas, as they have an overview of the
|
package/dist/cli.js
CHANGED
|
@@ -1,41 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import { printHelp } from './help.js';
|
|
3
|
+
import { register } from 'esbuild-register/dist/node.js';
|
|
4
|
+
import { printHelp } from './util/help.js';
|
|
6
5
|
import reporters from './reporters/index.js';
|
|
7
6
|
import { ConfigurationError } from './util/errors.js';
|
|
7
|
+
import parsedArgs from './util/parseArgs.js';
|
|
8
8
|
import { measure } from './util/performance.js';
|
|
9
|
-
import
|
|
10
|
-
|
|
9
|
+
import { main } from './index.js';
|
|
10
|
+
register();
|
|
11
|
+
const { values: { help, 'no-gitignore': isNoGitIgnore = false, strict: isStrict = false, production: isProduction = false, 'no-progress': noProgress = false, reporter = 'symbols', 'reporter-options': reporterOptions = '', 'max-issues': maxIssues = '0', debug: isDebug = false, }, } = parsedArgs;
|
|
11
12
|
if (help) {
|
|
12
13
|
printHelp();
|
|
13
14
|
process.exit(0);
|
|
14
15
|
}
|
|
15
16
|
const cwd = process.cwd();
|
|
16
|
-
const workingDir = dir ? path.resolve(dir) : cwd;
|
|
17
17
|
const isShowProgress = !isDebug && noProgress === false && process.stdout.isTTY && typeof process.stdout.cursorTo === 'function';
|
|
18
|
-
const printReport = reporter in reporters ? reporters[reporter] : await
|
|
18
|
+
const printReport = reporter in reporters ? reporters[reporter] : await import(path.join(cwd, reporter));
|
|
19
19
|
const run = async () => {
|
|
20
20
|
try {
|
|
21
21
|
const { report, issues, counters } = await main({
|
|
22
22
|
cwd,
|
|
23
|
-
workingDir,
|
|
24
|
-
configFilePath,
|
|
25
|
-
tsConfigFilePath,
|
|
26
|
-
include,
|
|
27
|
-
exclude,
|
|
28
|
-
ignore,
|
|
29
23
|
gitignore: !isNoGitIgnore,
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
isStrict,
|
|
25
|
+
isProduction,
|
|
32
26
|
isShowProgress,
|
|
33
|
-
debug: {
|
|
34
|
-
isEnabled: isDebug,
|
|
35
|
-
level: isDebug ? Number(debugLevel) : 0,
|
|
36
|
-
},
|
|
37
27
|
});
|
|
38
|
-
await printReport({ report, issues, cwd,
|
|
28
|
+
await printReport({ report, issues, cwd, isProduction, options: reporterOptions });
|
|
39
29
|
const totalErrorCount = Object.keys(report)
|
|
40
30
|
.filter(reportGroup => report[reportGroup])
|
|
41
31
|
.reduce((errorCount, reportGroup) => errorCount + counters[reportGroup], 0);
|
|
@@ -52,4 +42,4 @@ const run = async () => {
|
|
|
52
42
|
throw error;
|
|
53
43
|
}
|
|
54
44
|
};
|
|
55
|
-
run();
|
|
45
|
+
await run();
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { LocalConfiguration } from './types/validate.js';
|
|
3
|
+
import type { Configuration, WorkspaceConfiguration } from './types/config.js';
|
|
4
|
+
import type { PackageJson } from 'type-fest';
|
|
5
|
+
type ConfigurationManagerOptions = {
|
|
6
|
+
cwd?: string;
|
|
7
|
+
};
|
|
8
|
+
export default class ConfigurationChief {
|
|
9
|
+
cwd: string;
|
|
10
|
+
config: Configuration;
|
|
11
|
+
manifestPath: undefined | string;
|
|
12
|
+
manifest: undefined | PackageJson;
|
|
13
|
+
constructor({ cwd }: ConfigurationManagerOptions);
|
|
14
|
+
loadLocalConfig(): Promise<void>;
|
|
15
|
+
normalize(rawLocalConfig: z.infer<typeof LocalConfiguration>): {
|
|
16
|
+
include: string[];
|
|
17
|
+
exclude: string[];
|
|
18
|
+
ignoreBinaries: string[];
|
|
19
|
+
ignoreFiles: string[];
|
|
20
|
+
ignoreWorkspaces: string[];
|
|
21
|
+
workspaces: Record<string, WorkspaceConfiguration>;
|
|
22
|
+
};
|
|
23
|
+
private getManifestWorkspaces;
|
|
24
|
+
private getConfiguredWorkspaces;
|
|
25
|
+
getActiveWorkspaces(): Promise<{
|
|
26
|
+
name: string;
|
|
27
|
+
dir: string;
|
|
28
|
+
config: WorkspaceConfiguration;
|
|
29
|
+
ancestors: string[];
|
|
30
|
+
}[]>;
|
|
31
|
+
private getConfigKeyForWorkspace;
|
|
32
|
+
private hasConfigForWorkspace;
|
|
33
|
+
getConfigForWorkspace(workspaceName: string): WorkspaceConfiguration;
|
|
34
|
+
resolveIncludedIssueTypes(): import("./types/issues.js").Report;
|
|
35
|
+
}
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import micromatch from 'micromatch';
|
|
3
|
+
import { LocalConfiguration } from './types/validate.js';
|
|
4
|
+
import { arrayify } from './util/array.js';
|
|
5
|
+
import { ROOT_WORKSPACE_NAME } from './util/constants.js';
|
|
6
|
+
import { ConfigurationError } from './util/errors.js';
|
|
7
|
+
import { findFile, loadJSON } from './util/fs.js';
|
|
8
|
+
import { _dirGlob } from './util/glob.js';
|
|
9
|
+
import parsedArgs from './util/parseArgs.js';
|
|
10
|
+
import { resolveIncludedIssueTypes } from './util/resolveIncludedIssueTypes.js';
|
|
11
|
+
import { workspaceSorter } from './util/sort.js';
|
|
12
|
+
const { values: { workspace: rawWorkspaceArg, config: rawConfigArg, include = [], exclude = [], strict: isStrict = false, production: isProduction = false, }, } = parsedArgs;
|
|
13
|
+
const defaultConfig = {
|
|
14
|
+
include: [],
|
|
15
|
+
exclude: [],
|
|
16
|
+
ignoreBinaries: [],
|
|
17
|
+
ignoreFiles: [],
|
|
18
|
+
ignoreWorkspaces: [],
|
|
19
|
+
workspaces: {
|
|
20
|
+
[ROOT_WORKSPACE_NAME]: {
|
|
21
|
+
entryFiles: ['index.{js,ts}', 'src/index.{js,ts}'],
|
|
22
|
+
projectFiles: ['**/*.ts'],
|
|
23
|
+
ignore: [],
|
|
24
|
+
paths: {},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
const PLUGIN_NAMES = [
|
|
29
|
+
'babel',
|
|
30
|
+
'capacitor',
|
|
31
|
+
'changesets',
|
|
32
|
+
'cypress',
|
|
33
|
+
'eslint',
|
|
34
|
+
'gatsby',
|
|
35
|
+
'jest',
|
|
36
|
+
'next',
|
|
37
|
+
'nx',
|
|
38
|
+
'playwright',
|
|
39
|
+
'postcss',
|
|
40
|
+
'remark',
|
|
41
|
+
'remix',
|
|
42
|
+
'rollup',
|
|
43
|
+
'storybook',
|
|
44
|
+
];
|
|
45
|
+
export default class ConfigurationChief {
|
|
46
|
+
cwd = process.cwd();
|
|
47
|
+
config;
|
|
48
|
+
manifestPath;
|
|
49
|
+
manifest;
|
|
50
|
+
constructor({ cwd }) {
|
|
51
|
+
this.cwd = cwd ?? this.cwd;
|
|
52
|
+
this.config = defaultConfig;
|
|
53
|
+
}
|
|
54
|
+
async loadLocalConfig() {
|
|
55
|
+
const manifestPath = await findFile(this.cwd, 'package.json');
|
|
56
|
+
const manifest = manifestPath && (await loadJSON(manifestPath));
|
|
57
|
+
if (!manifestPath || !manifest) {
|
|
58
|
+
throw new ConfigurationError('Unable to find package.json');
|
|
59
|
+
}
|
|
60
|
+
this.manifestPath = manifestPath;
|
|
61
|
+
this.manifest = manifest;
|
|
62
|
+
const configFilePath = rawConfigArg ?? 'knip.json';
|
|
63
|
+
const resolvedConfigFilePath = (await findFile(this.cwd, configFilePath)) ?? (!rawConfigArg && (await findFile(this.cwd, configFilePath + 'c')));
|
|
64
|
+
if (rawConfigArg && !resolvedConfigFilePath && !manifest.knip) {
|
|
65
|
+
throw new ConfigurationError(`Unable to find ${rawConfigArg} or package.json#knip`);
|
|
66
|
+
}
|
|
67
|
+
const rawLocalConfig = resolvedConfigFilePath ? await loadJSON(resolvedConfigFilePath) : manifest.knip;
|
|
68
|
+
if (rawLocalConfig) {
|
|
69
|
+
this.config = this.normalize(LocalConfiguration.parse(rawLocalConfig));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
normalize(rawLocalConfig) {
|
|
73
|
+
const workspaces = rawLocalConfig.workspaces ?? {
|
|
74
|
+
[ROOT_WORKSPACE_NAME]: {
|
|
75
|
+
...rawLocalConfig,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
const include = rawLocalConfig.include ?? defaultConfig.include;
|
|
79
|
+
const exclude = rawLocalConfig.exclude ?? defaultConfig.exclude;
|
|
80
|
+
const ignoreBinaries = rawLocalConfig.ignoreBinaries ?? defaultConfig.ignoreBinaries;
|
|
81
|
+
const ignoreFiles = arrayify(rawLocalConfig.ignoreFiles ?? defaultConfig.ignoreFiles);
|
|
82
|
+
const ignoreWorkspaces = rawLocalConfig.ignoreWorkspaces ?? defaultConfig.ignoreWorkspaces;
|
|
83
|
+
return {
|
|
84
|
+
include,
|
|
85
|
+
exclude,
|
|
86
|
+
ignoreBinaries,
|
|
87
|
+
ignoreFiles,
|
|
88
|
+
ignoreWorkspaces,
|
|
89
|
+
workspaces: Object.entries(workspaces)
|
|
90
|
+
.filter(([workspaceName]) => !ignoreWorkspaces.includes(workspaceName))
|
|
91
|
+
.reduce((workspaces, workspace) => {
|
|
92
|
+
const [workspaceName, workspaceConfig] = workspace;
|
|
93
|
+
const entryFiles = arrayify(workspaceConfig.entryFiles);
|
|
94
|
+
workspaces[workspaceName] = {
|
|
95
|
+
entryFiles,
|
|
96
|
+
projectFiles: arrayify(workspaceConfig.projectFiles ?? entryFiles),
|
|
97
|
+
ignore: arrayify(workspaceConfig.ignore),
|
|
98
|
+
paths: workspaceConfig.paths ?? {},
|
|
99
|
+
};
|
|
100
|
+
for (const [pluginName, pluginConfig] of Object.entries(workspaceConfig)) {
|
|
101
|
+
if (PLUGIN_NAMES.includes(pluginName)) {
|
|
102
|
+
const isObject = typeof pluginConfig !== 'string' && !Array.isArray(pluginConfig);
|
|
103
|
+
const config = isObject ? arrayify(pluginConfig.config) : arrayify(pluginConfig);
|
|
104
|
+
const entryFiles = isObject && 'entryFiles' in pluginConfig ? arrayify(pluginConfig.entryFiles) : [];
|
|
105
|
+
const projectFiles = isObject && 'projectFiles' in pluginConfig ? arrayify(pluginConfig.projectFiles) : entryFiles;
|
|
106
|
+
workspaces[workspaceName][pluginName] = {
|
|
107
|
+
config,
|
|
108
|
+
entryFiles,
|
|
109
|
+
projectFiles,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return workspaces;
|
|
114
|
+
}, {}),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
async getManifestWorkspaces() {
|
|
118
|
+
const { workspaces } = this.manifest ?? {};
|
|
119
|
+
const patterns = workspaces ? (Array.isArray(workspaces) ? workspaces : workspaces.packages ?? []) : [];
|
|
120
|
+
return _dirGlob({ patterns, cwd: this.cwd, ignore: this.config.ignoreWorkspaces });
|
|
121
|
+
}
|
|
122
|
+
getConfiguredWorkspaces() {
|
|
123
|
+
return this.config.workspaces ? Object.keys(this.config.workspaces) : [];
|
|
124
|
+
}
|
|
125
|
+
async getActiveWorkspaces() {
|
|
126
|
+
const manifestWorkspaces = await this.getManifestWorkspaces();
|
|
127
|
+
const additionalWorkspaces = Object.keys(this.config.workspaces).filter(name => !name.includes('*') && !manifestWorkspaces.includes(name));
|
|
128
|
+
const rootWorkspace = {
|
|
129
|
+
name: ROOT_WORKSPACE_NAME,
|
|
130
|
+
dir: this.cwd,
|
|
131
|
+
config: this.getConfigForWorkspace(ROOT_WORKSPACE_NAME),
|
|
132
|
+
ancestors: [],
|
|
133
|
+
};
|
|
134
|
+
if (manifestWorkspaces.length === 0 && !rawWorkspaceArg)
|
|
135
|
+
return [rootWorkspace];
|
|
136
|
+
if (rawWorkspaceArg) {
|
|
137
|
+
return [
|
|
138
|
+
rootWorkspace,
|
|
139
|
+
{
|
|
140
|
+
name: rawWorkspaceArg,
|
|
141
|
+
dir: path.resolve(this.cwd, rawWorkspaceArg),
|
|
142
|
+
config: this.getConfigForWorkspace(rawWorkspaceArg),
|
|
143
|
+
ancestors: [ROOT_WORKSPACE_NAME],
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
}
|
|
147
|
+
const workspaces = [...manifestWorkspaces, ...additionalWorkspaces];
|
|
148
|
+
const activeWorkspaces = workspaces.filter(workspaceName => this.hasConfigForWorkspace(workspaceName));
|
|
149
|
+
return activeWorkspaces.sort(workspaceSorter).map(name => ({
|
|
150
|
+
name,
|
|
151
|
+
dir: path.resolve(this.cwd, name),
|
|
152
|
+
config: this.getConfigForWorkspace(name),
|
|
153
|
+
ancestors: activeWorkspaces.reduce((ancestors, ancestorName) => {
|
|
154
|
+
if (name === ancestorName)
|
|
155
|
+
return ancestors;
|
|
156
|
+
if (ancestorName === ROOT_WORKSPACE_NAME || name.startsWith(ancestorName)) {
|
|
157
|
+
if (this.hasConfigForWorkspace(ancestorName)) {
|
|
158
|
+
ancestors.push(ancestorName);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return ancestors;
|
|
162
|
+
}, []),
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
getConfigKeyForWorkspace(workspaceName) {
|
|
166
|
+
const configuredWorkspaces = this.getConfiguredWorkspaces();
|
|
167
|
+
return configuredWorkspaces
|
|
168
|
+
.sort(workspaceSorter)
|
|
169
|
+
.reverse()
|
|
170
|
+
.find(pattern => micromatch.isMatch(workspaceName, pattern));
|
|
171
|
+
}
|
|
172
|
+
hasConfigForWorkspace(workspaceName) {
|
|
173
|
+
return Boolean(this.getConfigKeyForWorkspace(workspaceName));
|
|
174
|
+
}
|
|
175
|
+
getConfigForWorkspace(workspaceName) {
|
|
176
|
+
const key = this.getConfigKeyForWorkspace(workspaceName);
|
|
177
|
+
if (key) {
|
|
178
|
+
return this.config?.workspaces?.[key] ?? { entryFiles: [], projectFiles: [], paths: {}, ignore: [] };
|
|
179
|
+
}
|
|
180
|
+
return { entryFiles: [], projectFiles: [], paths: {}, ignore: [] };
|
|
181
|
+
}
|
|
182
|
+
resolveIncludedIssueTypes() {
|
|
183
|
+
return resolveIncludedIssueTypes(include, exclude, {
|
|
184
|
+
include: this.config.include ?? [],
|
|
185
|
+
exclude: this.config.exclude ?? [],
|
|
186
|
+
isProduction,
|
|
187
|
+
isStrict,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|