knip 2.3.0 → 2.3.1

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 CHANGED
@@ -32,16 +32,11 @@ Knip shines in both small and large projects. It's a fresh take on keeping your
32
32
  [![An orange cow with scissors, Van Gogh style][7]][6] <sup>_“An orange cow with scissors, Van Gogh style” - generated
33
33
  with OpenAI_</sup>
34
34
 
35
- ## Migrating to v2.0.0
36
-
37
- Migrating from v1 to v2 requires no changes in configuration. Mostly it's just a whole lot faster! The [release notes
38
- for v2][8] provide more details.
39
-
40
35
  ## Installation
41
36
 
42
37
  npm install -D knip
43
38
 
44
- Knip supports LTS versions of Node.js, and currently requires at least Node.js v16.17 or v18.6. Knip is _cutting edge!_
39
+ Knip supports LTS versions of Node.js, and currently requires at least Node.js v16.17 or v18.6.
45
40
 
46
41
  ## Configuration
47
42
 
@@ -54,7 +49,7 @@ Knip has good defaults and you can run it without any configuration. Here's the
54
49
  }
55
50
  ```
56
51
 
57
- To be honest, here's the full list of default extensions: `js`, `mjs`, `cjs`, `jsx`, `ts`, `mts`, `cts` and `tsx`.
52
+ Well, almost, this is the full list of default extensions: `js`, `mjs`, `cjs`, `jsx`, `ts`, `mts`, `cts` and `tsx`.
58
53
 
59
54
  ### Entry Files
60
55
 
@@ -65,8 +60,8 @@ Knip looks for entry files at the default locations above, but also in other pla
65
60
  - The `scripts` in package.json may also provide entry files that Knip can use.
66
61
  - Knip does this for each [workspace][1] it finds.
67
62
 
68
- In other words, Knip looks in many places and you may not need much configuration. When everything is according to
69
- defaults you don't even need a `knip.json` file.
63
+ In other words, Knip looks in many places and you may not need much configuration. In a perfectly boring world where
64
+ everything is according to defaults you don't even need a `knip.json` file.
70
65
 
71
66
  Larger projects tend to have more things customized, and therefore probably get more out of Knip with a configuration
72
67
  file. Let's say you are using `.ts` files excusively and have all source files only in the `src` directory:
@@ -178,19 +173,23 @@ The report contains the following types of issues:
178
173
 
179
174
  - **Unused files**: did not find references to this file
180
175
  - **Unused dependencies**: did not find references to this dependency
181
- - **Unlisted dependencies**: used dependencies, but not listed in package.json
176
+ - **Unused devDependencies**: did not find references to this dependency
177
+ - **Unlisted dependencies**: used dependencies, but not listed in package.json _(1)_
182
178
  - **Unresolved imports**: import specifiers that could not be resolved
183
179
  - **Unused exports**: did not find references to this exported variable
184
- - **Unused exports in namespaces**: did not find direct references to this exported variable _(1)_
180
+ - **Unused exports in namespaces**: did not find direct references to this exported variable _(2)_
185
181
  - **Unused exported types**: did not find references to this exported type
186
- - **Unused exported types in namespaces**: did not find direct references to this exported variable _(1)_
182
+ - **Unused exported types in namespaces**: did not find direct references to this exported variable _(2)_
187
183
  - **Unused exported enum members**: did not find references to this member of the exported enum
188
184
  - **Unused exported class members**: did not find references to this member of the exported class
189
185
  - **Duplicate exports**: the same thing is exported more than once
190
186
 
191
187
  When an issue type has zero issues, it is not shown.
192
188
 
193
- _(1)_ The variable or type is not referenced directly, and has become a member of a namespace. Knip can't find a
189
+ _(1)_ If an unlisted dependency is prefixed with `bin:` it means a binary is missing. This often equals the package
190
+ name, but not always (e.g. `tsc` of `typescript` or `webpack` from `webpack-cli`).
191
+
192
+ _(2)_ The variable or type is not referenced directly, and has become a member of a namespace. Knip can't find a
194
193
  reference to it, so you can _probably_ remove it.
195
194
 
196
195
  ### Output filters
@@ -209,8 +208,8 @@ Use `--exclude` to ignore reports you're not interested in:
209
208
 
210
209
  Use `--dependencies` or `--exports` as shortcuts to combine groups of related types.
211
210
 
212
- Still not happy with the results? Getting too much output/false positives? The [FAQ][9] may be useful. Feel free to open
213
- an issue and I'm happy to look into it. Also see the next section on how to [ignore][10] certain false positives:
211
+ Still not happy with the results? Getting too much output/false positives? The [FAQ][8] may be useful. Feel free to open
212
+ an issue and I'm happy to look into it. Also see the next section on how to [ignore][9] certain false positives:
214
213
 
215
214
  ## Ignore
216
215
 
@@ -391,7 +390,7 @@ to override any defaults. Let's take Cypress for example. By default it uses `cy
391
390
  ### Multi-project repositories
