knip 5.61.3 → 5.63.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.
Files changed (77) hide show
  1. package/README.md +22 -24
  2. package/dist/ConfigurationChief.d.ts +3 -0
  3. package/dist/ConfigurationChief.js +14 -6
  4. package/dist/IssueCollector.js +1 -1
  5. package/dist/ProjectPrincipal.d.ts +1 -0
  6. package/dist/ProjectPrincipal.js +9 -3
  7. package/dist/binaries/fallback.js +4 -2
  8. package/dist/binaries/package-manager/bun.js +15 -1
  9. package/dist/cli.js +3 -2
  10. package/dist/compilers/index.d.ts +20 -0
  11. package/dist/graph/analyze.js +1 -1
  12. package/dist/graph/build.js +19 -9
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.js +2 -1
  15. package/dist/plugins/angular/index.js +6 -2
  16. package/dist/plugins/astro/index.d.ts +1 -0
  17. package/dist/plugins/astro/index.js +7 -7
  18. package/dist/plugins/astro/resolveFromAST.js +1 -2
  19. package/dist/plugins/biome/index.js +10 -2
  20. package/dist/plugins/eslint/helpers.js +2 -1
  21. package/dist/plugins/index.d.ts +19 -1
  22. package/dist/plugins/index.js +4 -0
  23. package/dist/plugins/lefthook/index.js +2 -0
  24. package/dist/plugins/node-modules-inspector/index.d.ts +12 -0
  25. package/dist/plugins/node-modules-inspector/index.js +17 -0
  26. package/dist/plugins/playwright/index.js +8 -1
  27. package/dist/plugins/playwright/types.d.ts +20 -14
  28. package/dist/plugins/postcss/index.js +1 -1
  29. package/dist/plugins/preconstruct/index.js +2 -1
  30. package/dist/plugins/react-router/index.js +18 -8
  31. package/dist/plugins/rsbuild/index.js +11 -2
  32. package/dist/plugins/rsbuild/types.d.ts +8 -0
  33. package/dist/plugins/rslib/index.d.ts +10 -0
  34. package/dist/plugins/rslib/index.js +15 -0
  35. package/dist/plugins/rslib/types.d.ts +1 -0
  36. package/dist/plugins/rslib/types.js +1 -0
  37. package/dist/plugins/rspack/index.js +1 -1
  38. package/dist/plugins/size-limit/index.js +1 -1
  39. package/dist/plugins/typescript/index.d.ts +1 -1
  40. package/dist/plugins/vitest/index.js +41 -1
  41. package/dist/plugins/vitest/types.d.ts +5 -0
  42. package/dist/reporters/symbols.js +3 -1
  43. package/dist/reporters/util/configuration-hints.d.ts +1 -1
  44. package/dist/reporters/util/configuration-hints.js +55 -19
  45. package/dist/reporters/util/util.d.ts +0 -2
  46. package/dist/reporters/util/util.js +2 -2
  47. package/dist/schema/configuration.d.ts +112 -0
  48. package/dist/schema/plugins.d.ts +46 -0
  49. package/dist/schema/plugins.js +2 -0
  50. package/dist/types/PluginNames.d.ts +2 -2
  51. package/dist/types/PluginNames.js +2 -0
  52. package/dist/types/entries.d.ts +3 -0
  53. package/dist/types/entries.js +1 -0
  54. package/dist/types/issues.d.ts +3 -1
  55. package/dist/types/package-json.d.ts +4 -0
  56. package/dist/types/tsconfig-json.d.ts +14 -0
  57. package/dist/types/tsconfig-json.js +1 -0
  58. package/dist/typescript/ast-helpers.js +2 -2
  59. package/dist/util/fs.d.ts +1 -1
  60. package/dist/util/glob-core.d.ts +1 -1
  61. package/dist/util/glob-core.js +8 -7
  62. package/dist/util/glob.d.ts +1 -0
  63. package/dist/util/glob.js +1 -1
  64. package/dist/util/is-identifier-referenced.js +17 -17
  65. package/dist/util/package-json.d.ts +2 -1
  66. package/dist/util/package-json.js +24 -12
  67. package/dist/util/parse-and-convert-gitignores.js +2 -0
  68. package/dist/util/plugin-config.js +1 -1
  69. package/dist/util/reporter.js +3 -3
  70. package/dist/util/resolve.js +1 -1
  71. package/dist/util/table.js +1 -3
  72. package/dist/version.d.ts +1 -1
  73. package/dist/version.js +1 -1
  74. package/package.json +9 -14
  75. package/schema.json +8 -0
  76. /package/dist/util/{tsconfig-loader.d.ts → load-tsconfig.d.ts} +0 -0
  77. /package/dist/util/{tsconfig-loader.js → load-tsconfig.js} +0 -0
