knip 1.0.0-alpha.2 → 1.0.0-alpha.3

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.
Files changed (47) hide show
  1. package/README.md +111 -70
  2. package/dist/cli.js +5 -3
  3. package/dist/configuration-chief.js +4 -21
  4. package/dist/{util/constants.d.ts → constants.d.ts} +0 -0
  5. package/dist/{util/constants.js → constants.js} +1 -1
  6. package/dist/index.js +12 -14
  7. package/dist/npm-scripts/helpers.js +1 -1
  8. package/dist/npm-scripts/index.js +1 -1
  9. package/dist/plugins/babel/index.js +7 -12
  10. package/dist/plugins/eslint/helpers.d.ts +2 -0
  11. package/dist/plugins/eslint/helpers.js +21 -0
  12. package/dist/plugins/eslint/index.d.ts +1 -0
  13. package/dist/plugins/eslint/index.js +29 -32
  14. package/dist/plugins/eslint/types.d.ts +4 -0
  15. package/dist/plugins/index.d.ts +2 -0
  16. package/dist/plugins/index.js +2 -0
  17. package/dist/plugins/mocha/index.d.ts +5 -0
  18. package/dist/plugins/mocha/index.js +17 -0
  19. package/dist/plugins/next/index.d.ts +0 -1
  20. package/dist/plugins/next/index.js +0 -1
  21. package/dist/plugins/playwright/index.d.ts +0 -1
  22. package/dist/plugins/playwright/index.js +0 -1
  23. package/dist/plugins/postcss/index.d.ts +0 -1
  24. package/dist/plugins/postcss/index.js +0 -1
  25. package/dist/plugins/remix/index.d.ts +0 -1
  26. package/dist/plugins/remix/index.js +0 -1
  27. package/dist/plugins/rollup/index.d.ts +0 -1
  28. package/dist/plugins/rollup/index.js +0 -1
  29. package/dist/plugins/stryker/index.d.ts +4 -0
  30. package/dist/plugins/stryker/index.js +15 -0
  31. package/dist/plugins/stryker/types.d.ts +5 -0
  32. package/dist/plugins/stryker/types.js +1 -0
  33. package/dist/types/cli.d.ts +1 -0
  34. package/dist/types/config.d.ts +2 -1
  35. package/dist/types/plugins.d.ts +5 -3
  36. package/dist/types/validate.d.ts +159 -35
  37. package/dist/types/validate.js +3 -11
  38. package/dist/util/externalImports.js +5 -4
  39. package/dist/util/glob.d.ts +7 -1
  40. package/dist/util/glob.js +12 -3
  41. package/dist/util/help.js +6 -3
  42. package/dist/util/loader.js +11 -0
  43. package/dist/util/parseArgs.d.ts +12 -12
  44. package/dist/util/parseArgs.js +12 -12
  45. package/dist/workspace-worker.d.ts +1 -17
  46. package/dist/workspace-worker.js +46 -62
  47. package/package.json +7 -4
package/README.md CHANGED
@@ -21,22 +21,23 @@ The dots don't connect themselves. This is where Knip comes in:
21
21
  - [x] Finds duplicate exports.
22
22
  - [x] Finds unused members of classes and enums
23
23
  - [x] Built-in support for monorepos (workspaces)
24
- - [x] Growing list of built-in plugins (= less to configure and custom dependency resolvers)
24
+ - [x] Growing list of [built-in plugins][1]
25
+ - [x] Checks npm scripts for used and unlisted dependencies
25
26
  - [x] Supports JavaScript (without `tsconfig.json`, or TypeScript `allowJs: true`).
26
- - [x] Features multiple [reporters][1] and supports [custom reporters][2] (think JSON and `CODEOWNERS`)
27
+ - [x] Features multiple [reporters][2] and supports [custom reporters][3]
27
28
  - [x] Run Knip as part of your CI environment to detect issues and prevent regressions.
28
29
 
29
30
  Knip really shines in larger projects. A little bit of configuration will pay off, I promise. A comparison with similar
30
- tools answers the question [why another unused file/dependency/export finder?][3]
31
+ tools answers the question [why another unused file/dependency/export finder?][4]
31
32
 
32
33
  Knip is a fresh take on keeping your projects clean & tidy!
33
34
 
34
- [![An orange cow with scissors, Van Gogh style][5]][4] <sup>_“An orange cow with scissors, Van Gogh style” - generated
35
+ [![An orange cow with scissors, Van Gogh style][6]][5] <sup>_“An orange cow with scissors, Van Gogh style” - generated
35
36
  with OpenAI_</sup>
36
37
 
37
38
  ## Roadmap
38
39
 
39
- Please report any false positives by [opening an issue in this repo][6]. Bonus points for adding a public repository or
40
+ Please report any false positives by [opening an issue in this repo][7]. Bonus points for adding a public repository or
40
41
  opening a pull request with a directory and example files in `test/fixtures`. Correctness and bug fixes have priority