392
391
 
393
392
  Some repositories have a single `package.json`, but consist of multiple projects with potentially lots of configuration
394
- files (such as the [Nx "intregrated repo" style][11]). Let's assume some of these projects are apps and have their own
393
+ files (such as the [Nx "intregrated repo" style][10]). Let's assume some of these projects are apps and have their own
395
394
  Cypress configuration and test files. In that case, we could configure the Cypress plugin like this:
396
395
 
397
396
  ```json
@@ -408,7 +407,7 @@ In case a plugin causes issues, it can be disabled by using `false` as its value
408
407
 
409
408
  ### Create a new plugin
410
409
 
411
- Getting false positives because a plugin is missing? Want to help out? Please read more at [writing a plugin][12]. This
410
+ Getting false positives because a plugin is missing? Want to help out? Please read more at [writing a plugin][11]. This
412
411
  guide also contains more details if you want to learn more about plugins and why they are useful.
413
412
 
414
413
  ## Compilers
@@ -431,7 +430,7 @@ export default {
431
430
  };
432
431
  ```
433
432
 
434
- Read [Compilers][13] for more details and examples.
433
+ Read [Compilers][12] for more details and examples.
435
434
 
436
435
  ## Production Mode
437
436
 
@@ -508,7 +507,7 @@ When the provided built-in reporters are not sufficient, a custom reporter can b
508
507
  Pass `--reporter ./my-reporter` from the command-line. The data can then be used to write issues to `stdout`, a JSON or
509
508
  CSV file, or sent to a service.
510
509
 
511
- Find more details and ideas in [custom reporters][14].
510
+ Find more details and ideas in [custom reporters][13].
512
511
 
513
512
  ## Public exports
514
513
 
@@ -557,34 +556,33 @@ When unused dependencies are related to dependencies having a Knip [plugin][1],
557
556
  for that dependency are at custom locations. The default values are at the plugin's documentation, and can be overridden
558
557
  to match the custom location(s).
559
558
 
560
- When the dependencies don't have a Knip plugin yet, please file an issue or [create a new plugin][15].
559
+ When the dependencies don't have a Knip plugin yet, please file an issue or [create a new plugin][14].
561
560
 
562
561
  #### Too many unused exports
563
562
 
564
- When the project is a library and the exports are meant to be used by consumers of the library, there are two options:
563
+ Unused exports of `entry` files are not reported. When exports of other files are marked as unused, because they are
564
+ meant to be used by consumers of the library, there are a few options:
565
565
 
566
- 1. By default, unused exports of `entry` files are not reported. You could re-export from an existing entry file, or
567
- add the containing file to the `entry` array in the configuration.
568
- 2. The exported values or types can be marked [using the JSDoc `@public` tag][16]
566
+ 1. Add the containing file to the `entry` array in the configuration.
567
+ 2. Re-export from an existing entry file.
568
+ 3. Mark the exported value or type [using the JSDoc `@public` tag][15].
569
569
 
570
570
  ### How to start using Knip in CI while having too many issues to sort out?
571
571
 
572
572
  Eventually this type of QA only really works when it's tied to an automated workflow. But with too many issues to
