knip 6.33.0 → 6.35.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 (90) hide show
  1. package/dist/CatalogCounselor.d.ts +2 -0
  2. package/dist/CatalogCounselor.js +30 -7
  3. package/dist/ConfigurationChief.d.ts +6 -0
  4. package/dist/ConfigurationChief.js +3 -5
  5. package/dist/ProjectPrincipal.d.ts +12 -10
  6. package/dist/ProjectPrincipal.js +56 -41
  7. package/dist/WorkspaceWorker.d.ts +3 -3
  8. package/dist/WorkspaceWorker.js +6 -6
  9. package/dist/YamlCatalogPeeker.d.ts +4 -0
  10. package/dist/YamlCatalogPeeker.js +17 -0
  11. package/dist/binaries/bash-parser.d.ts +1 -1
  12. package/dist/binaries/bash-parser.js +12 -58
  13. package/dist/binaries/command.d.ts +5 -0
  14. package/dist/binaries/command.js +47 -0
  15. package/dist/binaries/fallback.d.ts +2 -0
  16. package/dist/binaries/fallback.js +2 -0
  17. package/dist/binaries/plugins.d.ts +1 -0
  18. package/dist/binaries/plugins.js +2 -1
  19. package/dist/binaries/resolvers/pnpm.js +1 -0
  20. package/dist/binaries/util.d.ts +1 -0
  21. package/dist/binaries/util.js +12 -0
  22. package/dist/cli.js +9 -27
  23. package/dist/compilers/index.d.ts +3 -1866
  24. package/dist/compilers/index.js +17 -27
  25. package/dist/compilers/types.d.ts +4 -5
  26. package/dist/graph/analyze.js +74 -56
  27. package/dist/graph/build.d.ts +3 -3
  28. package/dist/graph/build.js +64 -32
  29. package/dist/plugins/_vue/auto-import.js +28 -4
  30. package/dist/plugins/index.d.ts +1 -0
  31. package/dist/plugins/index.js +2 -0
  32. package/dist/plugins/mise/index.d.ts +3 -0
  33. package/dist/plugins/mise/index.js +82 -0
  34. package/dist/plugins/mise/scripts.d.ts +7 -0
  35. package/dist/plugins/mise/scripts.js +108 -0
  36. package/dist/plugins/mise/types.d.ts +26 -0
  37. package/dist/plugins/mise/types.js +1 -0
  38. package/dist/plugins/node/index.js +5 -0
  39. package/dist/plugins/node/visitors/fsPromisesGlob.d.ts +2 -0
  40. package/dist/plugins/node/visitors/fsPromisesGlob.js +89 -0
  41. package/dist/plugins/nuxt/index.js +13 -10
  42. package/dist/plugins/rstest/index.js +3 -2
  43. package/dist/plugins/rstest/types.d.ts +3 -1
  44. package/dist/plugins/vite/helpers.d.ts +2 -2
  45. package/dist/plugins/vite/helpers.js +18 -11
  46. package/dist/plugins/vite/visitors/importMetaGlob.js +12 -0
  47. package/dist/plugins/vitest/index.js +5 -2
  48. package/dist/plugins/vitest/types.d.ts +1 -0
  49. package/dist/run.js +1 -1
  50. package/dist/schema/configuration.d.ts +20 -4
  51. package/dist/schema/configuration.js +5 -6
  52. package/dist/schema/plugins.d.ts +5 -0
  53. package/dist/schema/plugins.js +1 -0
  54. package/dist/session/index.d.ts +1 -1
  55. package/dist/session/index.js +1 -1
  56. package/dist/session/session.js +22 -4
  57. package/dist/types/PluginNames.d.ts +2 -2
  58. package/dist/types/PluginNames.js +1 -0
  59. package/dist/types/config.d.ts +5 -4
  60. package/dist/types/module-graph.d.ts +4 -0
  61. package/dist/types.d.ts +1 -0
  62. package/dist/typescript/SourceFileManager.d.ts +13 -9
  63. package/dist/typescript/SourceFileManager.js +58 -22
  64. package/dist/typescript/get-imports-and-exports.js +5 -1
  65. package/dist/typescript/glob-imports.js +8 -3
  66. package/dist/typescript/visitors/calls.js +1 -1
  67. package/dist/typescript/visitors/exports.js +8 -4
  68. package/dist/typescript/visitors/walk.d.ts +3 -1
  69. package/dist/typescript/visitors/walk.js +78 -13
  70. package/dist/util/catalog.d.ts +5 -0
  71. package/dist/util/catalog.js +37 -17
  72. package/dist/util/cli-arguments.d.ts +1 -0
  73. package/dist/util/cli-arguments.js +9 -0
  74. package/dist/util/create-options.d.ts +19 -4
  75. package/dist/util/create-options.js +17 -4
  76. package/dist/util/errors.d.ts +3 -1
  77. package/dist/util/errors.js +6 -1
  78. package/dist/util/file-entry-cache.js +2 -2
  79. package/dist/util/module-graph.js +1 -0
  80. package/dist/util/package-json.d.ts +1 -0
  81. package/dist/util/preprocessor.d.ts +6 -0
  82. package/dist/util/preprocessor.js +24 -0
  83. package/dist/util/reporter.d.ts +0 -1
  84. package/dist/util/reporter.js +0 -7
  85. package/dist/util/watch.d.ts +1 -1
  86. package/dist/util/watch.js +33 -15
  87. package/dist/version.d.ts +1 -1
  88. package/dist/version.js +1 -1
  89. package/package.json +8 -8
  90. package/schema.json +12 -0