41
42
  over new features:
42
43
 
@@ -92,15 +93,17 @@ Knip works by creating two sets of files:
92
93
  -c/--config [file] Configuration file path (default: ./knip.json, knip.jsonc or package.json#knip)
93
94
  -t/--tsConfig [file] TypeScript configuration path (default: ./tsconfig.json)
94
95
  --production Analyze only production source files (e.g. no tests, devDependencies, exported types)
96
+ --strict Consider only direct dependencies of workspaces. Not devDependencies, not ancestor workspaces.
95
97
  --workspace Analyze a single workspace (default: analyze all configured workspaces)
96
98
  --include Report only listed issue type(s), can be repeated
97
99
  --exclude Exclude issue type(s) from report, can be repeated
98
100
  --no-progress Don't show dynamic progress updates
101
+ --no-exit-code Always exit with code zero (0)
99
102
  --max-issues Maximum number of issues before non-zero exit code (default: 0)
100
103
  --reporter Select reporter: symbols, compact, codeowners, json (default: symbols)
101
104
  --reporter-options Pass extra options to the reporter (as JSON string, see example)
102
105
  --debug Show debug output
103
- --debug-level Set verbosity of debug output (default: 1, max: 2)
106
+ --debug-level Set verbosity of debug output (default: 1, max: 3)
104
107
  --performance Measure running time of expensive functions and display stats table
105
108
 
106
109
  Issue types: files, dependencies, unlisted, exports, nsExports, classMembers, types, nsTypes, enumMembers, duplicates
@@ -212,31 +215,46 @@ analyze unused dependencies and exports from any directory with a `package.json`
212
215
 
213
216
  Knip contains a growing list of plugins:
214
217
 
215
- - Babel
216
- - Capacitor
217
- - Changesets
218
- - Cypress
219
- - ESLint
220
- - Gatsby
221
- - Jest
222
- - Next.js
223
- - Nx
224
- - Playwright
225
- - PostCSS
226
- - Remark
227
- - Remix
228
- - Rollup
229
- - Storybook
218
+ - [Babel][9]
219
+ - [Capacitor][10]
220
+ - [Changesets][11]
221
+ - [Cypress][12]
222
+ - [ESLint][13]
223
+ - [Gatsby][14]
224
+ - [Jest][15]
225
+ - [Mocha][16]
226
+ - [Next.js][17]
227
+ - [Nx][18]
228
+ - [Playwright][19]
229
+ - [PostCSS][20]
230
+ - [Remark][21]
231
+ - [Remix][22]
232
+ - [Rollup][23]
233
+ - [Storybook][24]
234
+ - [Stryker][25]
230
235
 
231
236
  Plugins are automatically activated, no need to enable anything. Each plugin is automatically enabled based on simple
232
237
  heuristics. Most of them check whether one or one of a few (dev) dependencies are listed in `package.json`. Once
233
- enabled, they add the right set of configuration and entry files for Knip to analyze.
238
+ enabled, they add a set of configuration and/or entry files for Knip to analyze. These defaults can be overriden.
234
239
 
235
- Most configuration files are easy to find dependencies in. Yet some of the plugins include custom dependency resolvers.
236
- For instance, the `eslint` plugin tells Knip that an `"import"` entry in the array of plugins means that the
237
- `eslint-plugin-import` dependency should be installed. Or the `storybook` plugin understands that
238
- `core.builder: 'webpack5'` in `main.js` means that the `@storybook/builder-webpack5` and `@storybook/manager-webpack5`
239
- dependencies are required.
240
+ Most plugins use one or both of the following file types:
241
+
242
+ - `config` - custom dependency resolvers are applied to these files
243
+ - `entryFiles` - files to include with the analysis of the rest of the source code
244
+
245
+ Many configuration files use `require` or `import` statements to use dependencies, so they can be analyzed like the rest
246
+ of the source files. These configuration files are also `entryFiles`.
247
+
248
+ Many plugins also include `config` files. They are parsed by custom dependency resolvers.
249
+
250
+ - The `eslint` plugin tells Knip that an `"prettier"` entry in the array of plugins means that the
251
+ `eslint-plugin-prettier` dependency should be installed.
252
+ - The `storybook` plugin understands that `core.builder: 'webpack5'` in `main.js` means that the
253
+ `@storybook/builder-webpack5` and `@storybook/manager-webpack5` dependencies are required.
254
+ - Static configuration files such as JSON and YAML always require a custom dependency resolver.
255
+
256
+ The only thing that a custom dependency resolver function does is return all referenced dependencies for the
257
+ configuration files it is given.
240
258
 
241
259
  ## Configuration
242
260
 
@@ -258,26 +276,34 @@ export const merge = function () {};
258
276
 
259
277
  Knip does not report public exports and types as unused.
260
278
 
261
- ### Production mode
262
-
263
- The default configuration for Knip is loose and targets all project code. Non-production files such as tests often
264
- import production files, which will prevent the production files from being reported as unused.
279
+ ### Production Mode
265
280
 
266
- Non-production code includes files such as unit tests, end-to-end tests, tooling, scripts, Storybook stories, etc. Think
267
- of it the same way as the convention to split `dependencies` and `devDependencies` in `package.json`.
281
+ The default mode for Knip is holistic and targets all project code. For instance test files usually import production
282
+ files. This prevents the production files or its exports from being reported as unused, while both of them can be
283
+ removed. This is why Knip has a production mode.
268
284
 
269
- To exclude non-production code such as configuration files and tests, use production mode with `--production`. This will
270
- only include the files meant for production use. Add an exclamation mark behind the patterns that are meant for
271
- production like so:
285
+ To analyze only production code, add an exclamation mark behind the patterns that are meant for production and use the
286
+ `--production` flag to analyze (only) them. Here's an example:
272
287
 
273
288
  ```json
274
289
  {
275
290
  "entryFiles": ["build/script.js", "src/index.ts!"],
276
- "projectFiles": ["src/**/*.ts"]
291
+ "projectFiles": ["src/**/*.ts!"]
277
292
  }
278
293
  ```
279
294
 
280
- Configuration, build and test files are not included. Knip finds unused files and export values in production code only.
295
+ Configuration files, test files and build scripts should not be included. Knip looks for unused files, dependencies and
296
+ export values in production code only.
297
+
298
+ #### Strict
299
+
300
+ Additionally, the `--strict` flag can be used to:
301
+
302
+ - Consider only `dependencies` (not `devDependencies`) when finding unused or unlisted dependencies.
303
+ - Assume each workspace is self-contained: they have their own `dependencies` (and not rely on packages of ancestor
304
+ workspaces).
305
+
306
+ #### Plugins
281
307
 
282
308
  Plugins also have this distinction. For instance, Next.js entry files for pages (`pages/**/*.tsx`) and Remix routes
283
309
  (`app/routes/**/*.tsx`) are production code, while Jest and Playwright entry files (e.g. `*.spec.ts`) are not. All of
@@ -288,10 +314,10 @@ locations. The more plugins Knip will have, the more projects can be analyzed ou
288
314
 
289
315
  Knip provides the following built-in reporters:
290
316
 
291
- - [`json`][9]
292
- - [`symbol`][10] (default)
293
- - [`compact`][11]
294
- - [`codeowners`][12]
317
+ - [`json`][26]
318
+ - [`symbol`][27] (default)
319
+ - [`compact`][28]
320
+ - [`codeowners`][29]
295
321
 
296
322
  ### Custom Reporters
297
323
 
@@ -346,11 +372,11 @@ per file like this:
346
372
  ]