573
573
  resolve this might not be feasible right away, especially in existing larger codebase. Here are a few options that may
574
- help:
574
+ help in the meantime:
575
575
 
576
576
  - Use `--no-exit-code` for exit code 0 in CI.
577
577
  - Use `--include` (or `--exclude`) to report only the issue types that have little or no errors.
578
- - Use separate Knip commands to analyze e.g. unused `--dependencies` and/or `--exports`.
579
- - Use [ignore patterns][10] to filter out the most problematic areas.
580
-
581
- All of this is hiding problems, so please make sure to plan for fixing them and/or open issues here for false positives.
578
+ - Use separate Knip commands to analyze e.g. only `--dependencies` or `--exports`.
579
+ - Use [ignore patterns][9] to filter out the most problematic areas.
582
580
 
583
581
  ## Comparison
584
582
 
585
583
  This table is an ongoing comparison. Based on their docs (please report any mistakes):
586
584
 
587
- | Feature | **knip** | [depcheck][17] | [unimported][18] | [ts-unused-exports][19] | [ts-prune][20] |
585
+ | Feature | **knip** | [depcheck][16] | [unimported][17] | [ts-unused-exports][18] | [ts-prune][19] |
588
586
  | :---------------------- | :------: | :------------: | :--------------: | :---------------------: | :------------: |
589
587
  | Unused files | ✅ | - | ✅ | - | - |
590
588
  | Unused dependencies | ✅ | ✅ | ✅ | - | - |
@@ -620,7 +618,7 @@ The following commands are similar:
620
618
  unimported
621
619
  knip --production --dependencies --include files
622
620
 
623
- Also see [production mode][21].
621
+ Also see [production mode][20].
624
622
 
625
623
  ### ts-unused-exports
626
624
 
@@ -642,13 +640,13 @@ The following commands are similar:
642
640
 
643
641
  Many thanks to some of the early adopters of Knip:
644
642
 
645
- - [Block Protocol][22]
646
- - [DeepmergeTS][23]
647
- - [eslint-plugin-functional][24]
648
- - [freeCodeCamp.org][25]
649
- - [is-immutable-type][26]
650
- - [release-it][27]
651
- - [Template TypeScript Node Package][28]
643
+ - [Block Protocol][21]
644
+ - [DeepmergeTS][22]
645
+ - [eslint-plugin-functional][23]
646
+ - [freeCodeCamp.org][24]
647
+ - [is-immutable-type][25]
648
+ - [release-it][26]
649
+ - [Template TypeScript Node Package][27]
652
650
 
653
651
  ## Knip?!
654
652
 
@@ -659,7 +657,7 @@ for the job. I'm motivated to make knip perfectly suited for the job of cutting
659
657
 
660
658
  Special thanks to the wonderful people who have contributed to this project:
661
659
 
662
- [![Contributors][30]][29]
660
+ [![Contributors][29]][28]
663
661
 
664
662
  [1]: #workspaces-monorepos
665
663
  [2]: #plugins
@@ -668,29 +666,28 @@ Special thanks to the wonderful people who have contributed to this project:
668
666
  [5]: #custom-reporters
669
667
  [6]: https://labs.openai.com/s/xZQACaLepaKya0PRUPtIN5dC
670
668
  [7]: ./assets/cow-with-orange-scissors-van-gogh-style.webp
