knip 2.19.4 → 2.19.6

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,43 +32,45 @@ 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
+ 📡 For updates, watch this repository or follow [@webprolific (Twitter)][8] or [@webpro (fosstodon.org)][9].
36
+
35
37
  ## Contents
36
38
 
37
- - [Getting Started][8]
38
- - [Installation][9]
39
- - [Default Configuration][10]
40
- - [Let's Go!][11]
41
- - [Configuration][12]
42
- - [Entry Files][13]
39
+ - [Getting Started][10]
40
+ - [Installation][11]
41
+ - [Default Configuration][12]
42
+ - [Let's Go!][13]
43
+ - [Configuration][14]
44
+ - [Entry Files][15]
43
45
  - [Workspaces][1]
44
46
  - [Plugins][2]
45
47
  - [Compilers][3]
46
- - [Ignore files, binaries, dependencies and workspaces][14]
47
- - [Public exports][15]
48
- - [Ignore exports used in file][16]
49
- - [Include exports in entry files][17]
50
- - [Paths][18]
51
- - [Production Mode][19]
52
- - [Strict][20]
53
- - [Plugins][21]
54
- - [Output][22]
55
- - [Screenshots][23]
56
- - [Reading the report][24]
57
- - [Rules & Filters][25]
48
+ - [Ignore files, binaries, dependencies and workspaces][16]
49
+ - [Public exports][17]
50
+ - [Ignore exports used in file][18]
51
+ - [Include exports in entry files][19]
52
+ - [Paths][20]
53
+ - [Production Mode][21]
54
+ - [Strict][22]
55
+ - [Plugins][23]
56
+ - [Output][24]
57
+ - [Screenshots][25]
58
+ - [Reading the report][26]
59
+ - [Rules & Filters][27]
58
60
  - [Reporters][4]
59
- - [Fixing Issues][26]
60
- - [Command Line Options][27]
61
- - [Potential boost with `--no-gitignore`][28]
62
- - [Comparison & Migration][29]
63
- - [depcheck][30]
64
- - [unimported][31]
65
- - [ts-unused-exports][32]
66
- - [ts-prune][33]
67
- - [Projects using Knip][34]
68
- - [Articles, etc.][35]
69
- - [Why "Knip"?][36]
70
- - [Really, another unused file/dependency/export finder?][37]
71
- - [Contributors][38]
61
+ - [Fixing Issues][28]
62
+ - [Command Line Options][29]
63
+ - [Potential boost with `--no-gitignore`][30]
64
+ - [Comparison & Migration][31]
65
+ - [depcheck][32]
66
+ - [unimported][33]
67
+ - [ts-unused-exports][34]
68
+ - [ts-prune][35]
69
+ - [Projects using Knip][36]
70
+ - [Articles, etc.][37]
71
+ - [Why "Knip"?][38]
72
+ - [Really, another unused file/dependency/export finder?][39]
73
+ - [Contributors][40]
72
74
 
73
75
  ## Getting Started
74
76
 
@@ -89,7 +91,7 @@ Knip has good defaults and you can run it without any configuration. The (simpli
89
91
  }
90
92
  ```
91
93
 
92
- There's more, jump to [Entry Files][13] for details.
94
+ There's more, jump to [Entry Files][15] for details.
93
95
 
94
96
  Places where Knip looks for configuration (ordered by priority):
95
97
 
@@ -131,7 +133,7 @@ Run the checks with `npx knip`. Or first add this script to `package.json`:
131
133
  Then use `npm run knip` to analyze the project and output unused files, dependencies and exports. Knip works just fine
132
134
  with `yarn` or `pnpm` as well.
133
135
 
134
- See [Command Line Options][27] for an overview of available CLI options.
136
+ See [Command Line Options][29] for an overview of available CLI options.
135
137
 
136
138
  ## Configuration
137
139
 
@@ -194,7 +196,7 @@ Here's an example `knip.json` configuration with some custom `entry` and `projec
194
196
  ```
195
197
 
196
198
  It might be useful to run Knip first with no or little configuration to see where it needs custom `entry` and/or
197
- `project` files. Each workspace has the same [default configuration][12].
199
+ `project` files. Each workspace has the same [default configuration][14].
198
200
 
199
201
  The root workspace is named `"."` under `workspaces` (like in the example).
200
202
 
@@ -317,7 +319,7 @@ has them at `e2e-tests/*.spec.ts`. Here's how to configure this:
317
319
  #### Multi-project repositories
318
320
 
319
321
  Some repositories have a single `package.json`, but consist of multiple projects with configuration files across the
320
- repository (such as the [Nx "intregrated repo" style][39]). Let's assume some of these projects are apps and have their
322
+ repository (such as the [Nx "intregrated repo" style][41]). Let's assume some of these projects are apps and have their
321
323
  own Cypress configuration and test files. In that case, we could configure the Cypress plugin like this:
322
324
 
323
325
  ```json
@@ -334,7 +336,7 @@ In case a plugin causes issues, it can be disabled by using `false` as its value
334
336
 
335
337
  #### Create a new plugin
336
338
 
337
- Getting false positives because a plugin is missing? Want to help out? Please read more at [writing a plugin][40]. This
339
+ Getting false positives because a plugin is missing? Want to help out? Please read more at [writing a plugin][42]. This
338
340
  guide also contains more details if you want to learn more about plugins and why they are useful.
339
341
 
340
342
  ### Compilers
@@ -357,7 +359,7 @@ export default {
357
359
  };
358
360
  ```
359
361
 
360
- Read [Compilers][41] for more details and examples.
362
+ Read [Compilers][43] for more details and examples.
361
363
 
362
364
  ### Ignore files, binaries, dependencies and workspaces
363
365
 
@@ -519,7 +521,7 @@ The report contains the following types of issues:
519
521
 
520
522
  When an issue type has zero issues, it is not shown.
521
523
 
522
- Getting too many reported issues and false positives? Read more about [handling issues][42].
524
+ Getting too many reported issues and false positives? Read more about [handling issues][44].
523
525
 
524
526
  ### Rules & Filters
525
527
 
@@ -546,7 +548,7 @@ Example:
546
548
  }
547
549
  ```
548
550
 
549
- See [reading the report][24] for the list of issue types.
551
+ See [reading the report][26] for the list of issue types.
550
552
 
551
553
  The rules are modeled after the ESLint `rules` configuration, and could be extended in the future. For instance, to
552
554
  apply filters or configurations only to a specific issue type.
@@ -567,7 +569,7 @@ Use `--exclude` to ignore reports you're not interested in:
567
569
 
568
570
  Use `--dependencies` or `--exports` as shortcuts to combine groups of related types.
569
571
 
570
- See [reading the report][24] for the list of issue types.
572
+ See [reading the report][26] for the list of issue types.
571
573
 
572
574
  #### When to use rules or filters
573
575
 
@@ -594,7 +596,7 @@ When the provided built-in reporters are not sufficient, a custom reporter can b
594
596
  Pass something like `--reporter ./my-reporter` from the command line. The results are passed to the function from its
595
597
  default export and can be used to write issues to `stdout`, a JSON or CSV file, or sent to a service.
596
598
 
597
- Find more details and ideas in [custom reporters][43].
599
+ Find more details and ideas in [custom reporters][45].
598
600
 
599
601
  ## Fixing Issues
600
602
 
@@ -612,7 +614,7 @@ Tip: back up files or use an VCS like Git before deleting files or making change
612
614
 
613
615
  Repeat the process to reveal new unused files and exports. It's so liberating to remove unused things!
614
616
 
615
- Getting too many reported issues and false positives? Read more about [handling issues][44] describing potential causes
617
+ Getting too many reported issues and false positives? Read more about [handling issues][46] describing potential causes
616
618
  for false positives, and how to handle them.
617
619
 
618
620
  ## Command Line Options
@@ -661,13 +663,13 @@ for false positives, and how to handle them.
661
663
 
662
664
  ## Potential boost with `--no-gitignore`
663
665
 
664
- To increase performance in a large monorepo, check out [Potential boost with `--no-gitignore`][45].
666
+ To increase performance in a large monorepo, check out [Potential boost with `--no-gitignore`][47].
665
667
 
666
668
  ## Comparison & Migration
667
669
 
668
670
  This table is an ongoing comparison. Based on their docs (please report any mistakes):
669
671
 
670
- | Feature | **knip** | [depcheck][46] | [unimported][47] | [ts-unused-exports][48] | [ts-prune][49] |
672
+ | Feature | **knip** | [depcheck][48] | [unimported][49] | [ts-unused-exports][50] | [ts-prune][51] |
671
673
  | :---------------------- | :------: | :------------: | :--------------: | :---------------------: | :------------: |
672
674
  | Unused files | ✅ | - | ✅ | - | - |
673
675
  | Unused dependencies | ✅ | ✅ | ✅ | - | - |
@@ -703,7 +705,7 @@ The following commands are similar:
703
705
  unimported
704
706
  knip --production --dependencies --include files
705
707
 
706
- Also see [production mode][19].
708
+ Also see [production mode][21].
707
709
 
708
710
  ### ts-unused-exports
709
711
 
@@ -725,21 +727,23 @@ The following commands are similar:
725
727
 
726
728
  Many thanks to some of the early adopters of Knip:
727
729
 
728
- - [Block Protocol][50]
729
- - [DeepmergeTS][51]
730
- - [eslint-plugin-functional][52]
731
- - [freeCodeCamp.org][53]
732
- - [is-immutable-type][54]
733
- - [IsaacScript][55]
734
- - [Owncast][56]
735
- - [release-it][57]
736
- - [Template TypeScript Node Package][58]
730
+ - [Block Protocol][52]
731
+ - [DeepmergeTS][53]
732
+ - [eslint-plugin-functional][54]
733
+ - [freeCodeCamp.org][55]
734
+ - [is-immutable-type][56]
735
+ - [IsaacScript][57]
736
+ - [Nuxt][58]
737
+ - [Owncast][59]
738
+ - [release-it][60]
739
+ - [Template TypeScript Node Package][61]
740
+ - [Tipi][62]
737
741
 
738
742
  ## Articles, etc.
739
743
 
740
- - Ask your questions in the [Knip knowledge base][59] (powered by OpenAI and [7-docs][60], experimental!)
741
- - Smashing Magazine: [Knip: An Automated Tool For Finding Unused Files, Exports, And Dependencies][61]
742
- - Effective TypeScript: [Recommendation Update: ✂️ Use knip to detect dead code and types][62]
744
+ - Ask your questions in the [Knip knowledge base][63] (powered by OpenAI and [7-docs][64], experimental!)
745
+ - Smashing Magazine: [Knip: An Automated Tool For Finding Unused Files, Exports, And Dependencies][65]
746
+ - Effective TypeScript: [Recommendation Update: ✂️ Use knip to detect dead code and types][66]
743
747
 
744
748
  ## Why "Knip"?
745
749
 
@@ -757,7 +761,7 @@ each file, and traversing all of this, why not collect the various issues in one
757
761
 
758
762
  Special thanks to the wonderful people who have contributed to this project:
759
763
 
760
- [![Contributors][64]][63]
764
+ [![Contributors][68]][67]
761
765
 
762
766
  [1]: #workspaces
763
767
  [2]: #plugins
@@ -766,63 +770,67 @@ Special thanks to the wonderful people who have contributed to this project:
766
770
  [5]: #custom-reporters
767
771
  [6]: https://labs.openai.com/s/xZQACaLepaKya0PRUPtIN5dC
768
772
  [7]: ./assets/cow-with-orange-scissors-van-gogh-style.webp
769
- [8]: #getting-started
770
- [9]: #installation
771
- [10]: #default-configuration
772
- [11]: #lets-go
773
- [12]: #configuration
774
- [13]: #entry-files
775
- [14]: #ignore-files-binaries-dependencies-and-workspaces
776
- [15]: #public-exports
777
- [16]: #ignore-exports-used-in-file
778
- [17]: #include-exports-in-entry-files
779
- [18]: #paths
780
- [19]: #production-mode
781
- [20]: #strict
782
- [21]: #plugins-1
783
- [22]: #output
784
- [23]: #screenshots
785
- [24]: #reading-the-report
786
- [25]: #rules--filters
787
- [26]: #fixing-issues
788
- [27]: #command-line-options
789
- [28]: #potential-boost-with---no-gitignore
790
- [29]: #comparison--migration
791
- [30]: #depcheck
792
- [31]: #unimported
793
- [32]: #ts-unused-exports
794
- [33]: #ts-prune
795
- [34]: #projects-using-knip
796
- [35]: #articles-etc
797
- [36]: #why-knip
798
- [37]: #really-another-unused-filedependencyexport-finder
799
- [38]: #contributors
800
- [39]: https://nx.dev/concepts/integrated-vs-package-based
801
- [40]: ./docs/writing-a-plugin.md
802
- [41]: ./docs/compilers.md
803
- [42]: #handling-issues
804
- [43]: ./docs/custom-reporters.md
805
- [44]: ./docs/handling-issues.md
806
- [45]: ./docs/perf-boost-with-no-gitignore.md
807
- [46]: https://github.com/depcheck/depcheck
808
- [47]: https://github.com/smeijer/unimported
809
- [48]: https://github.com/pzavolinsky/ts-unused-exports
810
- [49]: https://github.com/nadeesha/ts-prune
811
- [50]: https://github.com/blockprotocol/blockprotocol
812
- [51]: https://github.com/RebeccaStevens/deepmerge-ts
813
- [52]: https://github.com/eslint-functional/eslint-plugin-functional
814
- [53]: https://github.com/freeCodeCamp/freeCodeCamp
815
- [54]: https://github.com/RebeccaStevens/is-immutable-type
816
- [55]: https://github.com/IsaacScript/isaacscript
817
- [56]: https://github.com/owncast/owncast
818
- [57]: https://github.com/release-it/release-it
819
- [58]: https://github.com/JoshuaKGoldberg/template-typescript-node-package
820
- [59]: https://knip.deno.dev
821
- [60]: https://github.com/7-docs/7-docs
822
- [61]: https://www.smashingmagazine.com/2023/08/knip-automated-tool-find-unused-files-exports-dependencies/
823
- [62]: https://effectivetypescript.com/2023/07/29/knip/
824
- [63]: https://github.com/webpro/knip/graphs/contributors
825
- [64]: https://contrib.rocks/image?repo=webpro/knip
773
+ [8]: https://twitter.com/webprolific
774
+ [9]: https://fosstodon.org/@webpro
775
+ [10]: #getting-started
776
+ [11]: #installation
777
+ [12]: #default-configuration
778
+ [13]: #lets-go
779
+ [14]: #configuration
780
+ [15]: #entry-files
781
+ [16]: #ignore-files-binaries-dependencies-and-workspaces
782
+ [17]: #public-exports
783
+ [18]: #ignore-exports-used-in-file
784
+ [19]: #include-exports-in-entry-files
785
+ [20]: #paths
786
+ [21]: #production-mode
787
+ [22]: #strict
788
+ [23]: #plugins-1
789
+ [24]: #output
790
+ [25]: #screenshots
791
+ [26]: #reading-the-report
792
+ [27]: #rules--filters
793
+ [28]: #fixing-issues
794
+ [29]: #command-line-options
795
+ [30]: #potential-boost-with---no-gitignore
796
+ [31]: #comparison--migration
797
+ [32]: #depcheck
798
+ [33]: #unimported
799
+ [34]: #ts-unused-exports
800
+ [35]: #ts-prune
801
+ [36]: #projects-using-knip
802
+ [37]: #articles-etc
803
+ [38]: #why-knip
804
+ [39]: #really-another-unused-filedependencyexport-finder
805
+ [40]: #contributors
806
+ [41]: https://nx.dev/concepts/integrated-vs-package-based
807
+ [42]: ./docs/writing-a-plugin.md
808
+ [43]: ./docs/compilers.md
809
+ [44]: #handling-issues
810
+ [45]: ./docs/custom-reporters.md
811
+ [46]: ./docs/handling-issues.md
812
+ [47]: ./docs/perf-boost-with-no-gitignore.md
813
+ [48]: https://github.com/depcheck/depcheck
814
+ [49]: https://github.com/smeijer/unimported
815
+ [50]: https://github.com/pzavolinsky/ts-unused-exports
816
+ [51]: https://github.com/nadeesha/ts-prune
817
+ [52]: https://github.com/blockprotocol/blockprotocol
818
+ [53]: https://github.com/RebeccaStevens/deepmerge-ts
819
+ [54]: https://github.com/eslint-functional/eslint-plugin-functional
820
+ [55]: https://github.com/freeCodeCamp/freeCodeCamp
821
+ [56]: https://github.com/RebeccaStevens/is-immutable-type
822
+ [57]: https://github.com/IsaacScript/isaacscript
823
+ [58]: https://github.com/nuxt/nuxt
824
+ [59]: https://github.com/owncast/owncast
825
+ [60]: https://github.com/release-it/release-it
826
+ [61]: https://github.com/JoshuaKGoldberg/template-typescript-node-package
827
+ [62]: https://github.com/meienberger/runtipi
828
+ [63]: https://knip.deno.dev
829
+ [64]: https://github.com/7-docs/7-docs
830
+ [65]: https://www.smashingmagazine.com/2023/08/knip-automated-tool-find-unused-files-exports-dependencies/
831
+ [66]: https://effectivetypescript.com/2023/07/29/knip/
832
+ [67]: https://github.com/webpro/knip/graphs/contributors
833
+ [68]: https://contrib.rocks/image?repo=webpro/knip
826
834
  [plugin-ava]: ./src/plugins/ava
827
835
  [plugin-babel]: ./src/plugins/babel
828
836
  [plugin-capacitor]: ./src/plugins/capacitor
@@ -1,3 +1,4 @@
1
+ import { isGitIgnoredSync } from 'globby';
1
2
  import ts from 'typescript';
2
3
  import { DEFAULT_EXTENSIONS } from './constants.js';
3
4
  import { IGNORED_FILE_EXTENSIONS } from './constants.js';
@@ -6,7 +7,7 @@ import { createHosts } from './typescript/createHosts.js';
6
7
  import { getImportsAndExports } from './typescript/getImportsAndExports.js';
7
8
  import { SourceFileManager } from './typescript/SourceFileManager.js';
8
9
  import { isMaybePackageName } from './util/modules.js';
9
- import { extname, isInNodeModules } from './util/path.js';
10
+ import { extname, isInNodeModules, join } from './util/path.js';
10
11
  import { timerify } from './util/Performance.js';
11
12
  const baseCompilerOptions = {
12
13
  allowJs: true,
@@ -22,6 +23,7 @@ const baseCompilerOptions = {
22
23
  moduleResolution: ts.ModuleResolutionKind.NodeNext,
23
24
  };
24
25
  const tsCreateProgram = timerify(ts.createProgram);
26
+ const isGitIgnored = isGitIgnoredSync();
25
27
  export class ProjectPrincipal {
26
28
  entryPaths = new Set();
27
29
  projectPaths = new Set();
@@ -125,13 +127,17 @@ export class ProjectPrincipal {
125
127
  }
126
128
  }
127
129
  else {
128
- if (isMaybePackageName(specifier)) {
129
- external.add(specifier);
130
+ const sanitizedSpecifier = specifier.replace(/^([?!|-]+)?([^!?]+).*/, '$2');
131
+ if (isMaybePackageName(sanitizedSpecifier)) {
132
+ external.add(sanitizedSpecifier);
130
133
  }
131
134
  else {
132
- const ext = extname(specifier);
133
- if (!ext || (ext !== '.json' && !IGNORED_FILE_EXTENSIONS.includes(ext))) {
134
- unresolvedImports.add(specifier);
135
+ const isIgnored = isGitIgnored(join(filePath, sanitizedSpecifier));
136
+ if (!isIgnored) {
137
+ const ext = extname(sanitizedSpecifier);
138
+ if (!ext || (ext !== '.json' && !IGNORED_FILE_EXTENSIONS.includes(ext))) {
139
+ unresolvedImports.add(specifier);
140
+ }
135
141
  }
136
142
  }
137
143
  }
@@ -1,51 +1,55 @@
1
- import parse from '@ericcornelissen/bash-parser';
1
+ import Parser from 'tree-sitter';
2
+ import Bash from 'tree-sitter-bash';
2
3
  import { debugLogObject } from '../util/debug.js';
3
4
  import * as FallbackResolver from './resolvers/fallback.js';
4
5
  import * as KnownResolvers from './resolvers/index.js';
5
6
  import { stripBinaryPath } from './util.js';
7
+ const parser = new Parser();
8
+ parser.setLanguage(Bash);
9
+ const getCommandsFromScript = (script) => {
10
+ const tree = parser.parse(script);
11
+ const commands = [];
12
+ const traverse = (node) => {
13
+ switch (node.type) {
14
+ case 'command': {
15
+ const commandNameIndex = node.children.findIndex(node => node.type === 'command_name');
16
+ const command = node.children.slice(commandNameIndex).map(node => node.text);
17
+ commands.push(command);
18
+ break;
19
+ }
20
+ default:
21
+ break;
22
+ }
23
+ for (const child of node.children) {
24
+ traverse(child);
25
+ }
26
+ };
27
+ traverse(tree.rootNode);
28
+ return commands;
29
+ };
6
30
  export const getBinariesFromScript = (script, { cwd, manifest, knownGlobalsOnly = false }) => {
7
31
  if (!script)
8
32
  return [];
9
33
  const fromArgs = (args) => getBinariesFromScript(args.filter(arg => arg !== '--').join(' '), { cwd, manifest });
10
- const getBinariesFromNodes = (nodes) => nodes.flatMap(node => {
11
- switch (node.type) {
12
- case 'Command': {
13
- const binary = node.name?.text ? stripBinaryPath(node.name.text) : node.name?.text;
14
- const commandExpansions = node.prefix?.flatMap(prefix => prefix.expansion?.filter(expansion => expansion.type === 'CommandExpansion') ?? []) ?? [];
15
- if (commandExpansions.length > 0) {
16
- return commandExpansions.flatMap(expansion => getBinariesFromNodes(expansion.commandAST.commands)) ?? [];
17
- }
18
- if (!binary || binary === '.' || binary === 'source')
19
- return [];
20
- if (binary.startsWith('-') || binary.startsWith('"') || binary.startsWith('..'))
21
- return [];
22
- if (['bun', 'deno'].includes(binary))
23
- return [];
24
- const args = node.suffix?.map(arg => arg.text) ?? [];
25
- if (['!', 'test'].includes(binary))
26
- return fromArgs(args);
27
- if (binary in KnownResolvers) {
28
- return KnownResolvers[binary].resolve(binary, args, { cwd, manifest, fromArgs });
29
- }
30
- if (knownGlobalsOnly)
31
- return [];
32
- return FallbackResolver.resolve(binary, args, { cwd, manifest, fromArgs });
33
- }
34
- case 'LogicalExpression':
35
- return getBinariesFromNodes([node.left, node.right]);
36
- case 'If':
37
- return getBinariesFromNodes([...node.clause.commands, ...node.then.commands, ...(node.else?.commands ?? [])]);
38
- case 'For':
39
- return getBinariesFromNodes(node.do.commands);
40
- case 'CompoundList':
41
- return getBinariesFromNodes(node.commands);
42
- default:
43
- return [];
34
+ const commands = getCommandsFromScript(script);
35
+ const getBinariesFromCommand = (command) => {
36
+ const [bin, ...args] = command;
37
+ const binary = stripBinaryPath(bin);
38
+ if (!binary || binary === '.' || binary === 'source')
39
+ return [];
40
+ if (binary.startsWith('-') || binary.startsWith('"') || binary.startsWith('..'))
41
+ return [];
42
+ if (['bun', 'deno'].includes(binary))
43
+ return [];
44
+ if (binary in KnownResolvers) {
45
+ return KnownResolvers[binary].resolve(binary, args, { cwd, manifest, fromArgs });
44
46
  }
45
- });
47
+ if (knownGlobalsOnly)
48
+ return [];
49
+ return FallbackResolver.resolve(binary, args, { cwd, manifest, fromArgs });
50
+ };
46
51
  try {
47
- const parsed = parse(script);
48
- return parsed?.commands ? getBinariesFromNodes(parsed.commands) : [];
52
+ return commands.map(getBinariesFromCommand).flat();
49
53
  }
50
54
  catch (error) {
51
55
  debugLogObject('Bash parser error', error);
@@ -1,5 +1,6 @@
1
1
  import parseArgs from 'minimist';
2
2
  import { isInternal } from '../../util/path.js';
3
+ import { stripQuotes } from '../../util/string.js';
3
4
  import { getBinariesFromScript } from '../bash-parser.js';
4
5
  import { argsFrom, stripVersionFromSpecifier, toBinary } from '../util.js';
5
6
  export const resolve = (binary, args, { cwd, fromArgs, manifest }) => {
@@ -10,7 +11,7 @@ export const resolve = (binary, args, { cwd, fromArgs, manifest }) => {
10
11
  const packageSpecifier = parsed._[0];
11
12
  const specifier = packageSpecifier ? stripVersionFromSpecifier(packageSpecifier) : '';
12
13
  const packages = parsed.package ? [parsed.package].flat().map(stripVersionFromSpecifier) : [];
13
- const command = parsed.call ? getBinariesFromScript(parsed.call, { cwd, manifest }) : [];
14
+ const command = parsed.call ? getBinariesFromScript(stripQuotes(parsed.call), { cwd, manifest }) : [];
14
15
  const restArgs = argsFrom(args, packageSpecifier);
15
16
  const dependencies = manifest ? Object.keys({ ...manifest.dependencies, ...manifest.devDependencies }) : [];
16
17
  const isBinary = specifier && !packageSpecifier.includes('@') && !isInternal(specifier) && !dependencies.includes(specifier);
@@ -1,10 +1,11 @@
1
1
  import parseArgs from 'minimist';
2
2
  import { compact } from '../../util/array.js';
3
+ import { stripQuotes } from '../../util/string.js';
3
4
  import { toBinary, tryResolveSpecifiers } from '../util.js';
4
5
  export const resolve = (binary, args, { cwd, fromArgs }) => {
5
6
  const safeArgs = args.filter(arg => arg !== '--watch');
6
7
  const parsed = parseArgs(safeArgs, { alias: { plugin: 'p' } });
7
- const watchers = parsed.watch ? fromArgs(Object.values(parsed.watch)) : [];
8
+ const watchers = parsed.watch ? fromArgs(Object.values(parsed.watch).map(value => stripQuotes(value))) : [];
8
9
  const plugins = parsed.plugin ? tryResolveSpecifiers(cwd, [parsed.plugin].flat()) : [];
9
10
  const configPlugins = parsed.configPlugin ? tryResolveSpecifiers(cwd, [parsed.configPlugin].flat()) : [];
10
11
  return compact([toBinary(binary), ...watchers, ...plugins, ...configPlugins]);
package/dist/constants.js CHANGED
@@ -13,6 +13,7 @@ export const IGNORED_GLOBAL_BINARIES = [
13
13
  'cd',
14
14
  'cp',
15
15
  'deno',
16
+ 'dirname',
16
17
  'echo',
17
18
  'exec',
18
19
  'exit',
@@ -28,6 +29,7 @@ export const IGNORED_GLOBAL_BINARIES = [
28
29
  'rm',
29
30
  'sh',
30
31
  'sudo',
32
+ 'test',
31
33
  'true',
32
34
  'yarn',
33
35
  ];
@@ -18,7 +18,6 @@ type LiteralLikeElementAccessExpression = ts.ElementAccessExpression & ts.Declar
18
18
  export declare function isModuleExportsAccessExpression(node: ts.Node): node is LiteralLikeElementAccessExpression & {
19
19
  expression: ts.Identifier;
20
20
  };
21
- export declare function stripQuotes(name: string): string;
22
21
  export declare function findAncestor<T>(node: ts.Node | undefined, callback: (element: ts.Node) => boolean | 'STOP'): T | undefined;
23
22
  export declare function findDescendants<T>(node: ts.Node | undefined, callback: (element: ts.Node) => boolean | 'STOP'): T[];
24
23
  export declare const isDeclarationFileExtension: (extension: string) => boolean;
@@ -1,4 +1,5 @@
1
1
  import ts from 'typescript';
2
+ import { stripQuotes } from '../util/string.js';
2
3
  export function isValidImportTypeNode(node) {
3
4
  return ts.isImportTypeNode(node);
4
5
  }
@@ -39,24 +40,6 @@ export function isModuleExportsAccessExpression(node) {
39
40
  node.expression.escapedText === 'module' &&
40
41
  getAccessExpressionName(node) === 'exports');
41
42
  }
42
- export function stripQuotes(name) {
43
- const length = name.length;
44
- if (length >= 2 && name.charCodeAt(0) === name.charCodeAt(length - 1) && isQuoteOrBacktick(name.charCodeAt(0))) {
45
- return name.substring(1, length - 1);
46
- }
47
- return name;
48
- }
49
- var CharacterCodes;
50
- (function (CharacterCodes) {
51
- CharacterCodes[CharacterCodes["backtick"] = 96] = "backtick";
52
- CharacterCodes[CharacterCodes["doubleQuote"] = 34] = "doubleQuote";
53
- CharacterCodes[CharacterCodes["singleQuote"] = 39] = "singleQuote";
54
- })(CharacterCodes || (CharacterCodes = {}));
55
- function isQuoteOrBacktick(charCode) {
56
- return (charCode === CharacterCodes.singleQuote ||
57
- charCode === CharacterCodes.doubleQuote ||
58
- charCode === CharacterCodes.backtick);
59
- }
60
43
  export function findAncestor(node, callback) {
61
44
  node = node?.parent;
62
45
  while (node) {
@@ -1,7 +1,8 @@
1
1
  import ts from 'typescript';
2
2
  import { SymbolType } from '../../../types/issues.js';
3
3
  import { compact } from '../../../util/array.js';
4
- import { isPrivateMember, stripQuotes } from '../../ast-helpers.js';
4
+ import { stripQuotes } from '../../../util/string.js';
5
+ import { isPrivateMember } from '../../ast-helpers.js';
5
6
  import { exportVisitor as visit } from '../index.js';
6
7
  export default visit(() => true, node => {
7
8
  const modifierKinds = node.modifiers?.map(modifier => modifier.kind) ?? [];
@@ -1,6 +1,7 @@
1
1
  import ts from 'typescript';
2
2
  import { SymbolType } from '../../../types/issues.js';
3
- import { isModuleExportsAccessExpression, stripQuotes } from '../../ast-helpers.js';
3
+ import { stripQuotes } from '../../../util/string.js';
4
+ import { isModuleExportsAccessExpression } from '../../ast-helpers.js';
4
5
  import { isJS } from '../helpers.js';
5
6
  import { exportVisitor as visit } from '../index.js';
6
7
  export default visit(isJS, node => {
@@ -1,9 +1,11 @@
1
1
  import ts from 'typescript';
2
- import { stripQuotes } from '../../ast-helpers.js';
2
+ import { stripQuotes } from '../../../util/string.js';
3
3
  import { scriptVisitor as visit } from '../index.js';
4
4
  export default visit(sourceFile => sourceFile.statements.some(statementImportsExeca$), node => {
5
- if (ts.isTaggedTemplateExpression(node) && node.tag.getText() === '$') {
6
- return stripQuotes(node.template.getText());
5
+ if (ts.isTaggedTemplateExpression(node)) {
6
+ if (node.tag.getText() === '$' || (ts.isCallExpression(node.tag) && node.tag.expression.getText() === '$')) {
7
+ return stripQuotes(node.template.getText());
8
+ }
7
9
  }
8
10
  });
9
11
  function statementImportsExeca$(node) {
@@ -1,5 +1,5 @@
1
1
  import ts from 'typescript';
2
- import { stripQuotes } from '../../ast-helpers.js';
2
+ import { stripQuotes } from '../../../util/string.js';
3
3
  import { scriptVisitor as visit } from '../index.js';
4
4
  export default visit(sourceFile => ts.getShebang(sourceFile.text) === '#!/usr/bin/env zx', node => {
5
5
  if (ts.isTaggedTemplateExpression(node) && node.tag.getText() === '$') {
@@ -0,0 +1 @@
1
+ export declare function stripQuotes(name: string): string;
@@ -0,0 +1,18 @@
1
+ export function stripQuotes(name) {
2
+ const length = name.length;
3
+ if (length >= 2 && name.charCodeAt(0) === name.charCodeAt(length - 1) && isQuoteOrBacktick(name.charCodeAt(0))) {
4
+ return name.substring(1, length - 1);
5
+ }
6
+ return name;
7
+ }
8
+ var CharacterCodes;
9
+ (function (CharacterCodes) {
10
+ CharacterCodes[CharacterCodes["backtick"] = 96] = "backtick";
11
+ CharacterCodes[CharacterCodes["doubleQuote"] = 34] = "doubleQuote";
12
+ CharacterCodes[CharacterCodes["singleQuote"] = 39] = "singleQuote";
13
+ })(CharacterCodes || (CharacterCodes = {}));
14
+ function isQuoteOrBacktick(charCode) {
15
+ return (charCode === CharacterCodes.singleQuote ||
16
+ charCode === CharacterCodes.doubleQuote ||
17
+ charCode === CharacterCodes.backtick);
18
+ }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "2.19.4";
1
+ export declare const version = "2.19.6";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '2.19.4';
1
+ export const version = '2.19.6';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "2.19.4",
3
+ "version": "2.19.6",
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",
@@ -42,35 +42,36 @@
42
42
  "schema.json"
43
43
  ],
44
44
  "dependencies": {
45
- "@ericcornelissen/bash-parser": "^0.5.2",
46
45
  "@npmcli/map-workspaces": "^3.0.4",
47
46
  "@snyk/github-codeowners": "^1.1.0",
48
47
  "chalk": "^5.2.0",
49
48
  "easy-table": "^1.2.0",
50
49
  "fast-glob": "^3.2.12",
51
50
  "globby": "^13.1.3",
52
- "jiti": "1.19.1",
51
+ "jiti": "^1.19.3",
53
52
  "js-yaml": "^4.1.0",
54
53
  "micromatch": "^4.0.5",
55
54
  "minimist": "^1.2.8",
56
55
  "pretty-ms": "^8.0.0",
57
56
  "strip-json-comments": "^5.0.0",
58
57
  "summary": "^2.1.0",
58
+ "tree-sitter": "0.20.5",
59
+ "tree-sitter-bash": "0.20.0",
59
60
  "typescript": "^5.0.2",
60
- "zod": "^3.20.6",
61
- "zod-validation-error": "1.3.1"
61
+ "zod": "^3.22.2",
62
+ "zod-validation-error": "^1.5.0"
62
63
  },
63
64
  "devDependencies": {
64
65
  "@jest/types": "29.6.1",
65
66
  "@npmcli/package-json": "5.0.0",
66
67
  "@release-it/bumper": "5.1.0",
67
68
  "@swc/cli": "0.1.62",
68
- "@swc/core": "1.3.77",
69
+ "@swc/core": "1.3.78",
69
70
  "@types/eslint": "8.44.2",
70
71
  "@types/js-yaml": "4.0.5",
71
72
  "@types/micromatch": "4.0.2",
72
73
  "@types/minimist": "1.2.2",
73
- "@types/node": "20.5.0",
74
+ "@types/node": "20.5.1",
74
75
  "@types/npmcli__map-workspaces": "3.0.1",
75
76
  "@types/webpack": "5.28.1",
76
77
  "@typescript-eslint/eslint-plugin": "6.4.0",
@@ -78,7 +79,7 @@
78
79
  "c8": "8.0.1",
79
80
  "eslint": "8.47.0",
80
81
  "eslint-import-resolver-typescript": "3.6.0",
81
- "eslint-plugin-import": "2.28.0",
82
+ "eslint-plugin-import": "2.28.1",
82
83
  "eslint-plugin-n": "16.0.1",
83
84
  "prettier": "3.0.2",
84
85
  "release-it": "16.1.5",