knip 2.21.2 → 2.23.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 CHANGED
@@ -25,54 +25,56 @@ The dots don't connect themselves. This is where Knip comes in:
25
25
  - [x] Finds duplicate exports
26
26
  - [x] Supports any combination of JavaScript and TypeScript
27
27
  - [x] Multiple built-in [reporters][4] (or use [custom reporters][5] and [preprocessors][6])
28
+ - [x] Understands [JSDoc/TSDoc tags][7] (e.g. `@public` and `@internal`)
28
29
  - [x] Run Knip as part of your CI environment to detect issues and prevent regressions
29
30
 
30
31
  Knip shines in both small and large projects. It's a fresh take on keeping your projects clean & tidy!
31
32
 
32
- [![An orange cow with scissors, Van Gogh style][8]][7] <sup>_“An orange cow with scissors, Van Gogh style” - generated
33
+ [![An orange cow with scissors, Van Gogh style][9]][8] <sup>_“An orange cow with scissors, Van Gogh style” - generated
33
34
  with OpenAI_</sup>
34
35
 
35
- For updates or questions, come hang out in [The Knip Barn (Discord)][9], or follow [@webprolific (Twitter)][10] or
36
- [@webpro (fosstodon.org)][11]. Please use GitHub to [report issues][12].
36
+ For updates or questions, come hang out in [The Knip Barn (Discord)][10], or follow [@webprolific (Twitter)][11] or
37
+ [@webpro (fosstodon.org)][12]. Please use GitHub to [report issues][13].
37
38
 
38
39
  ## Contents
39
40
 