671
- [8]: ./docs/release-notes-v2.md
672
- [9]: #faq
673
- [10]: #ignore
674
- [11]: https://nx.dev/concepts/integrated-vs-package-based
675
- [12]: ./docs/writing-a-plugin.md
676
- [13]: ./docs/compilers.md
677
- [14]: ./docs/custom-reporters.md
678
- [15]: #create-a-new-plugin
679
- [16]: #public-exports
680
- [17]: https://github.com/depcheck/depcheck
681
- [18]: https://github.com/smeijer/unimported
682
- [19]: https://github.com/pzavolinsky/ts-unused-exports
683
- [20]: https://github.com/nadeesha/ts-prune
684
- [21]: #production-mode
685
- [22]: https://github.com/blockprotocol/blockprotocol
686
- [23]: https://github.com/RebeccaStevens/deepmerge-ts
687
- [24]: https://github.com/eslint-functional/eslint-plugin-functional
688
- [25]: https://github.com/freeCodeCamp/freeCodeCamp
689
- [26]: https://github.com/RebeccaStevens/is-immutable-type
690
- [27]: https://github.com/release-it/release-it
691
- [28]: https://github.com/JoshuaKGoldberg/template-typescript-node-package
692
- [29]: https://github.com/webpro/knip/graphs/contributors
693
- [30]: https://contrib.rocks/image?repo=webpro/knip
669
+ [8]: #faq
670
+ [9]: #ignore
671
+ [10]: https://nx.dev/concepts/integrated-vs-package-based
672
+ [11]: ./docs/writing-a-plugin.md
673
+ [12]: ./docs/compilers.md
674
+ [13]: ./docs/custom-reporters.md
675
+ [14]: #create-a-new-plugin
676
+ [15]: #public-exports
677
+ [16]: https://github.com/depcheck/depcheck
678
+ [17]: https://github.com/smeijer/unimported
679
+ [18]: https://github.com/pzavolinsky/ts-unused-exports
680
+ [19]: https://github.com/nadeesha/ts-prune
681
+ [20]: #production-mode
682
+ [21]: https://github.com/blockprotocol/blockprotocol
683
+ [22]: https://github.com/RebeccaStevens/deepmerge-ts
684
+ [23]: https://github.com/eslint-functional/eslint-plugin-functional
685
+ [24]: https://github.com/freeCodeCamp/freeCodeCamp
686
+ [25]: https://github.com/RebeccaStevens/is-immutable-type
687
+ [26]: https://github.com/release-it/release-it
688
+ [27]: https://github.com/JoshuaKGoldberg/template-typescript-node-package
689
+ [28]: https://github.com/webpro/knip/graphs/contributors
690
+ [29]: https://contrib.rocks/image?repo=webpro/knip
694
691
  [plugin-ava]: ./src/plugins/ava
695
692
  [plugin-babel]: ./src/plugins/babel
696
693
  [plugin-capacitor]: ./src/plugins/capacitor
@@ -72,6 +72,24 @@ export class DependencyDeputy {
72
72
  return true;
73
73
  const workspaceNames = this.isStrict ? [workspace.name] : [workspace.name, ...[...workspace.ancestors].reverse()];
74
74
  const closestWorkspaceName = workspaceNames.find(name => this.isInDependencies(name, packageName));
75
+ if (packageName.startsWith('bin:')) {
76
+ const pkg = packageName.replace(/^bin:/, '');
77
+ if (IGNORED_GLOBAL_BINARIES.includes(pkg))
78
+ return true;
79
+ if (this.getWorkspaceManifest(workspace.name)?.ignoreBinaries.includes(pkg))
80
+ return true;
81
+ for (const name of workspaceNames) {
82
+ const binaries = this.getInstalledBinaries(name);
83
+ if (binaries?.has(pkg)) {
84
+ const dependencies = binaries.get(pkg);
85
+ if (dependencies?.size) {
86
+ dependencies.forEach(dependency => this.addReferencedDependency(name, dependency));
87
+ return true;
88
+ }
89
+ }
90
+ }
91
+ return false;
92
+ }
75
93
  if (this.getWorkspaceManifest(workspace.name)?.ignoreDependencies.includes(packageName))
76
94
  return true;
77
95
  const typesPackageName = !isDefinitelyTyped(packageName) && getDefinitelyTypedFor(packageName);
@@ -81,20 +99,6 @@ export class DependencyDeputy {
81
99
  closestWorkspaceNameForTypes && this.addReferencedDependency(closestWorkspaceNameForTypes, typesPackageName);
82
100
  return true;
83
101
  }
84
- if (IGNORED_GLOBAL_BINARIES.includes(packageName))
85
- return true;
86
- if (this.getWorkspaceManifest(workspace.name)?.ignoreBinaries.includes(packageName))
87
- return true;
88
- for (const name of workspaceNames) {
89
- const binaries = this.getInstalledBinaries(name);
90
- if (binaries?.has(packageName)) {
91
- const dependencies = binaries.get(packageName);
92
- if (dependencies?.size) {
93
- dependencies.forEach(dependency => this.addReferencedDependency(name, dependency));
94
- return true;
95
- }
96
- }
97
- }
98
102
  return false;
99
103
  }