@@ -27,7 +27,7 @@ export const getCatalogContainer = async (cwd, manifest, manifestPath, pnpmWorks
27
27
  yarnWorkspace?.catalogs ??
28
28
  manifest.catalogs ??
29
29
  ((!Array.isArray(manifest.workspaces) && manifest.workspaces?.catalogs) || {});
30
- return { filePath, catalog, catalogs };
30
+ return { filePath, catalog, catalogs, overrides: pnpmWorkspace?.overrides };
31
31
  };
32
32
  const extractEntries = (catalog) => {
33
33
  if (catalog && typeof catalog === 'object')
@@ -56,23 +56,43 @@ export const parseCatalog = (container) => {
56
56
  entries.add(id);
57
57
  return entries;
58
58
  };
59
+ const OVERRIDE_DELIMITER = /[^ |@]>/;
60
+ const toCatalogReference = (packageName, version) => {
61
+ if (typeof version !== 'string' || !version.startsWith('catalog:'))
62
+ return;
63
+ return { catalogName: version.slice('catalog:'.length) || DEFAULT_CATALOG, packageName };
64
+ };
65
+ const collectCatalogReferences = (dependencies, catalogReferences) => {
66
+ if (!dependencies)
67
+ return;
68
+ for (const [packageName, version] of Object.entries(dependencies)) {
69
+ const reference = toCatalogReference(packageName, version);
70
+ if (reference)
71
+ catalogReferences.push(reference);
72
+ }
73
+ };
74
+ export const getOverrideCatalogReferences = (overrides) => {
75
+ const catalogReferences = [];
76
+ if (!overrides)
77
+ return catalogReferences;
78
+ for (const [selector, version] of Object.entries(overrides)) {
79
+ const delimiterIndex = selector.search(OVERRIDE_DELIMITER);
80
+ const target = delimiterIndex === -1 ? selector : selector.slice(delimiterIndex + 2);
81
+ const packageName = getPackageNameFromModuleSpecifier(target);
82
+ if (!packageName)
83
+ continue;
84
+ const reference = toCatalogReference(packageName, version);
85
+ if (reference)
86
+ catalogReferences.push({ ...reference, selector });
87
+ }
88
+ return catalogReferences;
89
+ };
59
90
  export const extractCatalogReferences = (manifest) => {
60
91
  const catalogReferences = [];
61
- const checkDependencies = (dependencies) => {
62
- if (!dependencies)
63
- return;
64
- for (const [name, version] of Object.entries(dependencies)) {
65
- if (typeof version === 'string' && version.startsWith('catalog:')) {
66
- const catalogName = version.slice('catalog:'.length) || DEFAULT_CATALOG;
67
- catalogReferences.push({ catalogName, packageName: name });
68
- }
69
- }
70
- };
71
- checkDependencies(manifest.dependencies);
72
- checkDependencies(manifest.devDependencies);
73
- checkDependencies(manifest.peerDependencies);
74
- checkDependencies(manifest.optionalDependencies);
75
- checkDependencies(manifest.resolutions);
76
- checkDependencies(manifest.pnpm?.overrides);
92
+ collectCatalogReferences(manifest.dependencies, catalogReferences);
93
+ collectCatalogReferences(manifest.devDependencies, catalogReferences);
94
+ collectCatalogReferences(manifest.peerDependencies, catalogReferences);
95
+ collectCatalogReferences(manifest.optionalDependencies, catalogReferences);
96
+ collectCatalogReferences(manifest.resolutions, catalogReferences);
77
97
  return catalogReferences;
78
98
  };
@@ -1,5 +1,6 @@
1
1
  export declare const helpText = "\u2702\uFE0F Find unused dependencies, exports and files in your JavaScript and TypeScript projects\n\nUsage: knip [options]\n\nOptions:\n -h, --help Print this help text\n -V, --version Print version\n -n, --no-progress Don't show dynamic progress updates (automatically enabled in CI environments)\n -c, --config [file] Configuration file path\n (default: [.]knip.json[c], knip.(js|ts), knip.config.(js|ts) or package.json#knip)\n --use-tsconfig-files Use tsconfig.json to define project files (override `project` patterns)\n -t, --tsConfig [file] TypeScript configuration path (default: tsconfig.json)\n\nMode\n --cache Enable caching\n --cache-location Change cache location (default: node_modules/.cache/knip)\n --include-entry-exports Include entry files when reporting unused exports\n --no-gitignore Don't respect .gitignore\n -p, --production Analyze only production source files (e.g. no test files, devDependencies)\n -s, --strict Consider only direct dependencies of workspace (not devDependencies, not other workspaces)\n -w, --watch Watch mode\n\nScope\n -W, --workspace [filter] Filter workspaces by name, directory, or glob (can be repeated)\n -D, --directory [dir] Run process from a different directory (default: cwd)\n --include Include provided issue type(s), can be comma-separated or repeated (1)\n --exclude Exclude provided issue type(s) from report, can be comma-separated or repeated (1)\n --dependencies Shortcut for --include dependencies,unlisted,binaries,unresolved,catalog,catalogReferences\n --exports Shortcut for --include exports,nsExports,types,nsTypes,enumMembers,namespaceMembers,duplicates\n --files Shortcut for --include files\n --cycles Shortcut for --include cycles (circular dependencies)\n --tags Include or exclude tagged exports\n\nFix\n -f, --fix Fix issues (modifies files in your repo)\n --fix-type Fix only issues of type, can be comma-separated or repeated (2)\n --allow-remove-files Allow Knip to remove files (with --fix)\n -F, --format Format modified files after --fix using the local formatter\n\nOutput\n --preprocessor Preprocess the results before providing it to the reporter(s), can be repeated\n --preprocessor-options Pass extra options to the preprocessor (as JSON string, see --reporter-options example)\n --reporter Select reporter (default: symbols), can be repeated (3)\n --reporter-options Pass extra options to the reporter (as JSON string, see example)\n --no-config-hints Suppress configuration hints\n --no-tag-hints Suppress tag hints\n --treat-config-hints-as-errors Exit with non-zero code (1) if there are any configuration hints\n --treat-tag-hints-as-errors Exit with non-zero code (1) if there are any tag hints\n --max-issues Maximum number of total issues before non-zero exit code (default: 0)\n --max-show-issues Maximum number of issues to display per type\n --no-exit-code Always exit with code zero (0)\n\nTroubleshooting\n -d, --debug Show debug output\n --memory Measure memory usage and display data table\n --memory-realtime Log memory usage in realtime\n --performance Measure count and running time of key functions and display stats table\n --performance-fn [name] Measure only function [name]\n -u, --duration Print total running time (zero overhead, no instrumentation)\n --trace Show trace output\n --trace-dependency [name] Show files that import the named dependency\n --trace-export [name] Show trace output for named export(s)\n --trace-file [file] Show trace output for exports in file\n\n(1) Issue types: files, dependencies, unlisted, unresolved, exports, nsExports, types, nsTypes, enumMembers, namespaceMembers, duplicates, catalog, catalogReferences, cycles\n(2) Fixable issue types: dependencies, exports, types, files, catalog\n(3) Built-in reporters: symbols (default), compact, codeowners, cycles, json, codeclimate, markdown, disclosure, github-actions, sarif\n\nExamples:\n\n$ knip\n$ knip --production\n$ knip --workspace packages/client --include files,dependencies\n$ knip --workspace @myorg/* --workspace '!@myorg/legacy'\n$ knip --workspace './apps/*' --workspace '@shared/utils'\n$ knip -c ./config/knip.json --reporter compact\n$ knip --reporter codeowners --reporter-options '{\"path\":\".github/CODEOWNERS\"}'\n$ knip --tags=-lintignore\n\nWebsite: https://knip.dev";
2
2
  export type ParsedCLIArgs = ReturnType<typeof parseCLIArgs>;
3
+ export declare const parseNumericOption: (value: string | undefined, option: string) => number | undefined;
3
4
  export default function parseCLIArgs(): {
4
5
  cache?: boolean | undefined;
5
6
  'cache-location'?: string | undefined;
@@ -1,4 +1,5 @@
1
1
  import { parseArgs } from 'node:util';
2
+ import { ConfigurationError } from './errors.js';
2
3
  export const helpText = `✂️ Find unused dependencies, exports and files in your JavaScript and TypeScript projects
3
4
 
4
5
  Usage: knip [options]
@@ -79,6 +80,14 @@ $ knip --reporter codeowners --reporter-options '{"path":".github/CODEOWNERS"}'
79
80
  $ knip --tags=-lintignore
80
81
 
81
82
  Website: https://knip.dev`;
83
+ export const parseNumericOption = (value, option) => {
84
+ if (value === undefined)
85
+ return undefined;
86
+ if (!/^\d+$/.test(value)) {
87
+ throw new ConfigurationError(`Option --${option} expects a non-negative integer, got: ${value}`);
88
+ }
89
+ return Number(value);
90
+ };
82
91
  export default function parseCLIArgs() {
83
92
  return parseArgs({
84
93
  options: {
@@ -1,5 +1,5 @@
1
1
  import type { Options } from '../types/options.ts';
2
- import type { ParsedCLIArgs } from './cli-arguments.ts';
2
+ import { type ParsedCLIArgs } from './cli-arguments.ts';
3
3
  interface CreateOptions extends Partial<Options> {
4
4
  args?: ParsedCLIArgs;
5
5
  }
@@ -43,6 +43,7 @@ export declare const createOptions: (options: CreateOptions) => Promise<{
43
43
  isTreatTagHintsAsErrors: boolean;
44
44
  isUseTscFiles: boolean | undefined;
45
45
  isWatch: boolean;
46
+ maxIssues: number;
46
47
  maxShowIssues: number | undefined;
47
48
  parsedConfig: {
48
49
  angular?: string | boolean | string[] | {
@@ -365,6 +366,11 @@ export declare const createOptions: (options: CreateOptions) => Promise<{
365
366
  entry?: string | string[] | undefined;
366
367
  project?: string | string[] | undefined;
367
368
  } | undefined;
369
+ mise?: string | boolean | string[] | {
370
+ config?: string | string[] | undefined;
371
+ entry?: string | string[] | undefined;
372
+ project?: string | string[] | undefined;
373
+ } | undefined;
368
374
  mocha?: string | boolean | string[] | {
369
375
  config?: string | string[] | undefined;
370
376
  entry?: string | string[] | undefined;
@@ -974,12 +980,13 @@ export declare const createOptions: (options: CreateOptions) => Promise<{
974
980
  } | undefined;
975
981
  ignoreWorkspaces?: string[] | undefined;
976
982
  includeEntryExports?: boolean | undefined;
977
- compilers?: Record<string, true | import("../compilers/types.ts").CompilerAsync | import("../compilers/types.ts").CompilerSync> | undefined;
978
- syncCompilers?: Record<string, true | import("../compilers/types.ts").CompilerSync> | undefined;
979
- asyncCompilers?: Record<string, import("../compilers/types.ts").CompilerAsync> | undefined;
983
+ compilers?: Record<string, true | import("../types.ts").Compiler> | undefined;
984
+ asyncCompilers?: Record<string, import("../types.ts").Compiler> | undefined;
980
985
  tags?: string[] | undefined;
981
986
  treatConfigHintsAsErrors?: boolean | undefined;
982
987
  treatTagHintsAsErrors?: boolean | undefined;
988
+ preprocessor?: string | string[] | undefined;
989
+ preprocessorOptions?: Record<string, unknown> | undefined;
983
990
  include?: ("binaries" | "catalog" | "catalogReferences" | "cycles" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[] | undefined;
984
991
  exclude?: ("binaries" | "catalog" | "catalogReferences" | "cycles" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[] | undefined;
985
992
  workspaces?: Record<string, {
@@ -1303,6 +1310,11 @@ export declare const createOptions: (options: CreateOptions) => Promise<{
1303
1310
  entry?: string | string[] | undefined;
1304
1311
  project?: string | string[] | undefined;
1305
1312
  } | undefined;
1313
+ mise?: string | boolean | string[] | {
1314
+ config?: string | string[] | undefined;
1315
+ entry?: string | string[] | undefined;
1316
+ project?: string | string[] | undefined;
1317
+ } | undefined;
1306
1318
  mocha?: string | boolean | string[] | {
1307
1319
  config?: string | string[] | undefined;
1308
1320
  entry?: string | string[] | undefined;
@@ -1907,6 +1919,9 @@ export declare const createOptions: (options: CreateOptions) => Promise<{
1907
1919
  includeEntryExports?: boolean | undefined;
1908
1920
  }> | undefined;
1909
1921
  };
1922
+ preprocessor: string[];
1923
+ preprocessorInputs: string[];
1924
+ preprocessorOptions: string;
1910
1925
  rules: {
1911
1926
  binaries: import("../types/issues.ts").IssueSeverity;
1912
1927
  catalog: import("../types/issues.ts").IssueSeverity;
@@ -1,7 +1,8 @@
1
- import { partitionCompilers } from '../compilers/index.js';
2
1
  import { ISSUE_TYPES, KNIP_CONFIG_LOCATIONS } from '../constants.js';
3
2
  import { knipConfigurationSchema } from '../schema/configuration.js';
3
+ import { arrayify } from './array.js';
4
4
  import { getCatalogContainer } from './catalog.js';
5
+ import { parseNumericOption } from './cli-arguments.js';
5
6
  import { ConfigurationError } from './errors.js';
6
7
  import { findFile, loadJSON } from './fs.js';
7
8
  import { getIncludedIssueTypes, shorthandCycles, shorthandDeps, shorthandExports, shorthandFiles, } from './get-included-issue-types.js';
@@ -10,7 +11,7 @@ import { loadResolvedConfigFile } from './load-config.js';
10
11
  import { _load } from './loader.js';
11
12
  import { logWarning } from './log.js';
12
13
  import { getKeysByValue } from './object.js';
13
- import { isAbsolute, join, normalize, toAbsolute, toPosix } from './path.js';
14
+ import { isAbsolute, isInternal, join, normalize, toAbsolute, toPosix } from './path.js';
14
15
  import { splitTags } from './tag.js';
15
16
  export const createOptions = async (options) => {
16
17
  const { args = {} } = options;
@@ -55,7 +56,7 @@ export const createOptions = async (options) => {
55
56
  }
56
57
  }
57
58
  }
58
- const parsedConfig = knipConfigurationSchema.parse(partitionCompilers(loadedConfig));
59
+ const parsedConfig = knipConfigurationSchema.parse(loadedConfig);
59
60
  if (!configFilePath && manifest.knip)
60
61
  configFilePath = manifestPath;
61
62
  const pnpmWorkspacePath = findFile(cwd, 'pnpm-workspace.yaml');
@@ -93,6 +94,14 @@ export const createOptions = async (options) => {
93
94
  const isFixFiles = args['allow-remove-files'] && (fixTypes.length === 0 || fixTypes.includes('files'));
94
95
  const tags = splitTags(args.tags ?? options.tags ?? parsedConfig.tags ?? []);
95
96
  const workspace = options.workspace ?? args.workspace;
97
+ const toPreprocessor = (specifier) => (isInternal(specifier) ? toAbsolute(specifier, cwd) : specifier);
98
+ const configuredPreprocessor = arrayify(parsedConfig.preprocessor).map(toPreprocessor);
99
+ const preprocessor = args.preprocessor ? args.preprocessor.map(toPreprocessor) : configuredPreprocessor;
100
+ const preprocessorInputs = args.preprocessor
101
+ ? [...new Set([...configuredPreprocessor, ...preprocessor])]
102
+ : preprocessor;
103
+ const preprocessorOptions = args['preprocessor-options'] ??
104
+ (parsedConfig.preprocessorOptions ? JSON.stringify(parsedConfig.preprocessorOptions) : '');
96
105
  return {
97
106
  cacheLocation: args['cache-location'] ?? join(cwd, 'node_modules', '.cache', 'knip'),
98
107
  catalog: await getCatalogContainer(cwd, manifest, manifestPath, pnpmWorkspacePath, pnpmWorkspace),
@@ -150,8 +159,12 @@ export const createOptions = async (options) => {
150
159
  isTreatTagHintsAsErrors: args['treat-tag-hints-as-errors'] ?? parsedConfig.treatTagHintsAsErrors ?? false,
151
160
  isUseTscFiles: options.isUseTscFiles ?? args['use-tsconfig-files'] ?? (options.isSession && !configFilePath),
152
161
  isWatch: args.watch ?? options.isWatch ?? false,
153
- maxShowIssues: args['max-show-issues'] ? Number(args['max-show-issues']) : undefined,
162
+ maxIssues: parseNumericOption(args['max-issues'], 'max-issues') ?? 0,
163
+ maxShowIssues: parseNumericOption(args['max-show-issues'], 'max-show-issues'),
154
164
  parsedConfig,
165
+ preprocessor,
166
+ preprocessorInputs,
167
+ preprocessorOptions,
155
168
  rules,
156
169
  tags,
157
170
  traceDependency: args['trace-dependency'],
@@ -4,9 +4,11 @@ interface ErrorWithCause extends Error {
4
4
  }
5
5
  export declare class ConfigurationError extends Error {
6
6
  }
7
+ export declare class CompilerError extends Error {
8
+ }
7
9
  export declare class LoaderError extends Error {
8
10
  }
9
- export declare const isKnownError: (error: Error) => error is core.$ZodError<any> | ConfigurationError | LoaderError;
11
+ export declare const isKnownError: (error: Error) => error is core.$ZodError<any> | CompilerError | ConfigurationError | LoaderError;
10
12
  export declare const hasErrorCause: (error: Error) => error is ErrorWithCause;
11
13
  export declare const isConfigurationError: (error: Error) => error is ConfigurationError;
12
14
  export declare const isModuleNotFoundError: (error: Error) => boolean;
@@ -3,9 +3,14 @@ import { substringBefore } from './string.js';
3
3
  const isZodErrorLike = (error) => error instanceof core.$ZodError;
4
4
  export class ConfigurationError extends Error {
5
5
  }
6
+ export class CompilerError extends Error {
7
+ }
6
8
  export class LoaderError extends Error {
7
9
  }
8
- export const isKnownError = (error) => error instanceof ConfigurationError || error instanceof LoaderError || isZodErrorLike(error);
10
+ export const isKnownError = (error) => error instanceof ConfigurationError ||
11
+ error instanceof CompilerError ||
12
+ error instanceof LoaderError ||
13
+ isZodErrorLike(error);
9
14
  export const hasErrorCause = (error) => !isZodErrorLike(error) && error.cause instanceof Error;
10
15
  export const isConfigurationError = (error) => error instanceof ConfigurationError;
11
16
  export const isModuleNotFoundError = (error) => 'code' in error && error.code === 'MODULE_NOT_FOUND';
@@ -4,7 +4,7 @@ import { deserialize, serialize } from 'node:v8';
4
4
  import { debugLog } from './debug.js';
5
5
  import { isDirectory, isFile } from './fs.js';
6
6
  import { timerify } from './Performance.js';
7
- import { dirname, isAbsolute, resolve } from './path.js';
7
+ import { isAbsolute, resolve } from './path.js';
8
8
  const createCache = (filePath) => {
9
9
  try {
10
10
  return deserialize(fs.readFileSync(filePath));
@@ -70,7 +70,7 @@ export class FileEntryCache {
70
70
  }
71
71
  reconcile() {
72
72
  try {
73
- const dir = dirname(this.filePath);
73
+ const dir = path.dirname(this.filePath);
74
74
  if (!isDirectory(dir))
75
75
  fs.mkdirSync(dir, { recursive: true });
76
76
  fs.writeFileSync(this.filePath, serialize(this.cache));
@@ -35,6 +35,7 @@ export const updateImportMap = (file, importMap, graph) => {
35
35
  }
36
36
  };
37
37
  export const createFileNode = () => ({
38
+ skipExports: false,
38
39
  imports: {
39
40
  internal: new Map(),
40
41
  external: new Set(),
@@ -231,6 +231,7 @@ export declare const getPublishedTypeManifest: (manifest: PackageJson) => {
231
231
  mdx: unknown;
232
232
  mdxlint: unknown;
233
233
  metro: unknown;
234
+ mise: unknown;
234
235
  mocha: unknown;
235
236
  moonrepo: unknown;
236
237
  msw: unknown;
@@ -0,0 +1,6 @@
1
+ import type { Results } from '../run.ts';
2
+ import type { ReporterOptions } from '../types/issues.ts';
3
+ import type { ParsedCLIArgs } from './cli-arguments.ts';
4
+ import type { MainOptions } from './create-options.ts';
5
+ export declare const toReporterOptions: (options: MainOptions, results: Results, args?: ParsedCLIArgs) => ReporterOptions;
6
+ export declare const createPreprocessor: (processors: string[]) => Promise<(data: ReporterOptions) => Promise<ReporterOptions>>;
@@ -0,0 +1,24 @@
1
+ import { _load } from './loader.js';
2
+ export const toReporterOptions = (options, results, args) => ({
3
+ ...results,
4
+ report: options.includedIssueTypes,
5
+ cwd: options.cwd,
6
+ configFilePath: options.configFilePath,
7
+ isDisableConfigHints: options.isDisableConfigHints,
8
+ isDisableTagHints: options.isDisableTagHints,
9
+ isProduction: options.isProduction,
10
+ isShowProgress: options.isShowProgress,
11
+ isTreatConfigHintsAsErrors: options.isTreatConfigHintsAsErrors,
12
+ isTreatTagHintsAsErrors: options.isTreatTagHintsAsErrors,
13
+ maxShowIssues: options.maxShowIssues,
14
+ options: args?.['reporter-options'] ?? '',
15
+ preprocessorOptions: options.preprocessorOptions,
16
+ });
17
+ export const createPreprocessor = async (processors) => {
18
+ const preprocessors = await Promise.all(processors.map(_load));
19
+ return async (data) => {
20
+ for (const preprocessor of preprocessors)
21
+ data = await preprocessor(data);
22
+ return data;
23
+ };
24
+ };
@@ -1,3 +1,2 @@
1
1
  import type { ReporterOptions } from '../types/issues.ts';
2
- export declare const runPreprocessors: (processors: string[], data: ReporterOptions) => Promise<ReporterOptions>;
3
2
  export declare const runReporters: (reporter: string[], options: ReporterOptions) => Promise<void>;
@@ -1,13 +1,6 @@
1
1
  import internalReporters from '../reporters/index.js';
2
2
  import { _load } from './loader.js';
3
3
  import { isAbsolute, isInternal, resolve } from './path.js';
4
- export const runPreprocessors = async (processors, data) => {
5
- const preprocessors = await Promise.all(processors.map(proc => _load(isInternal(proc) && !isAbsolute(proc) ? resolve(proc) : proc)));
6
- let result = data;
7
- for (const preprocessor of preprocessors)
8
- result = await preprocessor(result);
9
- return result;
10
- };
11
4
  export const runReporters = async (reporter, options) => {
12
5
  const reporters = await Promise.all(reporter.map(async (reporter) => {
13
6
  return reporter in internalReporters
@@ -17,7 +17,7 @@ export type WatchChange = {
17
17
  export type SessionHandler = Awaited<ReturnType<typeof getSessionHandler>>;
18
18
  type WatchOptions = {
19
19
  analyzedFiles: Set<string>;
20
- analyzeSourceFile: (filePath: string, principal: ProjectPrincipal) => void;
20
+ analyzeSourceFile: (filePath: string) => Promise<void>;
21
21
  chief: ConfigurationChief;
22
22
  collector: IssueCollector;
23
23
  analyze: () => Promise<void>;
@@ -1,7 +1,8 @@
1
- import { readFileSync } from 'node:fs';
2
1
  import { invalidateCache } from '../graph-explorer/cache.js';
3
2
  import { debugLog } from './debug.js';
3
+ import { hasErrorCause } from './errors.js';
4
4
  import { isFile } from './fs.js';
5
+ import { logError } from './log.js';
5
6
  import { updateImportMap } from './module-graph.js';
6
7
  import { toAbsolute, toPosix, toRelative } from './path.js';
7
8
  import { clearModuleResolutionCaches } from '../typescript/resolve-module-names.js';
@@ -13,11 +14,12 @@ const createUpdate = (options) => {
13
14
  return { duration, mem };
14
15
  };
15
16
  export const getSessionHandler = async (options, { analyzedFiles, analyzeSourceFile, chief, collector, analyze, principal, graph, isIgnored, onFileChange, unreferencedFiles, entryPaths, }) => {
16
- const handleFileChanges = async (changes) => {
17
+ const added = new Set();
18
+ const deleted = new Set();
19
+ const modified = new Set();
20
+ const processFileChanges = async (changes) => {
17
21
  const startTime = performance.now();
18
- const added = new Set();
19
- const deleted = new Set();
20
- const modified = new Set();
22
+ let hasRelevantChanges = false;
21
23
  for (const change of changes) {
22
24
  const filePath = toAbsolute(change.filePath, options.cwd);
23
25
  const relativePath = toRelative(change.filePath, options.cwd);
@@ -28,23 +30,24 @@ export const getSessionHandler = async (options, { analyzedFiles, analyzeSourceF
28
30
  const workspace = chief.findWorkspaceByFilePath(filePath);
29
31
  if (!workspace)
30
32
  continue;
33
+ hasRelevantChanges = true;
31
34
  switch (change.type) {
32
35
  case 'added':
33
36
  principal.addProjectPath(filePath);
34
- principal.deletedFiles.delete(filePath);
37
+ deleted.delete(filePath);
35
38
  if (principal.projectPaths.has(filePath))
36
39
  added.add(filePath);
37
40
  debugLog(workspace.name, `Watcher: + ${relativePath}`);
38
41
  break;
39
42
  case 'deleted':
40
43
  deleted.add(filePath);
44
+ added.delete(filePath);
41
45
  analyzedFiles.delete(filePath);
42
46
  principal.removeProjectPath(filePath);
43
47
  debugLog(workspace.name, `Watcher: - ${relativePath}`);
44
48
  break;
45
49
  default: {
46
- const cached = principal.fileManager.sourceTextCache.get(filePath);
47
- if (cached !== undefined && cached === readFileSync(filePath, 'utf8')) {
50
+ if (!principal.fileManager.hasChanged(filePath)) {
48
51
  debugLog(workspace.name, `Watcher: = ${relativePath}`);
49
52
  continue;
50
53
  }
@@ -55,31 +58,31 @@ export const getSessionHandler = async (options, { analyzedFiles, analyzeSourceF
55
58
  }
56
59
  principal.invalidateFile(filePath);
57
60
  }
58
- if (added.size === 0 && deleted.size === 0 && modified.size === 0)
61
+ if (!hasRelevantChanges || (added.size === 0 && deleted.size === 0 && modified.size === 0))
59
62
  return;
60
63
  clearResolverCache();
61
64
  clearModuleResolutionCaches();
62
65
  clearGlobCache();
63
66
  invalidateCache(graph);
67
+ const filePaths = await principal.getUsedResolvedFiles();
64
68
  unreferencedFiles.clear();
65
69
  const cachedUnusedFiles = collector.purge();
66
70
  for (const filePath of added)
67
71
  cachedUnusedFiles.add(filePath);
68
72
  for (const filePath of deleted)
69
73
  cachedUnusedFiles.delete(filePath);
70
- const filePaths = principal.getUsedResolvedFiles();
71
74
  if (added.size > 0 || deleted.size > 0) {
72
75
  graph.clear();
73
76
  for (const filePath of filePaths) {
74
77
  const workspace = chief.findWorkspaceByFilePath(filePath);
75
78
  if (workspace) {
76
- analyzeSourceFile(filePath, principal);
79
+ await analyzeSourceFile(filePath);
77
80
  }
78
81
  }
79
82
  }
80
83
  else {
81
84
  for (const [filePath, file] of graph) {
82
- if (filePaths.includes(filePath)) {
85
+ if (filePaths.has(filePath)) {
83
86
  file.importedBy = undefined;
84
87
  }
85
88
  else {
@@ -94,7 +97,7 @@ export const getSessionHandler = async (options, { analyzedFiles, analyzeSourceF
94
97
  if (!graph.has(filePath)) {
95
98
  const workspace = chief.findWorkspaceByFilePath(filePath);
96
99
  if (workspace) {
97
- analyzeSourceFile(filePath, principal);
100
+ await analyzeSourceFile(filePath);
98
101
  }
99
102
  }
100
103
  }
@@ -103,7 +106,7 @@ export const getSessionHandler = async (options, { analyzedFiles, analyzeSourceF
103
106
  const workspace = chief.findWorkspaceByFilePath(filePath);
104
107
  if (workspace) {
105
108
  if (principal.projectPaths.has(filePath) || graph.has(filePath)) {
106
- analyzeSourceFile(filePath, principal);
109
+ await analyzeSourceFile(filePath);
107
110
  }
108
111
  }
109
112
  }
@@ -120,17 +123,32 @@ export const getSessionHandler = async (options, { analyzedFiles, analyzeSourceF
120
123
  collector.addFileCounts({ processed: analyzedFiles.size, unused: unusedFiles.length });
121
124
  for (const issue of collector.getRetainedIssues())
122
125
  collector.addIssue(issue);
126
+ added.clear();
127
+ deleted.clear();
128
+ modified.clear();
123
129
  const update = createUpdate({ startTime });
124
130
  if (onFileChange)
125
131
  onFileChange(Object.assign({ issues: getIssues().issues }, update));
126
132
  return update;
127
133
  };
134
+ const clearSourceText = () => principal.fileManager.sourceTextCache.clear();
135
+ let pending = Promise.resolve();
136
+ const handleFileChanges = (changes) => {
137
+ const update = pending.then(() => processFileChanges(changes));
138
+ pending = update.then(clearSourceText, clearSourceText);
139
+ return update;
140
+ };
128
141
  const listener = (eventType, filePath) => {
129
142
  debugLog('*', `(raw) ${eventType} ${filePath}`);
130
143
  if (typeof filePath === 'string') {
131
144
  const normalizedPath = toPosix(filePath);
132
145
  const type = eventType === 'rename' ? (isFile(options.cwd, normalizedPath) ? 'added' : 'deleted') : 'modified';
133
- handleFileChanges([{ type, filePath: normalizedPath }]);
146
+ handleFileChanges([{ type, filePath: normalizedPath }]).catch(error => {
147
+ const message = error instanceof Error ? error.message : String(error);
148
+ const reason = error instanceof Error && hasErrorCause(error) ? `\nReason: ${error.cause.message}` : '';
149
+ logError(message + reason);
150
+ process.exitCode = 2;
151
+ });
134
152
  }
135
153
  };
136
154
  const getIssues = () => collector.getIssues();
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "6.33.0";
1
+ export declare const version = "6.35.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '6.33.0';
1
+ export const version = '6.35.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "6.33.0",
3
+ "version": "6.35.0",
4
4
  "description": "Find and fix unused dependencies, exports and files in your TypeScript and JavaScript projects",
5
5
  "keywords": [
6
6
  "analysis",
@@ -85,25 +85,25 @@
85
85
  "formatly": "^0.7.0",
86
86
  "get-tsconfig": "4.14.3",
87
87
  "jiti": "^2.7.0",
88
- "oxc-parser": "^0.147.0",
88
+ "oxc-parser": "^0.148.0",
89
89
  "oxc-resolver": "11.24.2",
90
90
  "picomatch": "^4.0.7",
91
91
  "smol-toml": "^1.8.0",
92
92
  "strip-json-comments": "5.0.3",
93
93
  "tinyglobby": "^0.2.17",
94
- "unbash": "^4.0.10",
94
+ "unbash": "^4.0.11",
95
95
  "yaml": "^2.9.0",
96
96
  "zod": "^4.4.3"
97
97
  },
98
98
  "devDependencies": {
99
- "@jest/types": "^30.4.1",
100
- "@types/bun": "^1.4.0",
99
+ "@jest/types": "^30.5.1",
100
+ "@types/bun": "^1.4.1",
101
101
  "@types/picomatch": "^4.0.3",
102
- "@types/webpack": "^5.28.5",
103
102
  "codeclimate-types": "^0.3.1",
104
103
  "prettier": "^3.9.6",
105
- "tsx": "^4.23.12",
106
- "typescript": "7.0.2"
104
+ "tsx": "^4.23.13",
105
+ "typescript": "7.0.2",
106
+ "webpack": "^5.110.3"
107
107
  },
108
108
  "engines": {
109
109
  "node": "^20.19.0 || >=22.12.0"
package/schema.json CHANGED
@@ -173,6 +173,14 @@
173
173
  }
174
174
  }
175
175
  },
176
+ "preprocessor": {
177
+ "title": "Preprocess the results before providing them to the reporter(s)",
178
+ "oneOf": [{ "type": "string" }, { "$ref": "#/definitions/list" }]
179
+ },
180
+ "preprocessorOptions": {
181
+ "title": "Extra options to pass to the preprocessor",
182
+ "type": "object"
183
+ },
176
184
  "treatConfigHintsAsErrors": {
177
185
  "title": "Exit with non-zero code (1) if there are any configuration hints",
178
186
  "type": "boolean"
@@ -650,6 +658,10 @@
650
658
  "title": "metro plugin configuration (https://knip.dev/reference/plugins/metro)",
651
659
  "$ref": "#/definitions/plugin"
652
660
  },
661
+ "mise": {
662
+ "title": "mise plugin configuration (https://knip.dev/reference/plugins/mise)",
663
+ "$ref": "#/definitions/plugin"
664
+ },
653
665
  "mocha": {
654
666
  "title": "Mocha plugin configuration (https://knip.dev/reference/plugins/mocha)",
655
667
  "$ref": "#/definitions/plugin"