knip 2.33.0 → 2.33.2

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
@@ -23,36 +23,37 @@ The dots don't connect themselves. This is where Knip comes in:
23
23
  - [x] Finds binaries and dependencies in npm scripts, and a lot more locations
24
24
  - [x] Finds unused members of classes and enums
25
25
  - [x] Finds duplicate exports
26
+ - [x] Use [production and strict mode][4] to focus on isolated production code
26
27
  - [x] Supports any combination of JavaScript and TypeScript
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
+ - [x] Multiple built-in [reporters][5] (or use [custom reporters][6] and [preprocessors][7])
29
+ - [x] Understands [JSDoc/TSDoc tags][8] (e.g. `@public` and `@internal`)
29
30
  - [x] Run Knip as part of your CI environment to detect issues and prevent regressions
30
31
 
31
32
  Knip shines in both small and large projects. It's a fresh take on keeping your projects clean & tidy!
32
33
 
33
- [![An orange cow with scissors, Van Gogh style][9]][8] <sup>_“An orange cow with scissors, Van Gogh style” - generated
34
+ [![An orange cow with scissors, Van Gogh style][10]][9] <sup>_“An orange cow with scissors, Van Gogh style” - generated
34
35
  with OpenAI_</sup>
35
36
 
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
+ For updates or questions, come hang out in [The Knip Barn (Discord)][11], or follow [@webprolific (Twitter)][12] or
38
+ [@webpro (fosstodon.org)][13]. Please use GitHub to [report issues][14].
38
39
 
39
40
  ## Contents
40
41
 