@@ -48,6 +48,7 @@ import { default as nanoStaged } from './nano-staged/index.js';
48
48
  import { default as nest } from './nest/index.js';
49
49
  import { default as netlify } from './netlify/index.js';
50
50
  import { default as next } from './next/index.js';
51
+ import { default as nodeModulesInspector } from './node-modules-inspector/index.js';
51
52
  import { default as node } from './node/index.js';
52
53
  import { default as nodemon } from './nodemon/index.js';
53
54
  import { default as npmPackageJsonLint } from './npm-package-json-lint/index.js';
@@ -72,6 +73,7 @@ import { default as remark } from './remark/index.js';
72
73
  import { default as remix } from './remix/index.js';
73
74
  import { default as rollup } from './rollup/index.js';
74
75
  import { default as rsbuild } from './rsbuild/index.js';
76
+ import { default as rslib } from './rslib/index.js';
75
77
  import { default as rspack } from './rspack/index.js';
76
78
  import { default as semanticRelease } from './semantic-release/index.js';
77
79
  import { default as sentry } from './sentry/index.js';
@@ -159,6 +161,7 @@ export const Plugins = {
159
161
  netlify,
160
162
  next,
161
163
  node,
164
+ 'node-modules-inspector': nodeModulesInspector,
162
165
  nodemon,
163
166
  'npm-package-json-lint': npmPackageJsonLint,
164
167
  nuxt,
@@ -182,6 +185,7 @@ export const Plugins = {
182
185
  remix,
183
186
  rollup,
184
187
  rsbuild,
188
+ rslib,
185
189
  rspack,
186
190
  'semantic-release': semanticRelease,
187
191
  sentry,
@@ -9,6 +9,8 @@ const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
9
9
  const gitHookPaths = getGitHookPaths();
10
10
  const config = ['lefthook.yml', ...gitHookPaths];
11
11
  const resolveConfig = async (localConfig, options) => {
12
+ if (options.isProduction)
13
+ return [];
12
14
  const { manifest, configFileName, cwd, getInputsFromScripts } = options;
13
15
  const inputs = manifest.devDependencies ? Object.keys(manifest.devDependencies).map(id => toDependency(id)) : [];
14
16
  if (extname(configFileName) === '.yml') {
@@ -0,0 +1,12 @@
1
+ import type { IsPluginEnabled } from '../../types/config.js';
2
+ declare const _default: {
3
+ title: string;
4
+ enablers: string[];
5
+ isEnabled: IsPluginEnabled;
6
+ config: string[];
7
+ args: {
8
+ binaries: string[];
9
+ config: boolean;
10
+ };
11
+ };
12
+ export default _default;
@@ -0,0 +1,17 @@
1
+ import { toUnconfig } from '../../util/plugin-config.js';
2
+ import { hasDependency } from '../../util/plugin.js';
3
+ const title = 'node-modules-inspector';
4
+ const enablers = ['node-modules-inspector'];
5
+ const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
6
+ const config = [...toUnconfig('node-modules-inspector.config')];
7
+ const args = {
8
+ binaries: ['node-modules-inspector'],
9
+ config: true,
10
+ };
11
+ export default {
12
+ title,
13
+ enablers,
14
+ isEnabled,
15
+ config,
16
+ args,
17
+ };
@@ -1,3 +1,4 @@
1
+ import { arrayify } from '../../util/array.js';
1
2
  import { toDeferResolve, toEntry } from '../../util/input.js';
2
3
  import { join, relative } from '../../util/path.js';
3
4
  import { hasDependency } from '../../util/plugin.js';
@@ -15,6 +16,11 @@ const toEntryPatterns = (testMatch, cwd, configDir, localConfig, rootConfig) =>
15
16
  const builtinReporters = ['dot', 'line', 'list', 'junit', 'html', 'blob', 'json', 'github'];
16
17
  export const resolveConfig = async (localConfig, options) => {
17
18
  const { cwd, configFileDir } = options;
19
+ const inputs = [];
20
+ for (const id of arrayify(localConfig.globalSetup))
21
+ inputs.push(toEntry(id));
22
+ for (const id of arrayify(localConfig.globalTeardown))
23
+ inputs.push(toEntry(id));
18
24
  const projects = localConfig.projects ? [localConfig, ...localConfig.projects] : [localConfig];
19
25
  const reporters = [localConfig.reporter].flat().flatMap(reporter => {
20
26
  const name = typeof reporter === 'string' ? reporter : reporter?.[0];
@@ -24,7 +30,8 @@ export const resolveConfig = async (localConfig, options) => {
24
30
  });
25
31
  return projects
26
32
  .flatMap(config => toEntryPatterns(config.testMatch ?? entry, cwd, configFileDir, config, localConfig))
27
- .concat(reporters.map(id => toDeferResolve(id)));
33
+ .concat(reporters.map(id => toDeferResolve(id)))
34
+ .concat(inputs);
28
35
  };
29
36
  const args = {
30
37
  binaries: ['playwright'],
@@ -1,27 +1,31 @@
1
1
  type LiteralUnion<T extends U, U = string> = T | (U & {
2
2
  zz_IGNORE_ME?: never;
3
3
  });
4
- type ReporterDescription = Readonly<['blob'] | ['blob', {
4
+ type BlobReporterOptions = {
5
5
  outputDir?: string;
6
6
  fileName?: string;
7
- }] | ['dot'] | ['line'] | ['list'] | ['list', {
7
+ };
8
+ type ListReporterOptions = {
8
9
  printSteps?: boolean;
9
- }] | ['github'] | ['junit'] | ['junit', {
10
+ };
11
+ type JUnitReporterOptions = {
10
12
  outputFile?: string;
11
13
  stripANSIControlSequences?: boolean;
12
14
  includeProjectInTestName?: boolean;
13
- }] | ['json'] | ['json', {
15
+ };
16
+ type JsonReporterOptions = {
14
17
  outputFile?: string;
15
- }] | ['html'] | [
16
- 'html',
17
- {
18
- outputFolder?: string;
19
- open?: 'always' | 'never' | 'on-failure';
20
- host?: string;
21
- port?: number;
22
- attachmentsBaseURL?: string;
23
- }
24
- ] | ['null'] | [string] | [string, any]>;
18
+ };
19
+ type HtmlReporterOptions = {
20
+ outputFolder?: string;
21
+ open?: 'always' | 'never' | 'on-failure';
22
+ host?: string;
23
+ port?: number;
24
+ attachmentsBaseURL?: string;
25
+ title?: string;
26
+ noSnippets?: boolean;
27
+ };
28
+ type ReporterDescription = Readonly<['blob'] | ['blob', BlobReporterOptions] | ['dot'] | ['line'] | ['list'] | ['list', ListReporterOptions] | ['github'] | ['junit'] | ['junit', JUnitReporterOptions] | ['json'] | ['json', JsonReporterOptions] | ['html'] | ['html', HtmlReporterOptions] | ['null'] | [string] | [string, any]>;
25
29
  type Project = {
26
30
  name: string;
27
31
  use: string;
@@ -32,5 +36,7 @@ export type PlaywrightTestConfig = {
32
36
  testMatch?: string | RegExp | (string | RegExp)[];
33
37
  testDir?: string;
34
38
  reporter?: LiteralUnion<'dot' | 'line' | 'list' | 'junit' | 'html' | 'json' | 'github' | 'null', string> | ReporterDescription[];
39
+ globalSetup?: string | Array<string>;
40
+ globalTeardown?: string | Array<string>;
35
41
  };
36
42
  export {};
@@ -7,7 +7,7 @@ const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
7
7
  const config = [
8
8
  'package.json',
9
9
  'postcss.config.json',
10
- ...toLilconfig('postcss', { configDir: false, additionalExtensions: ['ts', 'mts', 'cts', 'yaml', 'yml'] }),
10
+ ...toLilconfig('postcss', { configDir: false, additionalExtensions: ['mts', 'cts', 'yaml', 'yml'] }),
11
11
  ];
12
12
  const resolveConfig = config => {
13
13
  const plugins = config.plugins
@@ -1,11 +1,12 @@
1
1
  import { toProductionEntry } from '../../util/input.js';
2
+ import { join } from '../../util/path.js';
2
3
  import { hasDependency } from '../../util/plugin.js';
3
4
  const title = 'Preconstruct';
4
5
  const enablers = ['@preconstruct/cli'];
5
6
  const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
6
7
  const config = ['package.json'];
7
8
  const resolveConfig = async (config) => {
8
- return (config.entrypoints ?? []).map(id => toProductionEntry(id, { allowIncludeExports: true }));
9
+ return (config.entrypoints ?? []).map(id => toProductionEntry(join('src', id), { allowIncludeExports: true }));
9
10
  };
10
11
  export default {
11
12
  title,
@@ -1,6 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import os from 'node:os';
3
- import { toEntry } from '../../util/input.js';
3
+ import { _glob } from '../../util/glob.js';
4
+ import { toEntry, toProductionDependency, toProductionEntry } from '../../util/input.js';
4
5
  import { join } from '../../util/path.js';
5
6
  import { hasDependency, load } from '../../util/plugin.js';
6
7
  import vite from '../vite/index.js';
@@ -28,13 +29,22 @@ const resolveConfig = async (localConfig, options) => {
28
29
  };
29
30
  const routes = routeConfig
30
31
  .flatMap(mapRoute)
31
- .map(route => (isWindows ? route : route.replace(/[$^*+?()\[\]]/g, '\\$&')));
32
- return [
33
- join(appDir, 'routes.{js,ts}'),
34
- join(appDir, 'root.{jsx,tsx}'),
35
- join(appDir, 'entry.{client,server}.{js,jsx,ts,tsx}'),
36
- ...routes,
37
- ].map(id => toEntry(id));
32
+ .map(route => (isWindows ? route : route.replace(/[\^*+?()\[\]]/g, '\\$&')));
33
+ const resolved = [
34
+ toEntry(join(appDir, 'routes.{js,ts}')),
35
+ toProductionEntry(join(appDir, 'root.{jsx,tsx}')),
36
+ toProductionEntry(join(appDir, 'entry.{client,server}.{js,jsx,ts,tsx}')),
37
+ ...routes.map(id => toProductionEntry(id)),
38
+ ];
39
+ const serverEntries = await _glob({
40
+ cwd: appDir,
41
+ patterns: ['entry.server.{js,ts,jsx,tsx}'],
42
+ });
43
+ if (serverEntries.length === 0) {
44
+ resolved.push(toProductionDependency('@react-router/node'));
45
+ resolved.push(toProductionDependency('isbot'));
46
+ }
47
+ return resolved;
38
48
  };
39
49
  export default {
40
50
  title,
@@ -1,10 +1,19 @@
1
+ import { toEntry } from '../../util/input.js';
1
2
  import { hasDependency } from '../../util/plugin.js';
2
3
  const title = 'Rsbuild';
3
4
  const enablers = ['@rsbuild/core'];
4
5
  const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
5
6
  const config = ['rsbuild*.config.{mjs,ts,js,cjs,mts,cts}'];
6
- const resolveConfig = async () => {
7
- return [];
7
+ const resolveConfig = async (config) => {
8
+ const inputs = new Set();
9
+ if (config.source?.entry) {
10
+ if (Array.isArray(config.source.entry))
11
+ for (const entry of config.source.entry)
12
+ inputs.add(toEntry(entry));
13
+ if (typeof config.source.entry === 'string')
14
+ inputs.add(toEntry(config.source.entry));
15
+ }
16
+ return Array.from(inputs);
8
17
  };
9
18
  export default {
10
19
  title,
@@ -1,3 +1,11 @@
1
+ type EntryDescription = Record<string, unknown>;
2
+ type Entry = Record<string, string | string[] | (EntryDescription & {
3
+ html?: boolean;
4
+ })>;
1
5
  export type RsbuildConfig = {
2
6
  plugins?: unknown[];
7
+ source?: {
8
+ entry?: Entry;
9
+ };
3
10
  };
11
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { IsPluginEnabled, ResolveConfig } from '../../types/config.js';
2
+ import type { RslibConfig } from './types.js';
3
+ declare const _default: {
4
+ title: string;
5
+ enablers: string[];
6
+ isEnabled: IsPluginEnabled;
7
+ entry: string[];
8
+ resolveConfig: ResolveConfig<RslibConfig>;
9
+ };
10
+ export default _default;
@@ -0,0 +1,15 @@
1
+ import { hasDependency } from '../../util/plugin.js';
2
+ const title = 'Rslib';
3
+ const enablers = ['rslib'];
4
+ const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
5
+ const entry = ['rslib*.config.{mjs,ts,js,cjs,mts,cts}'];
6
+ const resolveConfig = () => {
7
+ return [];
8
+ };
9
+ export default {
10
+ title,
11
+ enablers,
12
+ isEnabled,
13
+ entry,
14
+ resolveConfig,
15
+ };
@@ -0,0 +1 @@
1
+ export type RslibConfig = {};
@@ -0,0 +1 @@
1
+ export {};
@@ -3,7 +3,7 @@ import { findWebpackDependenciesFromConfig } from '../webpack/index.js';
3
3
  const title = 'Rspack';
4
4
  const enablers = ['@rspack/core'];
5
5
  const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
6
- const config = ['rspack.config*.{js,ts,mjs,cjs}'];
6
+ const config = ['rspack.config*.{js,ts,mjs,mts,cjs,cts}'];
7
7
  const resolveConfig = async (localConfig, options) => {
8
8
  const inputs = await findWebpackDependenciesFromConfig(localConfig, options);
9
9
  return inputs.filter(input => !input.specifier.startsWith('builtin:'));
@@ -6,7 +6,7 @@ const enablers = ['size-limit'];
6
6
  const isEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
7
7
  const config = [
8
8
  'package.json',
9
- ...toLilconfig('size-limit', { configDir: false, additionalExtensions: ['ts', 'mts', 'cts'], rcSuffix: '' }),
9
+ ...toLilconfig('size-limit', { configDir: false, additionalExtensions: ['mts', 'cts'], rcSuffix: '' }),
10
10
  ];
11
11
  const resolve = options => {
12
12
  const allDeps = [
@@ -1,5 +1,5 @@
1
- import type { TsConfigJson } from 'type-fest';
2
1
  import type { IsPluginEnabled, ResolveConfig } from '../../types/config.js';
2
+ import type { TsConfigJson } from '../../types/tsconfig-json.js';
3
3
  export declare const docs: {
4
4
  note: string;
5
5
  };
@@ -1,5 +1,6 @@
1
1
  import { DEFAULT_EXTENSIONS } from '../../constants.js';
2
- import { toAlias, toDeferResolve, toDependency, toEntry } from '../../util/input.js';
2
+ import { _glob } from '../../util/glob.js';
3
+ import { toAlias, toConfig, toDeferResolve, toDependency, toEntry } from '../../util/input.js';
3
4
  import { join, toPosix } from '../../util/path.js';
4
5
  import { hasDependency } from '../../util/plugin.js';
5
6
  import { getEnvPackageName, getExternalReporters } from './helpers.js';
@@ -28,11 +29,20 @@ const findConfigDependencies = (localConfig, options) => {
28
29
  workspaceDependencies.push(...findConfigDependencies(workspaceConfig, options));
29
30
  }
30
31
  }
32
+ const projectsDependencies = [];
33
+ if (testConfig.projects !== undefined) {
34
+ for (const projectConfig of testConfig.projects) {
35
+ if (typeof projectConfig !== 'string') {
36
+ projectsDependencies.push(...findConfigDependencies(projectConfig, options));
37
+ }
38
+ }
39
+ }
31
40
  return [
32
41
  ...[...environments, ...reporters, ...coverage].map(id => toDependency(id)),
33
42
  ...setupFiles,
34
43
  ...globalSetup,
35
44
  ...workspaceDependencies,
45
+ ...projectsDependencies,
36
46
  ];
37
47
  };
38
48
  const getConfigs = async (localConfig) => {
@@ -44,11 +54,25 @@ const getConfigs = async (localConfig) => {
44
54
  for (const mode of ['development', 'production']) {
45
55
  const cfg = await config({ command, mode, ssrBuild: undefined });
46
56
  configs.push(cfg);
57
+ if (cfg.test?.projects) {
58
+ for (const project of cfg.test.projects) {
59
+ if (typeof project !== 'string') {
60
+ configs.push(project);
61
+ }
62
+ }
63
+ }
47
64
  }
48
65
  }
49
66
  }
50
67
  else {
51
68
  configs.push(config);
69
+ if (config.test?.projects) {
70
+ for (const project of config.test.projects) {
71
+ if (typeof project !== 'string') {
72
+ configs.push(project);
73
+ }
74
+ }
75
+ }
52
76
  }
53
77
  }
54
78
  }
@@ -58,6 +82,22 @@ export const resolveConfig = async (localConfig, options) => {
58
82
  const inputs = new Set();
59
83
  inputs.add(toEntry(join(options.cwd, 'src/vite-env.d.ts')));
60
84
  const configs = await getConfigs(localConfig);
85
+ for (const cfg of configs) {
86
+ if (cfg.test?.projects) {
87
+ for (const project of cfg.test.projects) {
88
+ if (typeof project === 'string') {
89
+ const projectFiles = await _glob({
90
+ cwd: options.cwd,
91
+ patterns: [project],
92
+ gitignore: false,
93
+ });
94
+ for (const projectFile of projectFiles) {
95
+ inputs.add(toConfig('vitest', projectFile, { containingFilePath: options.configFilePath }));
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
61
101
  const addStar = (value) => (value.endsWith('*') ? value : join(value, '*').replace(/\/\*\*$/, '/*'));
62
102
  const addAliases = (aliasOptions) => {
63
103
  for (const [alias, value] of Object.entries(aliasOptions)) {
@@ -23,6 +23,11 @@ interface VitestConfig {
23
23
  workspace: never;
24
24
  };
25
25
  })[];
26
+ projects?: (string | (ViteConfig & {
27
+ test: VitestConfig['test'] & {
28
+ projects: never;
29
+ };
30
+ }))[];
26
31
  alias?: AliasOptions;
27
32
  };
28
33
  }
@@ -20,7 +20,9 @@ export default (options) => {
20
20
  if (!isDisableConfigHints) {
21
21
  printConfigurationHints(options);
22
22
  }
23
- if (totalIssues === 0 && isShowProgress) {
23
+ if (totalIssues === 0 &&
24
+ isShowProgress &&
25
+ (!options.isTreatConfigHintsAsErrors || options.configurationHints.size === 0)) {
24
26
  console.log('✂️ Excellent, Knip found no issues.');
25
27
  }
26
28
  };
@@ -1,2 +1,2 @@
1
1
  import type { ReporterOptions } from '../../types/issues.js';
2
- export declare const printConfigurationHints: ({ counters, issues, tagHints, configurationHints, isTreatConfigHintsAsErrors, includedWorkspaces, }: ReporterOptions) => void;
2
+ export declare const printConfigurationHints: ({ counters, issues, tagHints, configurationHints, isTreatConfigHintsAsErrors, includedWorkspaces, configFilePath, }: ReporterOptions) => void;
@@ -1,14 +1,33 @@
1
- import { toRelative } from '../../util/path.js';
1
+ import { relative, toRelative } from '../../util/path.js';
2
+ import { Table } from '../../util/table.js';
2
3
  import { byPathDepth } from '../../util/workspace.js';
3
- import { bright, dim, getColoredTitle, getDimmedTitle, plain, yellow } from './util.js';
4
- const id = (id) => bright(id.toString() + (id === '.' ? ' (root)' : ''));
5
- const type = (id) => yellow(id.split('-').at(0));
6
- const workspace = ({ isRootOnly, workspaceName: id }) => isRootOnly ? '' : id === '.' ? ` in root ${yellow('"."')} workspace` : ` in ${yellow(id ?? '.')}`;
7
- const unused = (options) => `Remove from ${type(options.type)}${options.workspaceName === '.' ? '' : `${workspace(options)}`}: ${id(options.identifier)}`;
8
- const empty = (options) => `Refine ${type(options.type)}${workspace(options)}: ${id(options.identifier)} (no files found)`;
9
- const remove = (options) => `Remove ${type(options.type)}${workspace(options)}: ${id(options.identifier)}`;
10
- const add = (options) => `Add to or refine in ${yellow('workspaces')}: ${id(options.identifier)} (${options.size} unused files)`;
11
- const topLevel = (options) => `Remove or move unused top-level ${type(options.type)} to one of ${yellow('workspaces')}: ${id(options.identifier)}`;
4
+ import { bright, dim, getColoredTitle, getDimmedTitle } from './util.js';
5
+ const getWorkspaceName = (hint) => hint.workspaceName &&
6
+ hint.workspaceName !== '.' &&
7
+ hint.type !== 'workspace-unconfigured' &&
8
+ hint.type !== 'package-entry'
9
+ ? hint.workspaceName
10
+ : '';
11
+ const getTableForHints = (hints) => {
12
+ const table = new Table({ truncateStart: ['identifier', 'workspace', 'filePath'] });
13
+ for (const hint of hints) {
14
+ table.row();
15
+ table.cell('identifier', hint.identifier.toString());
16
+ table.cell('workspace', getWorkspaceName(hint));
17
+ table.cell('filePath', hint.filePath ? relative(hint.filePath) : '');
18
+ table.cell('description', dim(hint.message));
19
+ }
20
+ return table;
21
+ };
22
+ const type = (id) => bright(id.split('-').at(0));
23
+ const unused = (options) => `Remove from ${type(options.type)}`;
24
+ const empty = (options) => `Refine ${type(options.type)} pattern (no matches)`;
25
+ const remove = (options) => `Remove redundant ${type(options.type)} pattern`;
26
+ const topLevel = (options) => `Remove, or move unused top-level ${type(options.type)} to one of ${bright('"workspaces"')}`;
27
+ const add = (options) => options.configFilePath
28
+ ? `Add ${bright('entry')} and/or refine ${bright('project')} files in ${bright(`workspaces["${options.workspaceName}"]`)} (${options.size} unused files)`
29
+ : `Create ${bright('knip.json')} configuration file with ${bright(`workspaces["${options.workspaceName}"]`)} object (${options.size} unused files)`;
30
+ const packageEntry = () => 'Package entry file not found';
12
31
  const hintPrinters = new Map([
13
32
  ['ignoreBinaries', { print: unused }],
14
33
  ['ignoreDependencies', { print: unused }],
@@ -21,8 +40,19 @@ const hintPrinters = new Map([
21
40
  ['workspace-unconfigured', { print: add }],
22
41
  ['entry-top-level', { print: topLevel }],
23
42
  ['project-top-level', { print: topLevel }],
43
+ ['package-entry', { print: packageEntry }],
24
44
  ]);
25
- export const printConfigurationHints = ({ counters, issues, tagHints, configurationHints, isTreatConfigHintsAsErrors, includedWorkspaces, }) => {
45
+ const hintTypesOrder = [
46
+ ['workspace-unconfigured'],
47
+ ['entry-top-level', 'project-top-level'],
48
+ ['ignoreWorkspaces'],
49
+ ['ignoreDependencies'],
50
+ ['ignoreBinaries'],
51
+ ['ignoreUnresolved'],
52
+ ['entry-empty', 'project-empty', 'entry-redundant', 'project-redundant'],
53
+ ['package-entry'],
54
+ ];
55
+ export const printConfigurationHints = ({ counters, issues, tagHints, configurationHints, isTreatConfigHintsAsErrors, includedWorkspaces, configFilePath, }) => {
26
56
  if (counters.files > 20) {
27
57
  const workspaces = includedWorkspaces
28
58
  .map(workspace => workspace.dir)
@@ -41,17 +71,23 @@ export const printConfigurationHints = ({ counters, issues, tagHints, configurat
41
71
  }
42
72
  }
43
73
  if (configurationHints.size > 0) {
44
- const isTopLevel = (type) => type.includes('top-level');
45
- const hintOrderer = (a, b) => isTopLevel(a.type) && !isTopLevel(b.type) ? -1 : !isTopLevel(a.type) && isTopLevel(b.type) ? 1 : 0;
46
74
  const getTitle = isTreatConfigHintsAsErrors ? getColoredTitle : getDimmedTitle;
47
- const style = isTreatConfigHintsAsErrors ? plain : dim;
48
75
  console.log(getTitle('Configuration hints', configurationHints.size));
49
- const isRootOnly = includedWorkspaces.length === 1 && includedWorkspaces[0].name === '.';
50
- for (const hint of Array.from(configurationHints).sort(hintOrderer)) {
51
- const hintPrinter = hintPrinters.get(hint.type);
52
- if (hintPrinter)
53
- console.warn(style(hintPrinter.print({ ...hint, isRootOnly })));
76
+ const hintsByType = new Map();
77
+ for (const hint of configurationHints) {
78
+ const hints = hintsByType.get(hint.type) ?? [];
79
+ hintsByType.set(hint.type, [...hints, hint]);
54
80
  }
81
+ const rows = hintTypesOrder.flatMap(hintTypes => hintTypes.flatMap(hintType => {
82
+ const hints = hintsByType.get(hintType) ?? [];
83
+ return hints.map(hint => {
84
+ hint.filePath ??= configFilePath;
85
+ const hintPrinter = hintPrinters.get(hint.type);
86
+ const message = hintPrinter ? hintPrinter.print({ ...hint, configFilePath }) : '';
87
+ return { ...hint, message };
88
+ });
89
+ }));
90
+ console.warn(getTableForHints(rows).toString());
55
91
  }
56
92
  if (tagHints.size > 0) {
57
93
  console.log(getColoredTitle('Tag issues', tagHints.size));
@@ -1,10 +1,8 @@
1
1
  import { ISSUE_TYPE_TITLE } from '../../constants.js';
2
2
  import { type Issue, type IssueSeverity, type IssueSymbol } from '../../types/issues.js';
3
3
  import { Table } from '../../util/table.js';
4
- export declare const plain: (text: string) => string;
5
4
  export declare const dim: import("picocolors/types.js").Formatter;
6
5
  export declare const bright: import("picocolors/types.js").Formatter;
7
- export declare const yellow: import("picocolors/types.js").Formatter;
8
6
  export declare const getIssueTypeTitle: (reportType: keyof typeof ISSUE_TYPE_TITLE) => string;
9
7
  export declare const getColoredTitle: (title: string, count: number) => string;
10
8
  export declare const getDimmedTitle: (title: string, count: number) => string;
@@ -3,10 +3,10 @@ import { ISSUE_TYPE_TITLE } from '../../constants.js';
3
3
  import { SymbolType } from '../../types/issues.js';
4
4
  import { relative } from '../../util/path.js';
5
5
  import { Table } from '../../util/table.js';
6
- export const plain = (text) => text;
6
+ const plain = (text) => text;
7
7
  export const dim = picocolors.gray;
8
8
  export const bright = picocolors.whiteBright;
9
- export const yellow = picocolors.yellow;
9
+ const yellow = picocolors.yellow;
10
10
  export const getIssueTypeTitle = (reportType) => ISSUE_TYPE_TITLE[reportType];
11
11
  export const getColoredTitle = (title, count) => `${picocolors.yellowBright(picocolors.underline(title))} (${count})`;
12
12
  export const getDimmedTitle = (title, count) => `${yellow(`${picocolors.underline(title)} (${count})`)}`;