100
104
  isInDependencies(workspaceName, packageName) {
@@ -207,7 +207,7 @@ export class WorkspaceWorker {
207
207
  if (!pluginConfig)
208
208
  continue;
209
209
  const patterns = this.getConfigurationFilePatterns(pluginName);
210
- const configFilePaths = await _pureGlob({ patterns, cwd, ignore });
210
+ const configFilePaths = await _pureGlob({ patterns, cwd, ignore, gitignore: false });
211
211
  debugLogArray(`Found ${plugin.NAME} config file paths`, configFilePaths);
212
212
  if (configFilePaths.length === 0)
213
213
  continue;
@@ -13,7 +13,7 @@ const getDependenciesFromScripts = (npmScripts, options = {}) => {
13
13
  return identifier;
14
14
  const packageName = getPackageNameFromModuleSpecifier(stripBinary(identifier));
15
15
  if (!packageName.startsWith('.'))
16
- return packageName;
16
+ return `bin:${packageName}`;
17
17
  }));
18
18
  };
19
19
  export const _getDependenciesFromScripts = timerify(getDependenciesFromScripts);
@@ -48,7 +48,7 @@ export const getDependenciesDeep = async (configFilePath, dependencies = new Set
48
48
  return dependencies;
49
49
  };
50
50
  const resolvePackageName = (namespace, pluginName) => {
51
- return pluginName.includes(namespace)
51
+ return pluginName.includes(namespace + '-')
52
52
  ? pluginName
53
53
  : pluginName.startsWith('@')
54
54
  ? pluginName.includes('/')
@@ -60,16 +60,17 @@ export const resolvePluginPackageName = (pluginName) => resolvePackageName('esli
60
60
  const resolveExtendsSpecifier = (specifier) => {
61
61
  if (isInternal(specifier))
62
62
  return;
63
- if (specifier.includes(':')) {
64
- const strippedSpecifier = specifier.replace(/(^plugin:|:.+$)/, '').replace(/\/(eslint-)?recommended$/, '');
65
- const pluginName = getPackageNameFromModuleSpecifier(strippedSpecifier);
66
- if (pluginName === 'eslint')
67
- return;
68
- if (pluginName.includes('eslint-'))
69
- return pluginName;
70
- return resolvePackageName('eslint-plugin', pluginName);
71
- }
72
- return specifier.includes('eslint') ? specifier : resolvePackageName('eslint-config', specifier);
63
+ if (/\/eslint-(config|plugin)/.test(specifier))
64
+ return specifier;
65
+ const strippedSpecifier = specifier
66
+ .replace(/(^plugin:|:.+$)/, '')
67
+ .replace(/\/(eslint-)?(recommended.*|strict|all)$/, '');
68
+ if (/eslint-(config|plugin)-/.test(strippedSpecifier))
69
+ return strippedSpecifier;
70
+ const pluginName = getPackageNameFromModuleSpecifier(strippedSpecifier);
71
+ if (pluginName === 'eslint')
72
+ return;
73
+ return resolvePackageName(specifier.startsWith('plugin:') ? 'eslint-plugin' : 'eslint-config', pluginName);
73
74
  };
74
75
  const getImportPluginDependencies = (settings) => {
75
76
  const knownKeys = ['typescript'];
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "2.3.0";
1
+ export declare const version = "2.3.1";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '2.3.0';
1
+ export const version = '2.3.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
4
4
  "description": "Find unused files, dependencies and exports in your TypeScript and JavaScript projects",
5
5
  "homepage": "https://github.com/webpro/knip",
6
6
  "repository": "github:webpro/knip",