41
- - [Getting Started][14]
42
- - [Installation][15]
43
- - [Default Configuration][16]
44
- - [Let's Go!][17]
45
- - [Configuration][18]
46
- - [Entry Files][19]
42
+ - [Getting Started][15]
43
+ - [Installation][16]
44
+ - [Default Configuration][17]
45
+ - [Let's Go!][18]
46
+ - [Configuration][19]
47
+ - [Entry Files][20]
47
48
  - [Workspaces][1]
48
49
  - [Plugins][2]
49
50
  - [Compilers][3]
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]
51
+ - [Ignore files, binaries, dependencies and workspaces][21]
52
+ - [Public exports][22]
53
+ - [Ignore exports used in file][23]
54
+ - [Include exports in entry files][24]
55
+ - [Paths][25]
56
+ - [Production Mode][4]
56
57
  - [Strict][26]
57
58
  - [Ignore `@internal` exports][27]
58
59
  - [Plugins][28]
@@ -60,9 +61,9 @@ For updates or questions, come hang out in [The Knip Barn (Discord)][10], or fol
60
61
  - [Screenshots][30]
61
62
  - [Reading the report][31]
62
63
  - [Rules & Filters][32]
63
- - [Reporters][4]
64
+ - [Reporters][5]
64
65
  - [Fixing Issues][33]
65
- - [JSDoc tags][7]
66
+ - [JSDoc tags][8]
66
67
  - [Command Line Options][34]
67
68
  - [Potential boost with `--no-gitignore`][35]
68
69
  - [Comparison & Migration][36]
@@ -88,7 +89,7 @@ Knip supports LTS versions of Node.js, and currently requires at least Node.js v
88
89
 
89
90
  ### Default Configuration
90
91
 
91
- Knip has good defaults and you can run it without any configuration. The (simplified) default config:
92
+ Knip has good defaults and aims for no or little configuration. The (simplified) default config:
92
93
 
93
94
  ```json
94
95
  {
@@ -97,9 +98,9 @@ Knip has good defaults and you can run it without any configuration. The (simpli
97
98
  }
98
99
  ```
99
100
 
100
- There's more, jump to [Entry Files][19] for details.
101
+ There's more, jump to [Entry Files][20] for details.
101
102
 
102
- Places where Knip looks for configuration (ordered by priority):
103
+ Knip looks for this configuration file (ordered by priority):
103
104
 
104
105
  - `knip.json`
105
106
  - `knip.jsonc`
@@ -122,7 +123,7 @@ const config: KnipConfig = {
122
123
  export default config;
123
124
  ```
124
125
 
125
- Use `--config path/to/knip.json` to use a different location.
126
+ Use `--config path/to/knip.config.json` for a different path.
126
127
 
127
128
  ### Let's Go!
128
129
 
@@ -136,8 +137,9 @@ Run the checks with `npx knip`. Or first add this script to `package.json`:
136
137
  }
137
138
  ```
138
139
 
139
- Then use `npm run knip` to analyze the project and output unused files, dependencies and exports. Knip works just fine
140
- with `yarn` or `pnpm` as well.
140
+ Then use `npm run knip` to analyze the project and output unused files, dependencies and exports.
141
+
142
+ Knip works just fine with `yarn` or `pnpm` or `bun`.
141
143
 
142
144
  See [Command Line Options][34] for an overview of available CLI options.
143
145
 
@@ -150,15 +152,15 @@ In addition to `index.js`, the following file names and extensions are also cons
150
152
  - `index`, `main` and `cli`
151
153
  - `js`, `mjs`, `cjs`, `jsx`, `ts`, `mts`, `cts` and `tsx`
152
154
 
153
- This means files like `main.cjs` and `src/cli.ts` are automatically added as entry files. Knip looks for entry files at
154
- those default locations, but also in other places:
155
+ This means files like `main.cjs` and `src/cli.ts` are automatically added as entry files. Knip also looks for `entry`
156
+ files in other places:
155
157
 
156
158
  - The `main`, `bin` and `exports` fields of `package.json`.
157
- - [Plugins][2] such as for Next.js, Remix, Gatsby or Svelte add entry files.
158
- - The `scripts` in package.json or other scripts may provide entry files.
159
+ - [Plugins][2] such as for Jest, Next.js, Svelte or Vitest etc. add entry files.
160
+ - The `scripts` in package.json and other scripts may provide entry files.
159
161
 
160
- Knip does this for each [workspace][1] it finds, trying to minimize the configuration to suit your project. In a
161
- perfectly boring world where everything is according to defaults you wouldn't even need a `knip.json` file at all.
162
+ Knip does this for each [workspace][1] it finds. In a perfectly boring world where everything is according to defaults
163
+ you wouldn't even need a `knip.json` file at all.
162
164
 
163
165
  Larger projects tend to have more things customized, and therefore probably get more out of Knip with a configuration
164
166
  file. Let's say you are using `.ts` files exclusively and have all source files only in the `src` directory:
@@ -202,7 +204,7 @@ Here's an example `knip.json` configuration with some custom `entry` and `projec
202
204
  ```
203
205
 
204
206
  It might be useful to run Knip first with no or little configuration to see where it needs custom `entry` and/or
205
- `project` files. Each workspace has the same [default configuration][18].
207
+ `project` files. Each workspace has the same [default configuration][19].
206
208
 
207
209
  The root workspace is named `"."` under `workspaces` (like in the example).
208
210
 
@@ -540,6 +542,9 @@ The report contains the following types of issues:
540
542
  | Unused exported class members | unable to find references to this class member | `classMembers` |
541
543
  | Duplicate exports | the same thing is exported more than once | `duplicates` |
542
544
 
545
+ Note that `devDependencies` is covered within a single key for all `dependencies`. In [strict production mode][4],
546
+ `devDependencies` are not included.
547
+
543
548
  When an issue type has zero issues, it is not shown.
544
549
 
545
550
  Getting too many reported issues and false positives? Read more about [handling issues][49].
@@ -753,7 +758,7 @@ unimported
753
758
  knip --production --dependencies --include files
754
759
  ```
755
760
 
756
- Also see [production mode][25].
761
+ Also see [production mode][4].
757
762
 
758
763
  ### ts-unused-exports
759
764
 
@@ -793,7 +798,7 @@ Many thanks to some of the early adopters of Knip:
793
798
 
794
799
  ## Articles, etc.
795
800
 
796
- - Discord: hang out in [The Knip Barn][10]
801
+ - Discord: hang out in [The Knip Barn][11]
797
802
  - Ask your questions in the [Knip knowledge base][67] (powered by OpenAI and [7-docs][68], experimental!)
798
803
  - Smashing Magazine: [Knip: An Automated Tool For Finding Unused Files, Exports, And Dependencies][69]
799
804
  - Effective TypeScript: [Recommendation Update: ✂️ Use knip to detect dead code and types][70]
@@ -820,28 +825,28 @@ Special thanks to the wonderful people who have contributed to this project:
820
825
  [1]: #workspaces
821
826
  [2]: #plugins
822
827
  [3]: #compilers
823
- [4]: #reporters
824
- [5]: #custom-reporters
825
- [6]: #preprocessers
826
- [7]: #jsdoc-tags
827
- [8]: https://labs.openai.com/s/xZQACaLepaKya0PRUPtIN5dC
828
- [9]: ./assets/cow-with-orange-scissors-van-gogh-style.webp
829
- [10]: https://discord.gg/r5uXTtbTpc
830
- [11]: https://twitter.com/webprolific
831
- [12]: https://fosstodon.org/@webpro
832
- [13]: https://github.com/webpro/knip/issues
833
- [14]: #getting-started
834
- [15]: #installation
835
- [16]: #default-configuration
836
- [17]: #lets-go
837
- [18]: #configuration
838
- [19]: #entry-files
839
- [20]: #ignore-files-binaries-dependencies-and-workspaces
840
- [21]: #public-exports
841
- [22]: #ignore-exports-used-in-file
842
- [23]: #include-exports-in-entry-files
843
- [24]: #paths
844
- [25]: #production-mode
828
+ [4]: #production-mode
829
+ [5]: #reporters
830
+ [6]: #custom-reporters
831
+ [7]: #preprocessers
832
+ [8]: #jsdoc-tags
833
+ [9]: https://labs.openai.com/s/xZQACaLepaKya0PRUPtIN5dC
834
+ [10]: ./assets/cow-with-orange-scissors-van-gogh-style.webp
835
+ [11]: https://discord.gg/r5uXTtbTpc
836
+ [12]: https://twitter.com/webprolific
837
+ [13]: https://fosstodon.org/@webpro
838
+ [14]: https://github.com/webpro/knip/issues
839
+ [15]: #getting-started
840
+ [16]: #installation
841
+ [17]: #default-configuration
842
+ [18]: #lets-go
843
+ [19]: #configuration
844
+ [20]: #entry-files
845
+ [21]: #ignore-files-binaries-dependencies-and-workspaces
846
+ [22]: #public-exports
847
+ [23]: #ignore-exports-used-in-file
848
+ [24]: #include-exports-in-entry-files
849
+ [25]: #paths
845
850
  [26]: #strict
846
851
  [27]: #ignore-internal-exports
847
852
  [28]: #plugins-1
@@ -182,8 +182,13 @@ export class WorkspaceWorker {
182
182
  const configFilePaths = allConfigFilePaths.filter(filePath => !filePath.endsWith('package.json') ||
183
183
  get(this.manifest, 'PACKAGE_JSON_PATH' in plugin ? plugin.PACKAGE_JSON_PATH : pluginName));
184
184
  debugLogArray(`Found ${plugin.NAME} config file paths`, configFilePaths);
185
- if (patterns.length > 0 && configFilePaths.length === 0)
186
- continue;
185
+ if (patterns.length > 0 && configFilePaths.length === 0) {
186
+ if (typeof pluginConfig !== 'boolean' && pluginConfig.entry !== null && pluginConfig.entry.length > 0) {
187
+ }
188
+ else {
189
+ continue;
190
+ }
191
+ }
187
192
  if (patterns.length === 0)
188
193
  configFilePaths.push(FAKE_PATH);
189
194
  const pluginDependencies = new Set();
@@ -1,6 +1,7 @@
1
+ import { IGNORED_FILE_EXTENSIONS } from '../constants.js';
1
2
  import { compact } from '../util/array.js';
2
3
  import { getPackageNameFromModuleSpecifier } from '../util/modules.js';
3
- import { isInternal } from '../util/path.js';
4
+ import { extname, isInternal } from '../util/path.js';
4
5
  import { timerify } from '../util/Performance.js';
5
6
  import { isBinary } from '../util/protocols.js';
6
7
  import { getBinariesFromScript } from './bash-parser.js';
@@ -10,8 +11,14 @@ const getDependenciesFromScripts = (npmScripts, options = {}) => {
10
11
  const scripts = typeof npmScripts === 'string' ? [npmScripts] : [...npmScripts];
11
12
  const results = scripts.flatMap(script => getBinariesFromScript(script, { cwd, manifest, knownGlobalsOnly }));
12
13
  return compact(results.map(identifier => {
13
- if (isBinary(identifier) || isInternal(identifier))
14
+ if (isBinary(identifier))
14
15
  return identifier;
16
+ if (isInternal(identifier)) {
17
+ const ext = extname(identifier);
18
+ if (ext && IGNORED_FILE_EXTENSIONS.includes(ext))
19
+ return;
20
+ return identifier;
21
+ }
15
22
  return getPackageNameFromModuleSpecifier(identifier);
16
23
  }));
17
24
  };
package/dist/constants.js CHANGED
@@ -45,6 +45,7 @@ export const IGNORED_FILE_EXTENSIONS = [
45
45
  '.sass',
46
46
  '.scss',
47
47
  '.svg',
48
+ '.sh',
48
49
  '.ttf',
49
50
  '.webp',
50
51
  '.woff',
@@ -14,12 +14,14 @@ export const toEntryPatterns = (testMatch, cwd, configFilePath, config) => {
14
14
  const patterns = [testMatch].flat().filter((p) => typeof p === 'string');
15
15
  return patterns.map(pattern => toEntryPattern(join(dir, pattern)));
16
16
  };
17
- const findPlaywrightDependencies = async (configFilePath, { cwd }) => {
18
- const config = await load(configFilePath);
19
- const projects = config.projects ? [config, ...config.projects] : [config];
20
- const patterns = projects.flatMap(config => toEntryPatterns(config.testMatch, cwd, configFilePath, config));
21
- if (patterns.length > 0)
22
- return patterns;
23
- return toEntryPatterns(ENTRY_FILE_PATTERNS, cwd, configFilePath, config);
17
+ const findPlaywrightDependencies = async (configFilePath, { cwd, config }) => {
18
+ const cfg = await load(configFilePath);
19
+ if (cfg) {
20
+ const projects = cfg.projects ? [cfg, ...cfg.projects] : [cfg];
21
+ const patterns = projects.flatMap(config => toEntryPatterns(config.testMatch, cwd, configFilePath, config));
22
+ if (patterns.length > 0)
23
+ return patterns;
24
+ }
25
+ return toEntryPatterns(config?.entry ?? ENTRY_FILE_PATTERNS, cwd, configFilePath, cfg ?? {});
24
26
  };
25
27
  export const findDependencies = timerify(findPlaywrightDependencies);
@@ -3,6 +3,7 @@ export declare const NAME = "Storybook";
3
3
  export declare const ENABLERS: (string | RegExp)[];
4
4
  export declare const isEnabled: IsPluginEnabledCallback;
5
5
  export declare const CONFIG_FILE_PATTERNS: string[];
6
+ export declare const STORIES_FILE_PATTERNS: string[];
6
7
  export declare const ENTRY_FILE_PATTERNS: string[];
7
8
  export declare const PROJECT_FILE_PATTERNS: string[];
8
9
  export declare const findDependencies: GenericPluginCallback;
@@ -1,23 +1,27 @@
1
+ import { dirname, join, relative } from '../../util/path.js';
1
2
  import { timerify } from '../../util/Performance.js';
2
3
  import { hasDependency, load } from '../../util/plugin.js';
3
4
  import { toEntryPattern } from '../../util/protocols.js';
4
5
  export const NAME = 'Storybook';
5
6
  export const ENABLERS = [/^@storybook\//, '@nrwl/storybook'];
6
7
  export const isEnabled = ({ dependencies }) => hasDependency(dependencies, ENABLERS);
7
- export const CONFIG_FILE_PATTERNS = ['.storybook/{main,manager,test-runner}.{js,ts}'];
8
- export const ENTRY_FILE_PATTERNS = ['.storybook/preview.{js,jsx,ts,tsx}', '**/*.stories.{js,jsx,ts,tsx}'];
8
+ export const CONFIG_FILE_PATTERNS = ['.storybook/{main,test-runner}.{js,ts}'];
9
+ export const STORIES_FILE_PATTERNS = ['**/*.stories.{js,jsx,ts,tsx}'];
10
+ export const ENTRY_FILE_PATTERNS = ['.storybook/{manager,preview}.{js,jsx,ts,tsx}', ...STORIES_FILE_PATTERNS];
9
11
  export const PROJECT_FILE_PATTERNS = ['.storybook/**/*.{js,jsx,ts,tsx}'];
10
- const findStorybookDependencies = async (configFilePath, { isProduction }) => {
11
- const entryPatterns = ENTRY_FILE_PATTERNS.map(toEntryPattern);
12
+ const findStorybookDependencies = async (configFilePath, { isProduction, config }) => {
13
+ const cfg = await load(configFilePath);
14
+ const stories = (typeof cfg.stories === 'function' ? await cfg.stories(STORIES_FILE_PATTERNS) : cfg.stories)?.map(pattern => relative(join(dirname(configFilePath), pattern)));
15
+ const cfgPatterns = [...(config?.entry ?? []), ...(stories ?? [])];
16
+ const entryPatterns = (cfgPatterns.length > 0 ? cfgPatterns : ENTRY_FILE_PATTERNS).map(toEntryPattern);
12
17
  if (isProduction)
13
18
  return entryPatterns;
14
- const config = await load(configFilePath);
15
- if (!config)
19
+ if (!cfg)
16
20
  return [];
17
- const addons = config.addons?.map(addon => (typeof addon === 'string' ? addon : addon.name)) ?? [];
18
- const builder = config?.core?.builder;
21
+ const addons = cfg.addons?.map(addon => (typeof addon === 'string' ? addon : addon.name)) ?? [];
22
+ const builder = cfg?.core?.builder;
19
23
  const builderPackages = builder && /webpack/.test(builder) ? [`@storybook/builder-${builder}`, `@storybook/manager-${builder}`] : [];
20
- const frameworks = config.framework?.name ? [config.framework.name] : [];
24
+ const frameworks = cfg.framework?.name ? [cfg.framework.name] : [];
21
25
  return [...entryPatterns, ...addons, ...builderPackages, ...frameworks];
22
26
  };
23
27
  export const findDependencies = timerify(findStorybookDependencies);
@@ -1,4 +1,5 @@
1
1
  export type StorybookConfig = {
2
+ stories?: string[] | ((patterns: string[]) => Promise<string[]>);
2
3
  addons?: (string | {
3
4
  name: string;
4
5
  })[];
@@ -53,16 +53,16 @@ const findWebpackDependencies = async (configFilePath, { manifest, isProduction
53
53
  const dependencies = passes.flatMap(isProduction => {
54
54
  const env = { production: isProduction };
55
55
  const argv = { mode: isProduction ? 'production' : 'development' };
56
- const cfg = typeof config === 'function' ? config(env, argv) : config;
57
- return [cfg].flat().flatMap(config => {
58
- const dependencies = (config.module?.rules?.flatMap(resolveRuleSetDependencies) ?? []).map(loader => loader.replace(/\?.*/, ''));
56
+ const resolvedConfig = typeof config === 'function' ? config(env, argv) : config;
57
+ return [resolvedConfig].flat().flatMap(options => {
58
+ const dependencies = (options.module?.rules?.flatMap(resolveRuleSetDependencies) ?? []).map(loader => loader.replace(/\?.*/, ''));
59
59
  const entries = [];
60
- if (typeof cfg.entry === 'string')
61
- entries.push(cfg.entry);
62
- else if (Array.isArray(cfg.entry))
63
- entries.push(...cfg.entry);
64
- else if (typeof cfg.entry === 'object') {
65
- Object.values(cfg.entry).map(entry => {
60
+ if (typeof options.entry === 'string')
61
+ entries.push(options.entry);
62
+ else if (Array.isArray(options.entry))
63
+ entries.push(...options.entry);
64
+ else if (typeof options.entry === 'object') {
65
+ Object.values(options.entry).map(entry => {
66
66
  if (typeof entry === 'string')
67
67
  entries.push(entry);
68
68
  else if (Array.isArray(entry))
@@ -73,7 +73,7 @@ const findWebpackDependencies = async (configFilePath, { manifest, isProduction
73
73
  entries.push(entry['filename']);
74
74
  });
75
75
  }
76
- return [...dependencies, ...entries.map(entry => (config.context ? join(config.context, entry) : entry))];
76
+ return [...dependencies, ...entries.map(entry => (options.context ? join(options.context, entry) : entry))];
77
77
  });
78
78
  });
79
79
  const scripts = Object.values(manifest.scripts ?? {});
@@ -24,6 +24,7 @@ export default async ({ report, issues, options }) => {
24
24
  ...(report.devDependencies && { devDependencies: [] }),
25
25
  ...(report.optionalPeerDependencies && { optionalPeerDependencies: [] }),
26
26
  ...(report.unlisted && { unlisted: [] }),
27
+ ...(report.binaries && { binaries: [] }),
27
28
  ...(report.unresolved && { unresolved: [] }),
28
29
  ...((report.exports || report.nsExports) && { exports: [] }),
29
30
  ...((report.types || report.nsTypes) && { types: [] }),
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "2.33.0";
1
+ export declare const version = "2.33.2";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '2.33.0';
1
+ export const version = '2.33.2';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "2.33.0",
3
+ "version": "2.33.2",
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",
@@ -69,22 +69,22 @@
69
69
  "@release-it/bumper": "5.1.0",
70
70
  "@swc/cli": "0.1.62",
71
71
  "@swc/core": "1.3.92",
72
- "@types/eslint": "8.44.3",
73
- "@types/js-yaml": "4.0.6",
72
+ "@types/eslint": "8.44.4",
73
+ "@types/js-yaml": "4.0.7",
74
74
  "@types/micromatch": "4.0.3",
75
75
  "@types/minimist": "1.2.3",
76
- "@types/node": "20.8.3",
76
+ "@types/node": "20.8.4",
77
77
  "@types/npmcli__map-workspaces": "3.0.2",
78
78
  "@types/pkgjs__parseargs": "0.10.1",
79
79
  "@types/webpack": "5.28.3",
80
- "@typescript-eslint/eslint-plugin": "6.7.4",
81
- "@typescript-eslint/parser": "6.7.4",
80
+ "@typescript-eslint/eslint-plugin": "6.7.5",
81
+ "@typescript-eslint/parser": "6.7.5",
82
82
  "c8": "8.0.1",
83
83
  "eslint": "8.51.0",
84
84
  "eslint-import-resolver-typescript": "3.6.1",
85
85
  "eslint-plugin-import": "2.28.1",
86
- "eslint-plugin-n": "16.1.0",
87
- "playwright": "1.38.1",
86
+ "eslint-plugin-n": "16.2.0",
87
+ "playwright": "1.39.0",
88
88
  "prettier": "3.0.3",
89
89
  "release-it": "16.2.1",
90
90
  "remark-cli": "12.0.0",