40
- - [Getting Started][13]
41
- - [Installation][14]
42
- - [Default Configuration][15]
43
- - [Let's Go!][16]
44
- - [Configuration][17]
45
- - [Entry Files][18]
41
+ - [Getting Started][14]
42
+ - [Installation][15]
43
+ - [Default Configuration][16]
44
+ - [Let's Go!][17]
45
+ - [Configuration][18]
46
+ - [Entry Files][19]
46
47
  - [Workspaces][1]
47
48
  - [Plugins][2]
48
49
  - [Compilers][3]
49
- - [Ignore files, binaries, dependencies and workspaces][19]
50
- - [Public exports][20]
51
- - [Ignore exports used in file][21]
52
- - [Include exports in entry files][22]
53
- - [Paths][23]
54
- - [Production Mode][24]
55
- - [Strict][25]
56
- - [Ignore `@internal` exports][26]
57
- - [Plugins][27]
58
- - [Output][28]
59
- - [Screenshots][29]
60
- - [Reading the report][30]
61
- - [Rules & Filters][31]
50
+ - [Ignore files, binaries, dependencies and workspaces][20]
51
+ - [Public exports][21]
52
+ - [Ignore exports used in file][22]
53
+ - [Include exports in entry files][23]
54
+ - [Paths][24]
55
+ - [Production Mode][25]
56
+ - [Strict][26]
57
+ - [Ignore `@internal` exports][27]
58
+ - [Plugins][28]
59
+ - [Output][29]
60
+ - [Screenshots][30]
61
+ - [Reading the report][31]
62
+ - [Rules & Filters][32]
62
63
  - [Reporters][4]
63
- - [Fixing Issues][32]
64
- - [Command Line Options][33]
65
- - [Potential boost with `--no-gitignore`][34]
66
- - [Comparison & Migration][35]
67
- - [depcheck][36]
68
- - [unimported][37]
69
- - [ts-unused-exports][38]
70
- - [ts-prune][39]
71
- - [Projects using Knip][40]
72
- - [Articles, etc.][41]
73
- - [Why "Knip"?][42]
74
- - [Really, another unused file/dependency/export finder?][43]
75
- - [Contributors][44]
64
+ - [Fixing Issues][33]
65
+ - [JSDoc tags][7]
66
+ - [Command Line Options][34]
67
+ - [Potential boost with `--no-gitignore`][35]
68
+ - [Comparison & Migration][36]
69
+ - [depcheck][37]
70
+ - [unimported][38]
71
+ - [ts-unused-exports][39]
72
+ - [ts-prune][40]
73
+ - [Projects using Knip][41]
74
+ - [Articles, etc.][42]
75
+ - [Why "Knip"?][43]
76
+ - [Really, another unused file/dependency/export finder?][44]
77
+ - [Contributors][45]
76
78
 
77
79
  ## Getting Started
78
80
 
@@ -93,7 +95,7 @@ Knip has good defaults and you can run it without any configuration. The (simpli
93
95
  }
94
96
  ```
95
97
 
96
- There's more, jump to [Entry Files][18] for details.
98
+ There's more, jump to [Entry Files][19] for details.
97
99
 
98
100
  Places where Knip looks for configuration (ordered by priority):
99
101
 
@@ -135,7 +137,7 @@ Run the checks with `npx knip`. Or first add this script to `package.json`:
135
137
  Then use `npm run knip` to analyze the project and output unused files, dependencies and exports. Knip works just fine
136
138
  with `yarn` or `pnpm` as well.
137
139
 
138
- See [Command Line Options][33] for an overview of available CLI options.
140
+ See [Command Line Options][34] for an overview of available CLI options.
139
141
 
140
142
  ## Configuration
141
143
 
@@ -198,7 +200,7 @@ Here's an example `knip.json` configuration with some custom `entry` and `projec
198
200
  ```
199
201
 
200
202
  It might be useful to run Knip first with no or little configuration to see where it needs custom `entry` and/or
201
- `project` files. Each workspace has the same [default configuration][17].
203
+ `project` files. Each workspace has the same [default configuration][18].
202
204
 
203
205
  The root workspace is named `"."` under `workspaces` (like in the example).
204
206
 
@@ -321,7 +323,7 @@ has them at `e2e-tests/*.spec.ts`. Here's how to configure this:
321
323
  #### Multi-project repositories
322
324
 
323
325
  Some repositories have a single `package.json`, but consist of multiple projects with configuration files across the
324
- repository (such as the [Nx "intregrated repo" style][45]). Let's assume some of these projects are apps and have their
326
+ repository (such as the [Nx "intregrated repo" style][46]). Let's assume some of these projects are apps and have their
325
327
  own Cypress configuration and test files. In that case, we could configure the Cypress plugin like this:
326
328
 
327
329
  ```json
@@ -338,7 +340,7 @@ In case a plugin causes issues, it can be disabled by using `false` as its value
338
340
 
339
341
  #### Create a new plugin
340
342
 
341
- Getting false positives because a plugin is missing? Want to help out? Please read more at [writing a plugin][46]. This
343
+ Getting false positives because a plugin is missing? Want to help out? Please read more at [writing a plugin][47]. This
342
344
  guide also contains more details if you want to learn more about plugins and why they are useful.
343
345
 
344
346
  ### Compilers
@@ -361,7 +363,7 @@ export default {
361
363
  };
362
364
  ```
363
365
 
364
- Read [Compilers][47] for more details and examples.
366
+ Read [Compilers][48] for more details and examples.
365
367
 
366
368
  ### Ignore files, binaries, dependencies and workspaces
367
369
 
@@ -528,7 +530,7 @@ The report contains the following types of issues:
528
530
 
529
531
  When an issue type has zero issues, it is not shown.
530
532
 
531
- Getting too many reported issues and false positives? Read more about [handling issues][48].
533
+ Getting too many reported issues and false positives? Read more about [handling issues][49].
532
534
 
533
535
  ### Rules & Filters
534
536
 
@@ -555,7 +557,7 @@ Example:
555
557
  }
556
558
  ```
557
559
 
558
- See [reading the report][30] for the list of issue types.
560
+ See [reading the report][31] for the list of issue types.
559
561
 
560
562
  The rules are modeled after the ESLint `rules` configuration, and could be extended in the future. For instance, to
561
563
  apply filters or configurations only to a specific issue type.
@@ -576,7 +578,7 @@ Use `--exclude` to ignore reports you're not interested in:
576
578
 
577
579
  Use `--dependencies` or `--exports` as shortcuts to combine groups of related types.
578
580
 
579
- See [reading the report][30] for the list of issue types.
581
+ See [reading the report][31] for the list of issue types.
580
582
 
581
583
  #### When to use rules or filters
582
584
 
@@ -600,13 +602,13 @@ Knip provides the following built-in reporters:
600
602
 
601
603
  When the provided built-in reporters are not sufficient, a custom reporter can be implemented.
602
604
 
603
- Find more details in [reporters and preprocessors][49].
605
+ Find more details in [reporters and preprocessors][50].
604
606
 
605
607
  #### Preprocessers
606
608
 
607
609
  Use preprocessers to modify the results before they're passed to the reporter(s).
608
610
 
609
- Find more details in [reporters and preprocessors][49].
611
+ Find more details in [reporters and preprocessors][50].
610
612
 
611
613
  ## Fixing Issues
612
614
 
@@ -624,9 +626,20 @@ Tip: back up files or use an VCS like Git before deleting files or making change
624
626
 
625
627
  Repeat the process to reveal new unused files and exports. It's so liberating to remove unused things!
626
628
 
627
- Getting too many reported issues and false positives? Read more about [handling issues][48] describing potential causes
629
+ Getting too many reported issues and false positives? Read more about [handling issues][49] describing potential causes
628
630
  for false positives, and how to handle them.
629
631
 
632
+ ## JSDoc tags
633
+
634
+ Knip takes the following JSDoc/TSDoc tags into account:
635
+
636
+ | Tag | Description |
637
+ | :---------- | :------------------------------------------------------ |
638
+ | `@public` | Do not report this unused export, type or member |
639
+ | `@beta` | ^^ Idem |
640
+ | `@internal` | Do not report this unused export in `--production` mode |
641
+ | `@alias` | Do not report this duplicate export |
642
+
630
643
  ## Command Line Options
631
644
 
632
645
  $ npx knip --help
@@ -640,7 +653,7 @@ for false positives, and how to handle them.
640
653
  --production Analyze only production source files (e.g. no tests, devDependencies, exported types)
641
654
  --strict Consider only direct dependencies of workspace (not devDependencies, not other workspaces)
642
655
  --ignore-internal Ignore exports with tag @internal (JSDoc/TSDoc)
643
- --workspace [dir] Analyze a single workspace (default: analyze all configured workspaces)
656
+ -W, --workspace [dir] Analyze a single workspace (default: analyze all configured workspaces)
644
657
  --no-gitignore Don't use .gitignore
645
658
  --include Report only provided issue type(s), can be comma-separated or repeated (1)
646
659
  --exclude Exclude provided issue type(s) from report, can be comma-separated or repeated (1)
@@ -675,13 +688,13 @@ for false positives, and how to handle them.
675
688
 
676
689
  ## Potential boost with `--no-gitignore`
677
690
 
678
- To increase performance in a large monorepo, check out [Potential boost with `--no-gitignore`][50].
691
+ To increase performance in a large monorepo, check out [Potential boost with `--no-gitignore`][51].
679
692
 
680
693
  ## Comparison & Migration
681
694
 
682
695
  This table is an ongoing comparison. Based on their docs (please report any mistakes):
683
696
 
684
- | Feature | **knip** | [depcheck][51] | [unimported][52] | [ts-unused-exports][53] | [ts-prune][54] |
697
+ | Feature | **knip** | [depcheck][52] | [unimported][53] | [ts-unused-exports][54] | [ts-prune][55] |
685
698
  | :---------------------- | :------: | :------------: | :--------------: | :---------------------: | :------------: |
686
699
  | Unused files | ✅ | - | ✅ | - | - |
687
700
  | Unused dependencies | ✅ | ✅ | ✅ | - | - |
@@ -717,7 +730,7 @@ The following commands are similar:
717
730
  unimported
718
731
  knip --production --dependencies --include files
719
732
 
720
- Also see [production mode][24].
733
+ Also see [production mode][25].
721
734
 
722
735
  ### ts-unused-exports
723
736
 
@@ -739,25 +752,25 @@ The following commands are similar:
739
752
 
740
753
  Many thanks to some of the early adopters of Knip:
741
754
 
742
- - [Block Protocol][55]
743
- - [DeepmergeTS][56]
744
- - [eslint-plugin-functional][57]
745
- - [freeCodeCamp.org][58]
746
- - [is-immutable-type][59]
747
- - [IsaacScript][60]
748
- - [Nuxt][61]
749
- - [Owncast][62]
750
- - [release-it][63]
751
- - [Template TypeScript Node Package][64]
752
- - [Tipi][65]
755
+ - [Block Protocol][56]
756
+ - [DeepmergeTS][57]
757
+ - [eslint-plugin-functional][58]
758
+ - [freeCodeCamp.org][59]
759
+ - [is-immutable-type][60]
760
+ - [IsaacScript][61]
761
+ - [Nuxt][62]
762
+ - [Owncast][63]
763
+ - [release-it][64]
764
+ - [Template TypeScript Node Package][65]
765
+ - [Tipi][66]
753
766
 
754
767
  ## Articles, etc.
755
768
 
756
- - Discord: hang out in [The Knip Barn][9]
757
- - Ask your questions in the [Knip knowledge base][66] (powered by OpenAI and [7-docs][67], experimental!)
758
- - Smashing Magazine: [Knip: An Automated Tool For Finding Unused Files, Exports, And Dependencies][68]
759
- - Effective TypeScript: [Recommendation Update: ✂️ Use knip to detect dead code and types][69]
760
- - Josh Goldberg: [Speeding Up Centered Part 4: Unused Code Bloat][70]
769
+ - Discord: hang out in [The Knip Barn][10]
770
+ - Ask your questions in the [Knip knowledge base][67] (powered by OpenAI and [7-docs][68], experimental!)
771
+ - Smashing Magazine: [Knip: An Automated Tool For Finding Unused Files, Exports, And Dependencies][69]
772
+ - Effective TypeScript: [Recommendation Update: ✂️ Use knip to detect dead code and types][70]
773
+ - Josh Goldberg: [Speeding Up Centered Part 4: Unused Code Bloat][71]
761
774
 
762
775
  ## Why "Knip"?
763
776
 
@@ -775,7 +788,7 @@ each file, and traversing all of this, why not collect the various issues in one
775
788
 
776
789
  Special thanks to the wonderful people who have contributed to this project:
777
790
 
778
- [![Contributors][72]][71]
791
+ [![Contributors][73]][72]
779
792
 
780
793
  [1]: #workspaces
781
794
  [2]: #plugins
@@ -783,72 +796,73 @@ Special thanks to the wonderful people who have contributed to this project:
783
796
  [4]: #reporters
784
797
  [5]: #custom-reporters
785
798
  [6]: #preprocessers
786
- [7]: https://labs.openai.com/s/xZQACaLepaKya0PRUPtIN5dC
787
- [8]: ./assets/cow-with-orange-scissors-van-gogh-style.webp
788
- [9]: https://discord.gg/ya5yktTq
789
- [10]: https://twitter.com/webprolific
790
- [11]: https://fosstodon.org/@webpro
791
- [12]: https://github.com/webpro/knip/issues
792
- [13]: #getting-started
793
- [14]: #installation
794
- [15]: #default-configuration
795
- [16]: #lets-go
796
- [17]: #configuration
797
- [18]: #entry-files
798
- [19]: #ignore-files-binaries-dependencies-and-workspaces
799
- [20]: #public-exports
800
- [21]: #ignore-exports-used-in-file
801
- [22]: #include-exports-in-entry-files
802
- [23]: #paths
803
- [24]: #production-mode
804
- [25]: #strict
805
- [26]: #ignore-internal-exports
806
- [27]: #plugins-1
807
- [28]: #output
808
- [29]: #screenshots
809
- [30]: #reading-the-report
810
- [31]: #rules--filters
811
- [32]: #fixing-issues
812
- [33]: #command-line-options
813
- [34]: #potential-boost-with---no-gitignore
814
- [35]: #comparison--migration
815
- [36]: #depcheck
816
- [37]: #unimported
817
- [38]: #ts-unused-exports
818
- [39]: #ts-prune
819
- [40]: #projects-using-knip
820
- [41]: #articles-etc
821
- [42]: #why-knip
822
- [43]: #really-another-unused-filedependencyexport-finder
823
- [44]: #contributors
824
- [45]: https://nx.dev/concepts/integrated-vs-package-based
825
- [46]: ./docs/writing-a-plugin.md
826
- [47]: ./docs/compilers.md
827
- [48]: ./docs/handling-issues.md
828
- [49]: ./docs/reporters-and-preprocessors.md
829
- [50]: ./docs/perf-boost-with-no-gitignore.md
830
- [51]: https://github.com/depcheck/depcheck
831
- [52]: https://github.com/smeijer/unimported
832
- [53]: https://github.com/pzavolinsky/ts-unused-exports
833
- [54]: https://github.com/nadeesha/ts-prune
834
- [55]: https://github.com/blockprotocol/blockprotocol
835
- [56]: https://github.com/RebeccaStevens/deepmerge-ts
836
- [57]: https://github.com/eslint-functional/eslint-plugin-functional
837
- [58]: https://github.com/freeCodeCamp/freeCodeCamp
838
- [59]: https://github.com/RebeccaStevens/is-immutable-type
839
- [60]: https://github.com/IsaacScript/isaacscript
840
- [61]: https://github.com/nuxt/nuxt
841
- [62]: https://github.com/owncast/owncast
842
- [63]: https://github.com/release-it/release-it
843
- [64]: https://github.com/JoshuaKGoldberg/template-typescript-node-package
844
- [65]: https://github.com/meienberger/runtipi
845
- [66]: https://knip.deno.dev
846
- [67]: https://github.com/7-docs/7-docs
847
- [68]: https://www.smashingmagazine.com/2023/08/knip-automated-tool-find-unused-files-exports-dependencies/
848
- [69]: https://effectivetypescript.com/2023/07/29/knip/
849
- [70]: https://www.joshuakgoldberg.com/blog/speeding-up-centered-part-4-unused-code-bloat/
850
- [71]: https://github.com/webpro/knip/graphs/contributors
851
- [72]: https://contrib.rocks/image?repo=webpro/knip
799
+ [7]: #jsdoc-tags
800
+ [8]: https://labs.openai.com/s/xZQACaLepaKya0PRUPtIN5dC
801
+ [9]: ./assets/cow-with-orange-scissors-van-gogh-style.webp
802
+ [10]: https://discord.gg/ya5yktTq
803
+ [11]: https://twitter.com/webprolific
804
+ [12]: https://fosstodon.org/@webpro
805
+ [13]: https://github.com/webpro/knip/issues
806
+ [14]: #getting-started
807
+ [15]: #installation
808
+ [16]: #default-configuration
809
+ [17]: #lets-go
810
+ [18]: #configuration
811
+ [19]: #entry-files
812
+ [20]: #ignore-files-binaries-dependencies-and-workspaces
813
+ [21]: #public-exports
814
+ [22]: #ignore-exports-used-in-file
815
+ [23]: #include-exports-in-entry-files
816
+ [24]: #paths
817
+ [25]: #production-mode
818
+ [26]: #strict
819
+ [27]: #ignore-internal-exports
820
+ [28]: #plugins-1
821
+ [29]: #output
822
+ [30]: #screenshots
823
+ [31]: #reading-the-report
824
+ [32]: #rules--filters
825
+ [33]: #fixing-issues
826
+ [34]: #command-line-options
827
+ [35]: #potential-boost-with---no-gitignore
828
+ [36]: #comparison--migration
829
+ [37]: #depcheck
830
+ [38]: #unimported
831
+ [39]: #ts-unused-exports
832
+ [40]: #ts-prune
833
+ [41]: #projects-using-knip
834
+ [42]: #articles-etc
835
+ [43]: #why-knip
836
+ [44]: #really-another-unused-filedependencyexport-finder
837
+ [45]: #contributors
838
+ [46]: https://nx.dev/concepts/integrated-vs-package-based
839
+ [47]: ./docs/writing-a-plugin.md
840
+ [48]: ./docs/compilers.md
841
+ [49]: ./docs/handling-issues.md
842
+ [50]: ./docs/reporters-and-preprocessors.md
843
+ [51]: ./docs/perf-boost-with-no-gitignore.md
844
+ [52]: https://github.com/depcheck/depcheck
845
+ [53]: https://github.com/smeijer/unimported
846
+ [54]: https://github.com/pzavolinsky/ts-unused-exports
847
+ [55]: https://github.com/nadeesha/ts-prune
848
+ [56]: https://github.com/blockprotocol/blockprotocol
849
+ [57]: https://github.com/RebeccaStevens/deepmerge-ts
850
+ [58]: https://github.com/eslint-functional/eslint-plugin-functional
851
+ [59]: https://github.com/freeCodeCamp/freeCodeCamp
852
+ [60]: https://github.com/RebeccaStevens/is-immutable-type
853
+ [61]: https://github.com/IsaacScript/isaacscript
854
+ [62]: https://github.com/nuxt/nuxt
855
+ [63]: https://github.com/owncast/owncast
856
+ [64]: https://github.com/release-it/release-it
857
+ [65]: https://github.com/JoshuaKGoldberg/template-typescript-node-package
858
+ [66]: https://github.com/meienberger/runtipi
859
+ [67]: https://knip.deno.dev
860
+ [68]: https://github.com/7-docs/7-docs
861
+ [69]: https://www.smashingmagazine.com/2023/08/knip-automated-tool-find-unused-files-exports-dependencies/
862
+ [70]: https://effectivetypescript.com/2023/07/29/knip/
863
+ [71]: https://www.joshuakgoldberg.com/blog/speeding-up-centered-part-4-unused-code-bloat/
864
+ [72]: https://github.com/webpro/knip/graphs/contributors
865
+ [73]: https://contrib.rocks/image?repo=webpro/knip
852
866
  [plugin-ava]: ./src/plugins/ava
853
867
  [plugin-babel]: ./src/plugins/babel
854
868
  [plugin-capacitor]: ./src/plugins/capacitor
@@ -32,6 +32,7 @@ export declare class DependencyDeputy {
32
32
  scripts: string[];
33
33
  dependencies: string[];
34
34
  peerDependencies: string[];
35
+ optionalPeerDependencies: string[];
35
36
  optionalDependencies: string[];
36
37
  devDependencies: string[];
37
38
  allDependencies: string[];
@@ -46,12 +47,15 @@ export declare class DependencyDeputy {
46
47
  addReferencedBinary(workspaceName: string, binaryName: string): void;
47
48
  addPeerDependencies(workspaceName: string, peerDependencies: Map<string, Set<string>>): void;
48
49
  getPeerDependenciesOf(workspaceName: string, dependency: string): string[];
50
+ getPeerDependencies(workspaceName: string): string[];
51
+ getOptionalPeerDependencies(workspaceName: string): string[];
49
52
  maybeAddReferencedExternalDependency(workspace: Workspace, packageName: string): boolean;
50
53
  maybeAddReferencedBinary(workspace: Workspace, binaryName: string): boolean;
51
54
  private isInDependencies;
52
55
  settleDependencyIssues(): {
53
56
  dependencyIssues: Issue[];
54
57
  devDependencyIssues: Issue[];
58
+ optionalPeerDependencyIssues: Issue[];
55
59
  };
56
60
  getConfigurationHints(): {
57
61
  configurationHints: ConfigurationHints;
@@ -22,6 +22,11 @@ export class DependencyDeputy {
22
22
  const dependencies = Object.keys(manifest.dependencies ?? {});
23
23
  const peerDependencies = Object.keys(manifest.peerDependencies ?? {});
24
24
  const optionalDependencies = Object.keys(manifest.optionalDependencies ?? {});
25
+ const optionalPeerDependencies = manifest.peerDependenciesMeta
26
+ ? peerDependencies.filter(peerDependency => manifest.peerDependenciesMeta &&
27
+ peerDependency in manifest.peerDependenciesMeta &&
28
+ manifest.peerDependenciesMeta[peerDependency].optional)
29
+ : [];
25
30
  const devDependencies = Object.keys(manifest.devDependencies ?? {});
26
31
  const allDependencies = [...dependencies, ...devDependencies, ...peerDependencies, ...optionalDependencies];
27
32
  this._manifests.set(name, {
@@ -32,6 +37,7 @@ export class DependencyDeputy {
32
37
  scripts,
33
38
  dependencies,
34
39
  peerDependencies,
40
+ optionalPeerDependencies,
35
41
  optionalDependencies,
36
42
  devDependencies,
37
43
  allDependencies,
@@ -79,6 +85,18 @@ export class DependencyDeputy {
79
85
  getPeerDependenciesOf(workspaceName, dependency) {
80
86
  return Array.from(this.peerDependencies.get(workspaceName)?.get(dependency) ?? []);
81
87
  }
88
+ getPeerDependencies(workspaceName) {
89
+ const manifest = this._manifests.get(workspaceName);
90
+ if (!manifest)
91
+ return [];
92
+ return manifest.peerDependencies;
93
+ }
94
+ getOptionalPeerDependencies(workspaceName) {
95
+ const manifest = this._manifests.get(workspaceName);
96
+ if (!manifest)
97
+ return [];
98
+ return manifest.optionalPeerDependencies;
99
+ }
82
100
  maybeAddReferencedExternalDependency(workspace, packageName) {
83
101
  if (isBuiltin(packageName))
84
102
  return true;
@@ -133,6 +151,7 @@ export class DependencyDeputy {
133
151
  settleDependencyIssues() {
134
152
  const dependencyIssues = [];
135
153
  const devDependencyIssues = [];
154
+ const optionalPeerDependencyIssues = [];
136
155
  for (const [workspaceName, { manifestPath, ignoreDependencies, ignoreBinaries }] of this._manifests.entries()) {
137
156
  const referencedDependencies = this.referencedDependencies.get(workspaceName);
138
157
  const installedBinaries = this.getInstalledBinaries(workspaceName);
@@ -175,6 +194,7 @@ export class DependencyDeputy {
175
194
  const isNotReferencedDependency = (dependency) => !isReferencedDependency(dependency);
176
195
  const pd = this.getProductionDependencies(workspaceName);
177
196
  const dd = this.getDevDependencies(workspaceName);
197
+ const od = this.getOptionalPeerDependencies(workspaceName);
178
198
  pd.filter(isNotIgnoredDependency)
179
199
  .filter(isNotIgnoredBinary)
180
200
  .filter(isNotReferencedDependency)
@@ -183,8 +203,12 @@ export class DependencyDeputy {
183
203
  .filter(isNotIgnoredBinary)
184
204
  .filter(isNotReferencedDependency)
185
205
  .forEach(symbol => devDependencyIssues.push({ type: 'devDependencies', filePath: manifestPath, symbol }));
206
+ od.filter(isNotIgnoredDependency)
207
+ .filter(isNotIgnoredBinary)
208
+ .filter(p => isReferencedDependency(p))
209
+ .forEach(symbol => optionalPeerDependencyIssues.push({ type: 'optionalPeerDependencies', filePath: manifestPath, symbol }));
186
210
  }
187
- return { dependencyIssues, devDependencyIssues };
211
+ return { dependencyIssues, devDependencyIssues, optionalPeerDependencyIssues };
188
212
  }
189
213
  getConfigurationHints() {
190
214
  const configurationHints = new Set();
@@ -199,12 +223,13 @@ export class DependencyDeputy {
199
223
  const dependencies = this.isStrict
200
224
  ? this.getProductionDependencies(workspaceName)
201
225
  : [...this.getProductionDependencies(workspaceName), ...this.getDevDependencies(workspaceName)];
226
+ const peerDependencies = this.getPeerDependencies(workspaceName);
202
227
  const isReferencedDep = (name) => referencedDependencies?.has(name) && dependencies.includes(name);
203
228
  const isReferencedBin = (name) => referencedBinaries?.has(name) && installedBinaries?.has(name);
204
229
  ignoreDependencies
205
230
  .filter(packageName => IGNORED_DEPENDENCIES.includes(packageName) ||
206
231
  (workspaceName !== ROOT_WORKSPACE_NAME && this.ignoreDependencies.includes(packageName)) ||
207
- isReferencedDep(packageName))
232
+ (!peerDependencies.includes(packageName) && isReferencedDep(packageName)))
208
233
  .forEach(identifier => configurationHints.add({ workspaceName, identifier, type: 'ignoreDependencies' }));
209
234
  ignoreBinaries
210
235
  .filter(binaryName => IGNORED_GLOBAL_BINARIES.includes(binaryName) ||
@@ -216,11 +241,13 @@ export class DependencyDeputy {
216
241
  const dependencies = this.isStrict
217
242
  ? this.getProductionDependencies(ROOT_WORKSPACE_NAME)
218
243
  : [...this.getProductionDependencies(ROOT_WORKSPACE_NAME), ...this.getDevDependencies(ROOT_WORKSPACE_NAME)];
244
+ const peerDependencies = this.getPeerDependencies(ROOT_WORKSPACE_NAME);
219
245
  Object.keys(rootIgnoreBinaries)
220
246
  .filter(key => IGNORED_GLOBAL_BINARIES.includes(key) || (rootIgnoreBinaries[key] !== 0 && installedBinaries?.has(key)))
221
247
  .forEach(identifier => configurationHints.add({ workspaceName: ROOT_WORKSPACE_NAME, identifier, type: 'ignoreBinaries' }));
222
248
  Object.keys(rootIgnoreDependencies)
223
- .filter(key => IGNORED_DEPENDENCIES.includes(key) || (rootIgnoreDependencies[key] !== 0 && dependencies.includes(key)))
249
+ .filter(key => IGNORED_DEPENDENCIES.includes(key) ||
250
+ (rootIgnoreDependencies[key] !== 0 && !peerDependencies.includes(key) && dependencies.includes(key)))
224
251
  .forEach(identifier => configurationHints.add({ workspaceName: ROOT_WORKSPACE_NAME, identifier, type: 'ignoreDependencies' }));
225
252
  return { configurationHints };
226
253
  }
@@ -60,7 +60,5 @@ export declare class ProjectPrincipal {
60
60
  };
61
61
  findUnusedMembers(filePath: string, members: ExportItemMember[]): string[];
62
62
  private findReferences;
63
- isPublicExport(exportedItem: ExportItem): boolean;
64
- getJSDocTags(exportedItem: ExportItem): string[];
65
63
  }
66
64
  export {};
@@ -2,7 +2,7 @@ import { isGitIgnoredSync } from 'globby';
2
2
  import ts from 'typescript';
3
3
  import { DEFAULT_EXTENSIONS } from './constants.js';
4
4
  import { IGNORED_FILE_EXTENSIONS } from './constants.js';
5
- import { isInModuleBlock } from './typescript/ast-helpers.js';
5
+ import { getJSDocTags, isInModuleBlock } from './typescript/ast-helpers.js';
6
6
  import { createHosts } from './typescript/createHosts.js';
7
7
  import { getImportsAndExports } from './typescript/getImportsAndExports.js';
8
8
  import { SourceFileManager } from './typescript/SourceFileManager.js';
@@ -178,7 +178,7 @@ export class ProjectPrincipal {
178
178
  findUnusedMembers(filePath, members) {
179
179
  return members
180
180
  .filter(member => {
181
- if (this.isPublicExport(member))
181
+ if (getJSDocTags(member.node).includes('@public'))
182
182
  return false;
183
183
  const referencedSymbols = this.findReferences(filePath, member.pos);
184
184
  const files = referencedSymbols
@@ -194,11 +194,4 @@ export class ProjectPrincipal {
194
194
  findReferences(filePath, pos) {
195
195
  return this.backend.lsFindReferences(filePath, pos) ?? [];
196
196
  }
197
- isPublicExport(exportedItem) {
198
- const tags = this.getJSDocTags(exportedItem);
199
- return tags.includes('@public');
200
- }
201
- getJSDocTags(exportedItem) {
202
- return ts.getJSDocTags(exportedItem.node).map(node => node.getText().match(/@\S+/)[0]);
203
- }
204
197
  }
@@ -15,7 +15,7 @@ export const getBinariesFromScript = (script, { cwd, manifest, knownGlobalsOnly
15
15
  if (commandExpansions.length > 0) {
16
16
  return commandExpansions.flatMap(expansion => getBinariesFromNodes(expansion.commandAST.commands)) ?? [];
17
17
  }
18
- if (!binary || binary === '.' || binary === 'source')
18
+ if (!binary || binary === '.' || binary === 'source' || binary === '[')
19
19
  return [];
20
20
  if (binary.startsWith('-') || binary.startsWith('"') || binary.startsWith('..'))
21
21
  return [];
@@ -34,13 +34,15 @@ export const getBinariesFromScript = (script, { cwd, manifest, knownGlobalsOnly
34
34
  case 'LogicalExpression':
35
35
  return getBinariesFromNodes([node.left, node.right]);
36
36
  case 'If':
37
- return getBinariesFromNodes([...node.clause.commands, ...node.then.commands, ...(node.else?.commands ?? [])]);
37
+ return getBinariesFromNodes([node.clause, node.then, ...(node.else ? [node.else] : [])]);
38
38
  case 'For':
39
39
  return getBinariesFromNodes(node.do.commands);
40
40
  case 'CompoundList':
41
41
  return getBinariesFromNodes(node.commands);
42
42
  case 'Pipeline':
43
43
  return getBinariesFromNodes(node.commands);
44
+ case 'Function':
45
+ return getBinariesFromNodes(node.body.commands);
44
46
  default:
45
47
  return [];
46
48
  }
@@ -53,7 +53,7 @@ const commands = [
53
53
  ];
54
54
  export const resolve = (_binary, args, { manifest }) => {
55
55
  const scripts = manifest.scripts ? Object.keys(manifest.scripts) : [];
56
- const parsed = parseArgs(args, {});
56
+ const parsed = parseArgs(args, { alias: { recursive: 'r' }, boolean: ['recursive'] });
57
57
  const [command, binary] = parsed._;
58
58
  if (scripts.includes(command) || commands.includes(command))
59
59
  return [];
package/dist/constants.js CHANGED
@@ -60,6 +60,7 @@ export const ISSUE_TYPES = [
60
60
  'files',
61
61
  'dependencies',
62
62
  'devDependencies',
63
+ 'optionalPeerDependencies',
63
64
  'unlisted',
64
65
  'binaries',
65
66
  'unresolved',
@@ -75,6 +76,7 @@ export const ISSUE_TYPE_TITLE = {
75
76
  files: 'Unused files',
76
77
  dependencies: 'Unused dependencies',
77
78
  devDependencies: 'Unused devDependencies',
79
+ optionalPeerDependencies: 'Referenced optional peerDependencies',
78
80
  unlisted: 'Unlisted dependencies',
79
81
  binaries: 'Unlisted binaries',
80
82
  unresolved: 'Unresolved imports',
package/dist/index.js CHANGED
@@ -208,8 +208,10 @@ export const main = async (unresolvedConfiguration) => {
208
208
  }
209
209
  }
210
210
  duplicate.forEach(symbols => {
211
- const symbol = symbols.join('|');
212
- collector.addIssue({ type: 'duplicates', filePath, symbol, symbols });
211
+ if (symbols.length > 1) {
212
+ const symbol = symbols.join('|');
213
+ collector.addIssue({ type: 'duplicates', filePath, symbol, symbols });
214
+ }
213
215
  });
214
216
  external.forEach(specifier => {
215
217
  const packageName = getPackageNameFromModuleSpecifier(specifier);
@@ -276,10 +278,9 @@ export const main = async (unresolvedConfiguration) => {
276
278
  continue;
277
279
  const importingModule = importedSymbols.get(filePath);
278
280
  for (const [symbol, exportedItem] of exportItems.entries()) {
279
- const jsDocTags = principal.getJSDocTags(exportedItem);
280
- if (jsDocTags.includes('@public') || jsDocTags.includes('@beta'))
281
+ if (exportedItem.jsDocTags.includes('@public') || exportedItem.jsDocTags.includes('@beta'))
281
282
  continue;
282
- if (isIgnoreInternal && jsDocTags.includes('@internal'))
283
+ if (isIgnoreInternal && exportedItem.jsDocTags.includes('@internal'))
283
284
  continue;
284
285
  if (importingModule && isSymbolImported(symbol, importingModule)) {
285
286
  if (importingModule.isReExport && isExportedInEntryFile(importingModule))
@@ -317,11 +318,12 @@ export const main = async (unresolvedConfiguration) => {
317
318
  }
318
319
  }
319
320
  if (isReportDependencies) {
320
- const { dependencyIssues, devDependencyIssues } = deputy.settleDependencyIssues();
321
+ const { dependencyIssues, devDependencyIssues, optionalPeerDependencyIssues } = deputy.settleDependencyIssues();
321
322
  const { configurationHints } = deputy.getConfigurationHints();
322
323
  dependencyIssues.forEach(issue => collector.addIssue(issue));
323
324
  if (!isProduction)
324
325
  devDependencyIssues.forEach(issue => collector.addIssue(issue));
326
+ optionalPeerDependencyIssues.forEach(issue => collector.addIssue(issue));
325
327
  configurationHints.forEach(hint => collector.addConfigurationHint(hint));
326
328
  }
327
329
  const unusedIgnoredWorkspaces = chief.getUnusedIgnoredWorkspaces();
@@ -5,7 +5,7 @@ import { _resolve } from '../../util/require.js';
5
5
  import { fallback } from './fallback.js';
6
6
  const getDependencies = (config) => {
7
7
  const extendsSpecifiers = config.extends ? [config.extends].flat().map(resolveExtendSpecifier) : [];
8
- if (extendsSpecifiers.includes('eslint-plugin-prettier'))
8
+ if (extendsSpecifiers.some(specifier => specifier?.startsWith('eslint-plugin-prettier')))
9
9
  extendsSpecifiers.push('eslint-config-prettier');
10
10
  const plugins = config.plugins ? config.plugins.map(resolvePluginSpecifier) : [];
11
11
  const parser = config.parser;
@@ -1,18 +1,13 @@
1
1
  import { readFileSync } from 'fs';
2
2
  import { _getDependenciesFromScripts } from '../../binaries/index.js';
3
+ import { getGitHookPaths } from '../../util/git.js';
3
4
  import { timerify } from '../../util/Performance.js';
4
5
  import { hasDependency } from '../../util/plugin.js';
5
- import { getGitHooksPath } from './helpers.js';
6
6
  export const NAME = 'husky';
7
7
  export const ENABLERS = ['husky'];
8
8
  export const isEnabled = ({ dependencies }) => hasDependency(dependencies, ENABLERS);
9
- const gitHooksPath = getGitHooksPath();
10
- export const CONFIG_FILE_PATTERNS = [
11
- `${gitHooksPath}/prepare-commit-msg`,
12
- `${gitHooksPath}/commit-msg`,
13
- `${gitHooksPath}/pre-{applypatch,commit,merge-commit,push,rebase,receive}`,
14
- `${gitHooksPath}/post-{checkout,commit,merge,rewrite}`,
15
- ];
9
+ const gitHookPaths = getGitHookPaths('.husky');
10
+ export const CONFIG_FILE_PATTERNS = [...gitHookPaths];
16
11
  const findHuskyDependencies = async (configFilePath, { cwd, manifest }) => {
17
12
  const script = readFileSync(configFilePath);
18
13
  return _getDependenciesFromScripts(String(script), {
@@ -1,20 +1,28 @@
1
+ import { readFileSync } from 'fs';
1
2
  import { _getDependenciesFromScripts } from '../../binaries/index.js';
3
+ import { fromBinary } from '../../binaries/util.js';
4
+ import { getGitHookPaths } from '../../util/git.js';
2
5
  import { getValuesByKeyDeep } from '../../util/object.js';
3
6
  import { timerify } from '../../util/Performance.js';
4
7
  import { hasDependency, load } from '../../util/plugin.js';
5
8
  export const NAME = 'Lefthook';
6
- export const ENABLERS = ['lefthook', '@arkweid/lefthook'];
9
+ export const ENABLERS = ['lefthook', '@arkweid/lefthook', '@evilmartians/lefthook'];
7
10
  export const isEnabled = ({ dependencies }) => hasDependency(dependencies, ENABLERS);
8
- export const CONFIG_FILE_PATTERNS = ['lefthook.yml'];
11
+ const gitHookPaths = getGitHookPaths();
12
+ export const CONFIG_FILE_PATTERNS = ['lefthook.yml', ...gitHookPaths];
9
13
  const findLefthookDependencies = async (configFilePath, { cwd, manifest }) => {
10
- const config = await load(configFilePath);
11
- if (!config)
12
- return [];
13
- const scripts = getValuesByKeyDeep(config, 'run').filter((value) => typeof value === 'string');
14
- return _getDependenciesFromScripts(scripts, {
15
- cwd,
16
- manifest,
17
- knownGlobalsOnly: true,
18
- });
14
+ const dependencies = manifest.devDependencies ? Object.keys(manifest.devDependencies) : [];
15
+ if (configFilePath.endsWith('.yml')) {
16
+ const config = await load(configFilePath);
17
+ if (!config)
18
+ return [];
19
+ const scripts = getValuesByKeyDeep(config, 'run').filter((value) => typeof value === 'string');
20
+ const lefthook = process.env.CI ? ENABLERS.filter(dependency => dependencies.includes(dependency)) : [];
21
+ return [...lefthook, ..._getDependenciesFromScripts(scripts, { cwd, manifest, knownGlobalsOnly: true })];
22
+ }
23
+ const script = readFileSync(configFilePath, 'utf8');
24
+ const scriptDependencies = _getDependenciesFromScripts([script], { cwd, manifest, knownGlobalsOnly: false });
25
+ const matches = scriptDependencies.find(dep => dependencies.includes(fromBinary(dep)));
26
+ return matches ? [matches] : [];
19
27
  };
20
28
  export const findDependencies = timerify(findLefthookDependencies);
@@ -18,6 +18,12 @@ const findReleaseItDependencies = async (configFilePath, { cwd, manifest }) => {
18
18
  return [];
19
19
  const plugins = config.plugins ? Object.keys(config.plugins) : [];
20
20
  const scripts = config.hooks ? Object.values(config.hooks).flat() : [];
21
+ if (typeof config.github?.releaseNotes === 'string') {
22
+ scripts.push(config.github.releaseNotes);
23
+ }
24
+ if (typeof config.gitlab?.releaseNotes === 'string') {
25
+ scripts.push(config.gitlab.releaseNotes);
26
+ }
21
27
  const dependencies = _getDependenciesFromScripts(scripts, { cwd, manifest });
22
28
  return [...plugins, ...dependencies];
23
29
  };
@@ -1,4 +1,10 @@
1
1
  export type ReleaseItConfig = {
2
+ github?: {
3
+ releaseNotes?: string | (() => string) | null;
4
+ };
5
+ gitlab?: {
6
+ releaseNotes?: string | (() => string) | null;
7
+ };
2
8
  plugins?: Record<string, unknown>;
3
9
  hooks?: Record<string, string | string[]>;
4
10
  };
@@ -22,6 +22,7 @@ export default async ({ report, issues, options }) => {
22
22
  ...(report.files && { files: false }),
23
23
  ...(report.dependencies && { dependencies: [] }),
24
24
  ...(report.devDependencies && { devDependencies: [] }),
25
+ ...(report.optionalPeerDependencies && { optionalPeerDependencies: [] }),
25
26
  ...(report.unlisted && { unlisted: [] }),
26
27
  ...(report.unresolved && { unresolved: [] }),
27
28
  ...((report.exports || report.nsExports) && { exports: [] }),
@@ -7,6 +7,7 @@ export type ExportItem = {
7
7
  pos: number;
8
8
  type: SymbolType;
9
9
  members?: ExportItemMember[];
10
+ jsDocTags?: string[];
10
11
  };
11
12
  export type ExportItemMember = {
12
13
  node: ts.Node;
@@ -14,6 +15,6 @@ export type ExportItemMember = {
14
15
  pos: number;
15
16
  type: SymbolType;
16
17
  };
17
- export type ExportItems = Map<string, ExportItem>;
18
+ export type ExportItems = Map<string, Required<ExportItem>>;
18
19
  export type Exports = Map<FilePath, ExportItems>;
19
20
  export {};
@@ -22,6 +22,7 @@ export type Issues = {
22
22
  files: IssueSet;
23
23
  dependencies: IssueRecords;
24
24
  devDependencies: IssueRecords;
25
+ optionalPeerDependencies: IssueRecords;
25
26
  unlisted: IssueRecords;
26
27
  binaries: IssueRecords;
27
28
  unresolved: IssueRecords;
@@ -4,6 +4,7 @@ type WorkspaceManifest = {
4
4
  scripts: string[];
5
5
  dependencies: string[];
6
6
  peerDependencies: string[];
7
+ optionalPeerDependencies: string[];
7
8
  optionalDependencies: string[];
8
9
  devDependencies: string[];
9
10
  allDependencies: string[];
@@ -23,4 +23,5 @@ export declare function findAncestor<T>(node: ts.Node | undefined, callback: (el
23
23
  export declare function findDescendants<T>(node: ts.Node | undefined, callback: (element: ts.Node) => boolean | 'STOP'): T[];
24
24
  export declare const isDeclarationFileExtension: (extension: string) => boolean;
25
25
  export declare const isInModuleBlock: (node: ts.Node) => boolean;
26
+ export declare const getJSDocTags: (node: ts.Node) => string[];
26
27
  export {};
@@ -98,3 +98,4 @@ export const isInModuleBlock = (node) => {
98
98
  }
99
99
  return false;
100
100
  };
101
+ export const getJSDocTags = (node) => ts.getJSDocTags(node).map(node => node.getText().match(/@\S+/)[0]);
@@ -3,7 +3,7 @@ import ts from 'typescript';
3
3
  import { getOrSet } from '../util/map.js';
4
4
  import { isMaybePackageName } from '../util/modules.js';
5
5
  import { isInNodeModules } from '../util/path.js';
6
- import { isDeclarationFileExtension, isAccessExpression, getAccessExpressionName } from './ast-helpers.js';
6
+ import { isDeclarationFileExtension, isAccessExpression, getAccessExpressionName, getJSDocTags, } from './ast-helpers.js';
7
7
  import getExportVisitors from './visitors/exports/index.js';
8
8
  import { getJSXImplicitImportBase } from './visitors/helpers.js';
9
9
  import getImportVisitors from './visitors/imports/index.js';
@@ -86,20 +86,25 @@ export const getImportsAndExports = (sourceFile, options) => {
86
86
  }
87
87
  }
88
88
  };
89
- const addExport = ({ node, identifier, type, pos, members }) => {
89
+ const addExport = ({ node, identifier, type, pos, members = [] }) => {
90
90
  if (options.skipExports)
91
91
  return;
92
+ const jsDocTags = getJSDocTags(node);
92
93
  if (exports.has(identifier)) {
93
94
  const item = exports.get(identifier);
94
- exports.set(identifier, { ...item, node, type, pos, members });
95
+ const crew = [...item.members, ...members];
96
+ const tags = [...item.jsDocTags, ...jsDocTags];
97
+ exports.set(identifier, { ...item, node, type, pos, members: crew, jsDocTags: tags });
95
98
  }
96
99
  else {
97
- exports.set(identifier, { node, type, pos, members });
100
+ exports.set(identifier, { node, type, pos, members, jsDocTags });
101
+ }
102
+ if (!jsDocTags.includes('@alias')) {
103
+ if (ts.isExportAssignment(node))
104
+ maybeAddAliasedExport(node.expression, 'default');
105
+ if (ts.isVariableDeclaration(node))
106
+ maybeAddAliasedExport(node.initializer, identifier);
98
107
  }
99
- if (ts.isExportAssignment(node))
100
- maybeAddAliasedExport(node.expression, 'default');
101
- if (ts.isVariableDeclaration(node))
102
- maybeAddAliasedExport(node.initializer, identifier);
103
108
  };
104
109
  const maybeAddAliasedExport = (node, alias) => {
105
110
  const identifier = node?.getText();
@@ -1,4 +1,4 @@
1
- export declare const helpText = "\u2702\uFE0F Find unused files, dependencies and exports in your JavaScript and TypeScript projects\n\nUsage: knip [options]\n\nOptions:\n -c, --config [file] Configuration file path (default: [.]knip.json[c], knip.js, knip.ts or package.json#knip)\n -t, --tsConfig [file] TypeScript configuration path (default: tsconfig.json)\n --production Analyze only production source files (e.g. no tests, devDependencies, exported types)\n --strict Consider only direct dependencies of workspace (not devDependencies, not other workspaces)\n --ignore-internal Ignore exports with tag @internal (JSDoc/TSDoc)\n --workspace [dir] Analyze a single workspace (default: analyze all configured workspaces)\n --no-gitignore Don't use .gitignore\n --include Report only provided issue type(s), can be comma-separated or repeated (1)\n --exclude Exclude provided issue type(s) from report, can be comma-separated or repeated (1)\n --dependencies Shortcut for --include dependencies,unlisted,unresolved\n --exports Shortcut for --include exports,nsExports,classMembers,types,nsTypes,enumMembers,duplicates\n --include-entry-exports Include entry files when reporting unused exports\n -n, --no-progress Don't show dynamic progress updates (automatically enabled in CI environments)\n --preprocessor Preprocess the results before providing it to the reporter(s), can be repeated\n --reporter Select reporter: symbols, compact, codeowners, json, can be repeated (default: symbols)\n --reporter-options Pass extra options to the reporter (as JSON string, see example)\n --no-config-hints Suppress configuration hints\n --no-exit-code Always exit with code zero (0)\n --max-issues Maximum number of issues before non-zero exit code (default: 0)\n -d, --debug Show debug output\n --debug-file-filter Filter for files in debug output (regex as string)\n --performance Measure count and running time of expensive functions and display stats table\n -h, --help Print this help text\n -V, --version Print version\n\n(1) Issue types: files, dependencies, unlisted, unresolved, exports, nsExports, classMembers, types, nsTypes, enumMembers, duplicates\n\nExamples:\n\n$ knip\n$ knip --production\n$ knip --workspace packages/client --include files,dependencies\n$ knip -c ./config/knip.json --reporter compact\n$ knip --reporter codeowners --reporter-options '{\"path\":\".github/CODEOWNERS\"}'\n$ knip --debug --debug-file-filter '(specific|particular)-module'\n\nMore documentation and bug reports: https://github.com/webpro/knip";
1
+ export declare const helpText = "\u2702\uFE0F Find unused files, dependencies and exports in your JavaScript and TypeScript projects\n\nUsage: knip [options]\n\nOptions:\n -c, --config [file] Configuration file path (default: [.]knip.json[c], knip.js, knip.ts or package.json#knip)\n -t, --tsConfig [file] TypeScript configuration path (default: tsconfig.json)\n --production Analyze only production source files (e.g. no tests, devDependencies, exported types)\n --strict Consider only direct dependencies of workspace (not devDependencies, not other workspaces)\n --ignore-internal Ignore exports with tag @internal (JSDoc/TSDoc)\n -W, --workspace [dir] Analyze a single workspace (default: analyze all configured workspaces)\n --no-gitignore Don't use .gitignore\n --include Report only provided issue type(s), can be comma-separated or repeated (1)\n --exclude Exclude provided issue type(s) from report, can be comma-separated or repeated (1)\n --dependencies Shortcut for --include dependencies,unlisted,unresolved\n --exports Shortcut for --include exports,nsExports,classMembers,types,nsTypes,enumMembers,duplicates\n --include-entry-exports Include entry files when reporting unused exports\n -n, --no-progress Don't show dynamic progress updates (automatically enabled in CI environments)\n --preprocessor Preprocess the results before providing it to the reporter(s), can be repeated\n --reporter Select reporter: symbols, compact, codeowners, json, can be repeated (default: symbols)\n --reporter-options Pass extra options to the reporter (as JSON string, see example)\n --no-config-hints Suppress configuration hints\n --no-exit-code Always exit with code zero (0)\n --max-issues Maximum number of issues before non-zero exit code (default: 0)\n -d, --debug Show debug output\n --debug-file-filter Filter for files in debug output (regex as string)\n --performance Measure count and running time of expensive functions and display stats table\n -h, --help Print this help text\n -V, --version Print version\n\n(1) Issue types: files, dependencies, unlisted, unresolved, exports, nsExports, classMembers, types, nsTypes, enumMembers, duplicates\n\nExamples:\n\n$ knip\n$ knip --production\n$ knip --workspace packages/client --include files,dependencies\n$ knip -c ./config/knip.json --reporter compact\n$ knip --reporter codeowners --reporter-options '{\"path\":\".github/CODEOWNERS\"}'\n$ knip --debug --debug-file-filter '(specific|particular)-module'\n\nMore documentation and bug reports: https://github.com/webpro/knip";
2
2
  declare const _default: {
3
3
  config: string | undefined;
4
4
  debug: boolean | undefined;
@@ -9,7 +9,7 @@ Options:
9
9
  --production Analyze only production source files (e.g. no tests, devDependencies, exported types)
10
10
  --strict Consider only direct dependencies of workspace (not devDependencies, not other workspaces)
11
11
  --ignore-internal Ignore exports with tag @internal (JSDoc/TSDoc)
12
- --workspace [dir] Analyze a single workspace (default: analyze all configured workspaces)
12
+ -W, --workspace [dir] Analyze a single workspace (default: analyze all configured workspaces)
13
13
  --no-gitignore Don't use .gitignore
14
14
  --include Report only provided issue type(s), can be comma-separated or repeated (1)
15
15
  --exclude Exclude provided issue type(s) from report, can be comma-separated or repeated (1)
@@ -68,7 +68,7 @@ try {
68
68
  strict: { type: 'boolean' },
69
69
  tsConfig: { type: 'string', short: 't' },
70
70
  version: { type: 'boolean', short: 'V' },
71
- workspace: { type: 'string' },
71
+ workspace: { type: 'string', short: 'W' },
72
72
  },
73
73
  });
74
74
  }
@@ -1,7 +1,14 @@
1
1
  import { ISSUE_TYPES } from '../constants.js';
2
2
  export const getIncludedIssueTypes = (cliArgs, { include = [], exclude = [], isProduction = false } = {}) => {
3
3
  if (cliArgs.dependencies) {
4
- cliArgs.include = [...cliArgs.include, 'dependencies', 'unlisted', 'binaries', 'unresolved'];
4
+ cliArgs.include = [
5
+ ...cliArgs.include,
6
+ 'dependencies',
7
+ 'optionalPeerDependencies',
8
+ 'unlisted',
9
+ 'binaries',
10
+ 'unresolved',
11
+ ];
5
12
  }
6
13
  if (cliArgs.exports) {
7
14
  const exports = ['exports', 'nsExports', 'classMembers', 'types', 'nsTypes', 'enumMembers', 'duplicates'];
@@ -21,9 +28,9 @@ export const getIncludedIssueTypes = (cliArgs, { include = [], exclude = [], isP
21
28
  }
22
29
  else {
23
30
  if (_include.includes('dependencies'))
24
- _include.push('devDependencies');
31
+ _include.push('devDependencies', 'optionalPeerDependencies');
25
32
  if (_exclude.includes('dependencies'))
26
- _exclude.push('devDependencies');
33
+ _exclude.push('devDependencies', 'optionalPeerDependencies');
27
34
  }
28
35
  const included = (_include.length > 0 ? _include : ISSUE_TYPES).filter(group => !_exclude.includes(group));
29
36
  return ISSUE_TYPES.reduce((types, group) => ((types[group] = included.includes(group)), types), {});
@@ -0,0 +1 @@
1
+ export declare const getGitHookPaths: (defaultPath?: string) => string[];
@@ -0,0 +1,20 @@
1
+ import { execSync } from 'child_process';
2
+ import { join } from './path.js';
3
+ const hookFileNames = [
4
+ `prepare-commit-msg`,
5
+ `commit-msg`,
6
+ `pre-{applypatch,commit,merge-commit,push,rebase,receive}`,
7
+ `post-{checkout,commit,merge,rewrite}`,
8
+ ];
9
+ const getGitHooksPath = (defaultPath = '.git/hooks') => {
10
+ try {
11
+ return execSync('git config --get core.hooksPath', { encoding: 'utf8' }).trim();
12
+ }
13
+ catch (error) {
14
+ return defaultPath;
15
+ }
16
+ };
17
+ export const getGitHookPaths = (defaultPath = '.git/hooks') => {
18
+ const gitHooksPath = getGitHooksPath(defaultPath);
19
+ return hookFileNames.map(fileName => join(gitHooksPath, fileName));
20
+ };
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "2.21.2";
1
+ export declare const version = "2.23.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '2.21.2';
1
+ export const version = '2.23.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "2.21.2",
3
+ "version": "2.23.0",
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",
@@ -25,7 +25,7 @@
25
25
  "pretest": "node rmdir.js tmp && swc src -d tmp/src && swc tests -d tmp/tests",
26
26
  "test": "node --no-warnings --test tmp",
27
27
  "coverage": "c8 --reporter html node --no-warnings --loader tsx --test tests/*.test.ts tests/*/*.test.ts",
28
- "watch": "tsc --watch",
28
+ "watch": "rm $(which knip); npm link && tsc --watch",
29
29
  "prebuild": "node rmdir.js dist",
30
30
  "build": "tsc",
31
31
  "docs": "npm run docs:cli && npm run docs:plugins && npm run docs:format",
@@ -65,21 +65,21 @@
65
65
  "@npmcli/package-json": "5.0.0",
66
66
  "@release-it/bumper": "5.1.0",
67
67
  "@swc/cli": "0.1.62",
68
- "@swc/core": "1.3.82",
68
+ "@swc/core": "1.3.84",
69
69
  "@types/eslint": "8.44.2",
70
70
  "@types/js-yaml": "4.0.5",
71
71
  "@types/micromatch": "4.0.2",
72
72
  "@types/minimist": "1.2.2",
73
- "@types/node": "20.5.9",
73
+ "@types/node": "20.6.0",
74
74
  "@types/npmcli__map-workspaces": "3.0.1",
75
75
  "@types/webpack": "5.28.2",
76
- "@typescript-eslint/eslint-plugin": "6.6.0",
77
- "@typescript-eslint/parser": "6.6.0",
76
+ "@typescript-eslint/eslint-plugin": "6.7.0",
77
+ "@typescript-eslint/parser": "6.7.0",
78
78
  "c8": "8.0.1",
79
- "eslint": "8.48.0",
79
+ "eslint": "8.49.0",
80
80
  "eslint-import-resolver-typescript": "3.6.0",
81
81
  "eslint-plugin-import": "2.28.1",
82
- "eslint-plugin-n": "16.0.2",
82
+ "eslint-plugin-n": "16.1.0",
83
83
  "prettier": "3.0.3",
84
84
  "release-it": "16.1.5",
85
85
  "remark-cli": "11.0.0",
@@ -1 +0,0 @@
1
- export declare const getGitHooksPath: () => string;
@@ -1,9 +0,0 @@
1
- import { execSync } from 'child_process';
2
- export const getGitHooksPath = () => {
3
- try {
4
- return execSync('git config --get core.hooksPath', { encoding: 'utf8' }).trim();
5
- }
6
- catch (error) {
7
- return '.husky';
8
- }
9
- };