347
373
  ```
348
374
 
349
- The keys match the [known issue types][13].
375
+ The keys match the [known issue types][30].
350
376
 
351
377
  #### Usage Ideas
352
378
 
353
- Use tools like [miller][14] or [jtbl][15] to consume the JSON and render a table in the terminal.
379
+ Use tools like [miller][31] or [jtbl][32] to consume the JSON and render a table in the terminal.
354
380
 
355
381
  ##### Table
356
382
 
@@ -467,12 +493,12 @@ collect the various issues in one go?
467
493
 
468
494
  This table is a work in progress, but here's a first impression. Based on their docs (please report any mistakes):
469
495
 
470
- | Feature | **knip** | [depcheck][16] | [unimported][17] | [ts-unused-exports][18] | [ts-prune][19] | [find-unused-exports][20] |
496
+ | Feature | **knip** | [depcheck][33] | [unimported][34] | [ts-unused-exports][35] | [ts-prune][36] | [find-unused-exports][37] |
471
497
  | :-------------------------------- | :------: | :------------: | :--------------: | :---------------------: | :------------: | :-----------------------: |
472
498
  | Unused files | ✅ | - | ✅ | - | - | - |
473
499
  | Unused dependencies | ✅ | ✅ | ✅ | - | - | - |
474
500
  | Unlisted dependencies | ✅ | ✅ | ✅ | - | - | - |
475
- | [Custom dependency resolvers][21] | ✅ | ✅ | ❌ | - | - | - |
501
+ | [Custom dependency resolvers][38] | ✅ | ✅ | ❌ | - | - | - |
476
502
  | Unused exports | ✅ | - | - | ✅ | ✅ | ✅ |
477
503
  | Unused class members | ✅ | - | - | - | - | - |
478
504
  | Unused enum members | ✅ | - | - | - | - | - |
@@ -481,7 +507,7 @@ This table is a work in progress, but here's a first impression. Based on their
481
507
  | Custom reporters | ✅ | - | - | - | - | - |
482
508
  | JavaScript support | ✅ | ✅ | ✅ | - | - | ✅ |
483
509
  | Configure entry files | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
484
- | [Support monorepos][22] | ✅ | - | - | - | - | - |
510
+ | [Support monorepos][39] | ✅ | - | - | - | - | - |
485
511
  | ESLint plugin available | - | - | - | ✅ | - | - |
486
512
 
487
513
  ✅ = Supported, ❌ = Not supported, - = Out of scope
@@ -498,27 +524,42 @@ userland territory, much like code linters.
498
524
  Knip is Dutch for a "cut". A Dutch expression is "to be ge**knip**t for something", which means to be perfectly suited
499
525
  for the job. I'm motivated to make knip perfectly suited for the job of cutting projects to perfection! ✂️
500
526
 
501
- [1]: #reporters
502
- [2]: #custom-reporters
503
- [3]: #really-another-unused-filedependencyexport-finder
504
- [4]: https://labs.openai.com/s/xZQACaLepaKya0PRUPtIN5dC
505
- [5]: ./assets/cow-with-orange-scissors-van-gogh-style.webp
506
- [6]: https://github.com/webpro/knip/issues
507
- [7]: #monorepos
527
+ [1]: #plugins
528
+ [2]: #reporters
529
+ [3]: #custom-reporters
530
+ [4]: #really-another-unused-filedependencyexport-finder
531
+ [5]: https://labs.openai.com/s/xZQACaLepaKya0PRUPtIN5dC
532
+ [6]: ./assets/cow-with-orange-scissors-van-gogh-style.webp
533
+ [7]: https://github.com/webpro/knip/issues
508
534
  [8]: ./assets/how-it-works.drawio.svg
509
- [9]: #json
510
- [10]: #symbol-default
511
- [11]: #compact
512
- [12]: #code-owners
513
- [13]: #reading-the-report
514
- [14]: https://github.com/johnkerl/miller
515
- [15]: https://github.com/kellyjonbrazil/jtbl
516
- [16]: https://github.com/depcheck/depcheck
517
- [17]: https://github.com/smeijer/unimported
518
- [18]: https://github.com/pzavolinsky/ts-unused-exports
519
- [19]: https://github.com/nadeesha/ts-prune
520
- [20]: https://github.com/jaydenseric/find-unused-exports
521
- [21]: #custom-dependency-resolvers
522
- [22]: #monorepos-1
523
- [23]: https://github.com/depcheck/depcheck#special
524
- [24]: #roadmap
535
+ [9]: ./src/plugins/babel
536
+ [10]: ./src/plugins/capacitor
537
+ [11]: ./src/plugins/changesets
538
+ [12]: ./src/plugins/cypress
539
+ [13]: ./src/plugins/eslint
540
+ [14]: ./src/plugins/gatsby
541
+ [15]: ./src/plugins/jest
542
+ [16]: ./src/plugins/mocha
543
+ [17]: ./src/plugins/next
544
+ [18]: ./src/plugins/nx
545
+ [19]: ./src/plugins/playwright
546
+ [20]: ./src/plugins/postcss
547
+ [21]: ./src/plugins/remark
548
+ [22]: ./src/plugins/remix
549
+ [23]: ./src/plugins/rollup
550
+ [24]: ./src/plugins/storybook
551
+ [25]: ./src/plugins/stryker
552
+ [26]: #json
553
+ [27]: #symbol-default
554
+ [28]: #compact
555
+ [29]: #code-owners
556
+ [30]: #reading-the-report
557
+ [31]: https://github.com/johnkerl/miller
558
+ [32]: https://github.com/kellyjonbrazil/jtbl
559
+ [33]: https://github.com/depcheck/depcheck
560
+ [34]: https://github.com/smeijer/unimported
561
+ [35]: https://github.com/pzavolinsky/ts-unused-exports
562
+ [36]: https://github.com/nadeesha/ts-prune
563
+ [37]: https://github.com/jaydenseric/find-unused-exports
564
+ [38]: #custom-dependency-resolvers
565
+ [39]: #monorepos-1
package/dist/cli.js CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import path from 'node:path';
3
3
  import { register } from 'esbuild-register/dist/node.js';
4
- import { printHelp } from './util/help.js';
5
4
  import reporters from './reporters/index.js';
6
5
  import { ConfigurationError } from './util/errors.js';
6
+ import { printHelp } from './util/help.js';
7
7
  import parsedArgs from './util/parseArgs.js';
8
8
  import { measure } from './util/performance.js';
9
9
  import { main } from './index.js';
10
10
  register();
11
- const { values: { help, 'no-gitignore': isNoGitIgnore = false, strict: isStrict = false, production: isProduction = false, 'no-progress': noProgress = false, reporter = 'symbols', 'reporter-options': reporterOptions = '', 'max-issues': maxIssues = '0', debug: isDebug = false, }, } = parsedArgs;
11
+ const { values: { debug: isDebug = false, help, 'max-issues': maxIssues = '0', 'no-exit-code': noExitCode = false, 'no-gitignore': isNoGitIgnore = false, 'no-progress': noProgress = false, production: isProduction = false, reporter = 'symbols', 'reporter-options': reporterOptions = '', strict: isStrict = false, tsConfig, }, } = parsedArgs;
12
12
  if (help) {
13
13
  printHelp();
14
14
  process.exit(0);
@@ -20,6 +20,7 @@ const run = async () => {
20
20
  try {
21
21
  const { report, issues, counters } = await main({
22
22
  cwd,
23
+ tsConfigFile: tsConfig,
23
24
  gitignore: !isNoGitIgnore,
24
25
  isStrict,
25
26
  isProduction,
@@ -30,8 +31,9 @@ const run = async () => {
30
31
  .filter(reportGroup => report[reportGroup])
31
32
  .reduce((errorCount, reportGroup) => errorCount + counters[reportGroup], 0);
32
33
  await measure.print();
33
- if (totalErrorCount > Number(maxIssues))
34
+ if (!noExitCode && totalErrorCount > Number(maxIssues)) {
34
35
  process.exit(totalErrorCount);
36
+ }
35
37
  }
36
38
  catch (error) {
37
39
  if (error instanceof ConfigurationError) {
@@ -1,15 +1,16 @@
1
1
  import path from 'node:path';
2
2
  import micromatch from 'micromatch';
3
+ import { ROOT_WORKSPACE_NAME } from './constants.js';
4
+ import * as plugins from './plugins/index.js';
3
5
  import { LocalConfiguration } from './types/validate.js';
4
6
  import { arrayify } from './util/array.js';
5
- import { ROOT_WORKSPACE_NAME } from './util/constants.js';
6
7
  import { ConfigurationError } from './util/errors.js';
7
8
  import { findFile, loadJSON } from './util/fs.js';
8
9
  import { _dirGlob } from './util/glob.js';
9
10
  import parsedArgs from './util/parseArgs.js';
10
11
  import { resolveIncludedIssueTypes } from './util/resolveIncludedIssueTypes.js';
11
12
  import { workspaceSorter } from './util/sort.js';
12
- const { values: { workspace: rawWorkspaceArg, config: rawConfigArg, include = [], exclude = [], strict: isStrict = false, production: isProduction = false, }, } = parsedArgs;
13
+ const { values: { config: rawConfigArg, workspace: rawWorkspaceArg, include = [], exclude = [], strict: isStrict = false, production: isProduction = false, }, } = parsedArgs;
13
14
  const defaultConfig = {
14
15
  include: [],
15
16
  exclude: [],
@@ -25,23 +26,7 @@ const defaultConfig = {
25
26
  },
26
27
  },
27
28
  };
28
- const PLUGIN_NAMES = [
29
- 'babel',
30
- 'capacitor',
31
- 'changesets',
32
- 'cypress',
33
- 'eslint',
34
- 'gatsby',
35
- 'jest',
36
- 'next',
37
- 'nx',
38
- 'playwright',
39
- 'postcss',
40
- 'remark',
41
- 'remix',
42
- 'rollup',
43
- 'storybook',
44
- ];
29
+ const PLUGIN_NAMES = Object.keys(plugins);
45
30
  export default class ConfigurationChief {
46
31
  cwd = process.cwd();
47
32
  config;
@@ -103,12 +88,10 @@ export default class ConfigurationChief {
103
88
  const config = isObject ? arrayify(pluginConfig.config) : arrayify(pluginConfig);
104
89
  const entryFiles = isObject && 'entryFiles' in pluginConfig ? arrayify(pluginConfig.entryFiles) : [];
105
90
  const projectFiles = isObject && 'projectFiles' in pluginConfig ? arrayify(pluginConfig.projectFiles) : entryFiles;
106
- const sampleFiles = isObject && 'sampleFiles' in pluginConfig ? arrayify(pluginConfig.sampleFiles) : [];
107
91
  workspaces[workspaceName][pluginName] = {
108
92
  config,
109
93
  entryFiles,
110
94
  projectFiles,
111
- sampleFiles,
112
95
  };
113
96
  }
114
97
  }
File without changes
@@ -1,4 +1,4 @@
1
1
  export const ROOT_WORKSPACE_NAME = '.';
2
2
  export const TEST_FILE_PATTERNS = ['**/*.{test,spec}.{js,jsx,ts,tsx}', '**/__tests__/**/*.{js,jsx,ts,tsx}'];
3
- export const IGNORED_GLOBAL_BINARIES = ['npm', 'npx', 'node', 'yarn', 'pnpm'];
3
+ export const IGNORED_GLOBAL_BINARIES = ['npm', 'npx', 'node', 'yarn', 'pnpm', 'deno', 'git'];
4
4
  export const FIRST_ARGUMENT_AS_BINARY_EXCEPTIONS = ['cross-env', 'dotenv'];
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import path from 'node:path';
2
2
  import ConfigurationChief from './configuration-chief.js';
3
+ import { ROOT_WORKSPACE_NAME } from './constants.js';
3
4
  import DependencyDeputy from './dependency-deputy.js';
4
5
  import IssueCollector from './issue-collector.js';
5
6
  import ProjectPrincipal from './project-principal.js';
6
7
  import SourceLab from './source-lab.js';
7
8
  import { compact } from './util/array.js';
8
- import { ROOT_WORKSPACE_NAME } from './util/constants.js';
9
9
  import { debugLogObject, debugLogFiles } from './util/debug.js';
10
10
  import { _findExternalImportModuleSpecifiers } from './util/externalImports.js';
11
11
  import { findFile, loadJSON } from './util/fs.js';
@@ -14,7 +14,7 @@ import { _findDuplicateExportedNames } from './util/project.js';
14
14
  import { loadTSConfig } from './util/tsconfig-loader.js';
15
15
  import WorkspaceWorker from './workspace-worker.js';
16
16
  export const main = async (unresolvedConfiguration) => {
17
- const { cwd, gitignore, isStrict, isProduction, isShowProgress } = unresolvedConfiguration;
17
+ const { cwd, tsConfigFile, gitignore, isStrict, isProduction, isShowProgress } = unresolvedConfiguration;
18
18
  const chief = new ConfigurationChief({ cwd });
19
19
  const deputy = new DependencyDeputy();
20
20
  debugLogObject(1, 'Unresolved configuration', unresolvedConfiguration);
@@ -22,8 +22,6 @@ export const main = async (unresolvedConfiguration) => {
22
22
  collector.updateMessage('Reading configuration and manifest files...');
23
23
  await chief.loadLocalConfig();
24
24
  const workspaces = await chief.getActiveWorkspaces();
25
- if (!chief.manifest || !chief.manifestPath)
26
- throw new Error('mani');
27
25
  debugLogObject(1, 'Included workspaces', workspaces);
28
26
  const report = chief.resolveIncludedIssueTypes();
29
27
  const workspaceDirs = Object.values(workspaces)
@@ -42,7 +40,7 @@ export const main = async (unresolvedConfiguration) => {
42
40
  if (!manifestPath || !manifest)
43
41
  continue;
44
42
  deputy.addWorkspace({ name, dir, manifestPath, manifest });
45
- const tsConfigFilePath = path.join(dir, 'tsconfig.json');
43
+ const tsConfigFilePath = path.join(dir, tsConfigFile ?? 'tsconfig.json');
46
44
  const tsConfig = await loadTSConfig(tsConfigFilePath);
47
45
  if (isRoot && tsConfig) {
48
46
  principal.tsConfigFilePath = tsConfigFilePath;
@@ -188,15 +186,15 @@ export const main = async (unresolvedConfiguration) => {
188
186
  }
189
187
  else {
190
188
  issue.symbol = deputy.resolvePackageName(issue.symbol);
191
- if (!deputy.isInternalDependency(name, issue.symbol)) {
192
- if (!workspaceDependencies.includes(issue.symbol)) {
193
- if (isStrict) {
194
- collector.addIssue(issue);
195
- }
196
- else if (!rootDependencies.includes(issue.symbol)) {
197
- collector.addIssue(issue);
198
- }
199
- }
189
+ if (deputy.isInternalDependency(name, issue.symbol))
190
+ continue;
191
+ if (workspaceDependencies.includes(issue.symbol))
192
+ continue;
193
+ if (isStrict) {
194
+ collector.addIssue(issue);
195
+ }
196
+ else if (!rootDependencies.includes(issue.symbol)) {
197
+ collector.addIssue(issue);
200
198
  }
201
199
  }
202
200
  }
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import path from 'node:path';
3
- import { FIRST_ARGUMENT_AS_BINARY_EXCEPTIONS } from '../util/constants.js';
3
+ import { FIRST_ARGUMENT_AS_BINARY_EXCEPTIONS } from '../constants.js';
4
4
  const require = createRequire(process.cwd());
5
5
  const normalizeBinaries = (command) => command.replace(/(\.\/)?node_modules\/\.bin\/(\w+)/, '$2').replace(/\$\(npm bin\)\/(\w+)/, '$1');
6
6
  const stripEnvironmentVariables = (value) => value.replace(/([A-Z][^ ]*)=([^ ])+ /g, '');
@@ -1,5 +1,5 @@
1
+ import { IGNORED_GLOBAL_BINARIES } from '../constants.js';
1
2
  import { compact } from '../util/array.js';
2
- import { IGNORED_GLOBAL_BINARIES } from '../util/constants.js';
3
3
  import { timerify } from '../util/performance.js';
4
4
  import { getBinariesFromScripts, getPackageManifest } from './helpers.js';
5
5
  const findManifestDependencies = async (ignoreBinaries, manifest, isRoot, dir, cwd) => {
@@ -20,22 +20,17 @@ const api = {
20
20
  caller: () => true,
21
21
  };
22
22
  const getDependenciesFromConfig = (config) => {
23
- if (config) {
24
- const presets = config.presets?.map(preset => (typeof preset === 'string' ? preset : preset[0])).map(resolvePresetName) ?? [];
25
- const plugins = config.plugins?.map(plugin => (typeof plugin === 'string' ? plugin : plugin[0])).map(resolvePluginName) ?? [];
26
- return compact([...presets, ...plugins]);
27
- }
28
- return [];
23
+ const presets = config.presets?.map(preset => (typeof preset === 'string' ? preset : preset[0])).map(resolvePresetName) ?? [];
24
+ const plugins = config.plugins?.map(plugin => (typeof plugin === 'string' ? plugin : plugin[0])).map(resolvePluginName) ?? [];
25
+ return compact([...presets, ...plugins]);
29
26
  };
30
27
  const findBabelDependencies = async (configFilePath, { manifest }) => {
31
- if (configFilePath.endsWith('package.json')) {
32
- const config = manifest?.babel;
33
- return getDependenciesFromConfig(config);
34
- }
35
- let config = await load(configFilePath);
28
+ let config = configFilePath.endsWith('package.json')
29
+ ? manifest.babel
30
+ : await load(configFilePath);
36
31
  if (typeof config === 'function') {
37
32
  config = config(api);
38
33
  }
39
- return getDependenciesFromConfig(config);
34
+ return config ? getDependenciesFromConfig(config) : [];
40
35
  };
41
36
  export const findDependencies = timerify(findBabelDependencies);
@@ -1,2 +1,4 @@
1
1
  import type { ESLintConfig } from './types.js';
2
+ export declare const resolvePluginPackageName: (pluginName: string) => string;
3
+ export declare const customResolvePluginPackageNames: (extend: string) => string | undefined;
2
4
  export declare const getDependenciesFromSettings: (settings?: ESLintConfig['settings']) => string[];
@@ -1,4 +1,25 @@
1
1
  import { compact } from '../../util/array.js';
2
+ import { getPackageName } from '../../util/modules.js';
3
+ export const resolvePluginPackageName = (pluginName) => {
4
+ return pluginName.startsWith('@')
5
+ ? pluginName.includes('/')
6
+ ? pluginName
7
+ : `${pluginName}/eslint-plugin`
8
+ : `eslint-plugin-${pluginName}`;
9
+ };
10
+ export const customResolvePluginPackageNames = (extend) => {
11
+ if (extend.includes('/node_modules/'))
12
+ return getPackageName(extend);
13
+ if (extend.startsWith('/') || extend.startsWith('.'))
14
+ return;
15
+ if (extend.includes(':')) {
16
+ const pluginName = extend.replace(/^plugin:/, '').replace(/(\/|:).+$/, '');
17
+ if (pluginName === 'eslint')
18
+ return;
19
+ return resolvePluginPackageName(pluginName);
20
+ }
21
+ return extend.includes('eslint') ? getPackageName(extend) : resolvePluginPackageName(extend);
22
+ };
2
23
  const getImportPluginDependencies = (settings) => {
3
24
  const knownKeys = ['typescript'];
4
25
  if (Array.isArray(settings))
@@ -1,4 +1,5 @@
1
1
  import type { IsPluginEnabledCallback, GenericPluginCallback } from '../../types/plugins.js';
2
2
  export declare const isEnabled: IsPluginEnabledCallback;
3
3
  export declare const CONFIG_FILE_PATTERNS: string[];
4
+ export declare const ENTRY_FILE_PATTERNS: string[];
4
5
  export declare const findDependencies: GenericPluginCallback;
@@ -1,39 +1,36 @@
1
- import path from 'node:path';
2
1
  import { ESLint } from 'eslint';
3
2
  import { compact } from '../../util/array.js';
3
+ import { _firstGlob } from '../../util/glob.js';
4
+ import load from '../../util/loader.js';
4
5
  import { getPackageName } from '../../util/modules.js';
5
6
  import { timerify } from '../../util/performance.js';
6
- import { getDependenciesFromSettings } from './helpers.js';
7
- export const isEnabled = ({ dependencies }) => {
8
- return dependencies.has('eslint');
9
- };
10
- export const CONFIG_FILE_PATTERNS = ['eslint.config.js', '.eslintrc', '.eslintrc.json', '.eslintrc.js'];
11
- const SAMPLE_FILE_PATHS = ['__placeholder__.js', '__placeholder__.ts'];
12
- const resolvePluginPackageName = (pluginName) => {
13
- return pluginName.startsWith('@')
14
- ? pluginName.includes('/')
15
- ? pluginName
16
- : `${pluginName}/eslint-plugin`
17
- : `eslint-plugin-${pluginName}`;
18
- };
19
- const findESLintDependencies = async (configFilePath, { cwd, config }) => {
20
- if (path.basename(configFilePath) === 'eslint.config.js') {
7
+ import { resolvePluginPackageName, customResolvePluginPackageNames, getDependenciesFromSettings } from './helpers.js';
8
+ export const isEnabled = ({ dependencies }) => dependencies.has('eslint');
9
+ export const CONFIG_FILE_PATTERNS = ['.eslintrc', '.eslintrc.{js,json}', 'package.json'];
10
+ export const ENTRY_FILE_PATTERNS = ['eslint.config.js'];
11
+ const findESLintDependencies = async (configFilePath, { cwd, manifest, workspaceConfig }) => {
12
+ if (configFilePath.endsWith('package.json') && !manifest.eslintConfig)
21
13
  return [];
22
- }
23
- else {
24
- const engine = new ESLint({ cwd, overrideConfigFile: configFilePath, useEslintrc: false });
25
- const calculateConfigForFile = async (sampleFile) => await engine.calculateConfigForFile(sampleFile);
26
- const sampleFiles = config?.sampleFiles.length > 0 ? config.sampleFiles : SAMPLE_FILE_PATHS;
27
- const dependencies = await Promise.all(sampleFiles.map(calculateConfigForFile)).then(configs => configs.flatMap(config => {
28
- if (!config)
29
- return [];
30
- const plugins = config.plugins?.map(resolvePluginPackageName) ?? [];
31
- const parsers = config.parser ? [config.parser] : [];
32
- const extraParsers = config.parserOptions?.babelOptions?.presets ?? [];
33
- const settings = config.settings ? getDependenciesFromSettings(config.settings) : [];
34
- return [...parsers, ...extraParsers, ...plugins, ...settings].map(getPackageName);
35
- }));
36
- return compact(dependencies.flat());
37
- }
14
+ const config = configFilePath.endsWith('package.json')
15
+ ? manifest.eslintConfig
16
+ : await load(configFilePath);
17
+ const rootExtends = config?.extends ? [config.extends].flat().map(customResolvePluginPackageNames) : [];
18
+ const patterns = [workspaceConfig.entryFiles, ...(config?.overrides?.map(overrides => overrides.files) ?? [])];
19
+ const samples = await Promise.all(patterns.map(patterns => _firstGlob({ patterns, cwd })));
20
+ const sampleFilePaths = samples
21
+ .filter((filePath) => typeof filePath !== 'undefined')
22
+ .map(String);
23
+ const engine = new ESLint({ cwd, overrideConfigFile: configFilePath, useEslintrc: false });
24
+ const calculateConfigForFile = async (sampleFile) => await engine.calculateConfigForFile(sampleFile);
25
+ const dependencies = await Promise.all(sampleFilePaths.map(calculateConfigForFile)).then(configs => configs.flatMap(config => {
26
+ if (!config)
27
+ return [];
28
+ const plugins = config.plugins?.map(resolvePluginPackageName) ?? [];
29
+ const parsers = config.parser ? [config.parser] : [];
30
+ const extraParsers = config.parserOptions?.babelOptions?.presets ?? [];
31
+ const settings = config.settings ? getDependenciesFromSettings(config.settings) : [];
32
+ return [...parsers, ...extraParsers, ...plugins, ...settings].map(getPackageName);
33
+ }));
34
+ return compact([...rootExtends, ...dependencies.flat()]);
38
35
  };
39
36
  export const findDependencies = timerify(findESLintDependencies);
@@ -1,4 +1,5 @@
1
1
  export type ESLintConfig = {
2
+ extends?: string | string[];
2
3
  parser?: string;
3
4
  parserOptions?: {
4
5
  babelOptions?: {
@@ -7,4 +8,7 @@ export type ESLintConfig = {
7
8
  };
8
9
  plugins?: string[];
9
10
  settings?: Record<string, Record<string, unknown>>;
11
+ overrides?: {
12
+ files: string[];
13
+ }[];
10
14
  };
@@ -5,6 +5,7 @@ export * as cypress from './cypress/index.js';
5
5
  export * as eslint from './eslint/index.js';
6
6
  export * as gatsby from './gatsby/index.js';
7
7
  export * as jest from './jest/index.js';
8
+ export * as mocha from './mocha/index.js';
8
9
  export * as next from './next/index.js';
9
10
  export * as nx from './nx/index.js';
10
11
  export * as playwright from './playwright/index.js';
@@ -13,3 +14,4 @@ export * as remark from './remark/index.js';
13
14
  export * as remix from './remix/index.js';
14
15
  export * as rollup from './rollup/index.js';
15
16
  export * as storybook from './storybook/index.js';
17
+ export * as stryker from './stryker/index.js';