knip 6.29.0 → 6.30.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 (105) hide show
  1. package/dist/CatalogCounselor.d.ts +5 -2
  2. package/dist/CatalogCounselor.js +26 -8
  3. package/dist/ConfigurationChief.d.ts +1 -1
  4. package/dist/ConfigurationChief.js +29 -36
  5. package/dist/DependencyDeputy.d.ts +8 -1
  6. package/dist/DependencyDeputy.js +5 -5
  7. package/dist/PackagePeeker.d.ts +5 -0
  8. package/dist/PackagePeeker.js +31 -3
  9. package/dist/WorkspaceWorker.d.ts +1 -1
  10. package/dist/WorkspaceWorker.js +11 -7
  11. package/dist/binaries/bash-parser.js +3 -3
  12. package/dist/binaries/create-script-parser-context.d.ts +7 -0
  13. package/dist/binaries/create-script-parser-context.js +17 -0
  14. package/dist/binaries/resolvers/pnpx.js +10 -5
  15. package/dist/compilers/index.d.ts +5 -5
  16. package/dist/compilers/scss.js +32 -5
  17. package/dist/compilers/shared.d.ts +1 -0
  18. package/dist/compilers/shared.js +1 -1
  19. package/dist/compilers/svelte.d.ts +2 -0
  20. package/dist/compilers/svelte.js +22 -0
  21. package/dist/constants.d.ts +4 -1
  22. package/dist/constants.js +4 -0
  23. package/dist/graph/analyze.js +5 -2
  24. package/dist/graph/build.d.ts +3 -1
  25. package/dist/graph/build.js +38 -19
  26. package/dist/graph-explorer/operations/build-exports-tree.js +2 -1
  27. package/dist/graph-explorer/operations/is-referenced.js +7 -4
  28. package/dist/graph-explorer/utils.js +2 -2
  29. package/dist/plugins/_template/index.js +1 -1
  30. package/dist/plugins/astro-markdoc/index.js +3 -2
  31. package/dist/plugins/eleventy/index.js +1 -1
  32. package/dist/plugins/esbuild/index.js +1 -1
  33. package/dist/plugins/eslint/helpers.js +2 -1
  34. package/dist/plugins/gatsby/index.js +2 -2
  35. package/dist/plugins/jest/helpers.js +1 -1
  36. package/dist/plugins/metro/index.js +1 -1
  37. package/dist/plugins/nest/index.js +1 -1
  38. package/dist/plugins/node/index.js +11 -2
  39. package/dist/plugins/nx/index.js +4 -3
  40. package/dist/plugins/openclaw/index.js +1 -1
  41. package/dist/plugins/oxlint/index.js +9 -2
  42. package/dist/plugins/remark/helpers.d.ts +3 -0
  43. package/dist/plugins/remark/helpers.js +42 -0
  44. package/dist/plugins/remark/index.js +4 -5
  45. package/dist/plugins/rolldown/index.js +1 -1
  46. package/dist/plugins/rollup/index.js +1 -1
  47. package/dist/plugins/rslib/index.js +1 -1
  48. package/dist/plugins/stryker/index.js +6 -1
  49. package/dist/plugins/svelte/compiler.js +7 -5
  50. package/dist/plugins/tsdown/index.js +2 -2
  51. package/dist/plugins/tsup/index.js +1 -1
  52. package/dist/plugins/vitest/index.js +1 -1
  53. package/dist/plugins/vitest/types.d.ts +1 -1
  54. package/dist/reporters/codeclimate.js +1 -5
  55. package/dist/reporters/github-actions.js +5 -3
  56. package/dist/reporters/index.d.ts +1 -0
  57. package/dist/reporters/index.js +2 -0
  58. package/dist/reporters/json.d.ts +1 -0
  59. package/dist/reporters/json.js +1 -0
  60. package/dist/reporters/sarif.d.ts +3 -0
  61. package/dist/reporters/sarif.js +79 -0
  62. package/dist/reporters/trace.js +3 -2
  63. package/dist/reporters/util/util.d.ts +2 -1
  64. package/dist/reporters/util/util.js +6 -1
  65. package/dist/run.d.ts +1 -1
  66. package/dist/run.js +30 -0
  67. package/dist/schema/configuration.d.ts +6 -6
  68. package/dist/schema/configuration.js +1 -0
  69. package/dist/session/build-maps.js +2 -1
  70. package/dist/types/issues.d.ts +1 -0
  71. package/dist/types/package-json.d.ts +10 -0
  72. package/dist/typescript/get-imports-and-exports.js +3 -2
  73. package/dist/typescript/get-published-type-dependencies.d.ts +13 -0
  74. package/dist/typescript/get-published-type-dependencies.js +155 -0
  75. package/dist/typescript/visitors/calls.js +8 -0
  76. package/dist/util/catalog.d.ts +6 -1
  77. package/dist/util/catalog.js +16 -2
  78. package/dist/util/cli-arguments.d.ts +1 -1
  79. package/dist/util/cli-arguments.js +3 -3
  80. package/dist/util/codeowners.js +1 -1
  81. package/dist/util/create-input-handler.js +8 -2
  82. package/dist/util/create-options.d.ts +6 -5
  83. package/dist/util/disk-cache.js +1 -1
  84. package/dist/util/errors.js +3 -2
  85. package/dist/util/file-entry-cache.js +5 -1
  86. package/dist/util/fs.js +2 -2
  87. package/dist/util/get-included-issue-types.js +2 -1
  88. package/dist/util/gitignore-cache.js +4 -1
  89. package/dist/util/glob-cache.js +2 -2
  90. package/dist/util/glob-core.js +3 -1
  91. package/dist/util/input.d.ts +7 -1
  92. package/dist/util/input.js +6 -0
  93. package/dist/util/package-json.d.ts +346 -0
  94. package/dist/util/package-json.js +123 -0
  95. package/dist/util/path.d.ts +8 -7
  96. package/dist/util/resolve.d.ts +6 -0
  97. package/dist/util/resolve.js +24 -0
  98. package/dist/util/string.d.ts +2 -0
  99. package/dist/util/string.js +6 -0
  100. package/dist/util/table.js +2 -2
  101. package/dist/util/to-source-path.js +1 -1
  102. package/dist/version.d.ts +1 -1
  103. package/dist/version.js +1 -1
  104. package/package.json +5 -5
  105. package/schema.json +4 -0
@@ -0,0 +1,155 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { IS_DTS } from '../constants.js';
3
+ import { isFile } from '../util/fs.js';
4
+ import { _syncGlob } from '../util/glob.js';
5
+ import { getDefinitelyTypedFor, getPackageNameFromModuleSpecifier, isDefinitelyTyped, isStartsLikePackageName, sanitizeSpecifier, } from '../util/modules.js';
6
+ import { getPublishedTypeManifest, getPublishedTypeEntrySpecifiers, getPublishedTypeExportSpecifiers, isPublishedTypeExportTarget, toDeclarationSpecifier, } from '../util/package-json.js';
7
+ import { dirname, join, relative } from '../util/path.js';
8
+ import { _resolveDeclarationSync } from '../util/resolve.js';
9
+ import { _getImportsAndExports } from './get-imports-and-exports.js';
10
+ import { buildVisitor } from './visitors/walk.js';
11
+ const options = {
12
+ skipTypeOnly: false,
13
+ isFixExports: false,
14
+ isFixTypes: false,
15
+ isReportExports: false,
16
+ tags: [[], []],
17
+ };
18
+ const resolveNothing = () => undefined;
19
+ const toGlobPattern = (specifier) => {
20
+ const index = specifier.indexOf('*');
21
+ return `${specifier.slice(0, index)}**/*${specifier.slice(index + 1).replaceAll('*', '**')}`;
22
+ };
23
+ const isInWorkspace = (filePath, workspaceDir) => filePath === workspaceDir || filePath.startsWith(`${workspaceDir}/`);
24
+ const toDeclarationPath = (filePath) => {
25
+ if (IS_DTS.test(filePath))
26
+ return filePath;
27
+ const candidate = toDeclarationSpecifier(filePath);
28
+ if (candidate && isFile(candidate))
29
+ return candidate;
30
+ };
31
+ const getDeclarationPaths = (specifiers, workspaceDir) => {
32
+ const paths = new Set();
33
+ const patterns = [];
34
+ for (const specifier of specifiers) {
35
+ if (specifier.includes('*')) {
36
+ patterns.push(toGlobPattern(specifier));
37
+ }
38
+ else if (IS_DTS.test(specifier)) {
39
+ const filePath = join(workspaceDir, specifier);
40
+ if (isFile(filePath))
41
+ paths.add(filePath);
42
+ }
43
+ }
44
+ if (patterns.length > 0) {
45
+ for (const filePath of _syncGlob({ cwd: workspaceDir, patterns })) {
46
+ if (IS_DTS.test(filePath))
47
+ paths.add(filePath);
48
+ }
49
+ }
50
+ return paths;
51
+ };
52
+ const getPublishedTypeEntryPaths = (manifest, workspaceDir) => {
53
+ const { candidates, versioned } = getPublishedTypeEntrySpecifiers(manifest);
54
+ const paths = getDeclarationPaths(versioned, workspaceDir);
55
+ for (const specifier of candidates) {
56
+ const filePath = join(workspaceDir, specifier);
57
+ if (!isFile(filePath))
58
+ continue;
59
+ paths.add(filePath);
60
+ break;
61
+ }
62
+ for (const filePath of getDeclarationPaths(getPublishedTypeExportSpecifiers(manifest.exports), workspaceDir)) {
63
+ if (isPublishedTypeExportTarget(manifest.exports, `./${relative(workspaceDir, filePath)}`))
64
+ paths.add(filePath);
65
+ }
66
+ return paths;
67
+ };
68
+ export const createPublishedTypeDependencyAnalyzer = () => {
69
+ const visitor = buildVisitor([]);
70
+ const packageJsonCache = new Map();
71
+ const packageTypesCache = new Map();
72
+ const loadPackageJson = (filePath) => {
73
+ if (packageJsonCache.has(filePath))
74
+ return packageJsonCache.get(filePath);
75
+ let manifest;
76
+ try {
77
+ manifest = JSON.parse(readFileSync(filePath, 'utf8'));
78
+ }
79
+ catch { }
80
+ packageJsonCache.set(filePath, manifest);
81
+ return manifest;
82
+ };
83
+ const packageProvidesTypes = (packageJsonPath, resolvedPath) => {
84
+ const cached = packageTypesCache.get(packageJsonPath);
85
+ if (cached !== undefined)
86
+ return cached;
87
+ const manifest = loadPackageJson(packageJsonPath);
88
+ const hasTypes = toDeclarationPath(resolvedPath) !== undefined ||
89
+ Boolean(manifest && (manifest.types || manifest.typings || manifest.typesVersions)) ||
90
+ isFile(join(dirname(packageJsonPath), 'index.d.ts'));
91
+ packageTypesCache.set(packageJsonPath, hasTypes);
92
+ return hasTypes;
93
+ };
94
+ const getExternalDependency = (packageName, containingFile, declaredDependencies) => {
95
+ const resolved = _resolveDeclarationSync(packageName, containingFile);
96
+ if (isDefinitelyTyped(packageName))
97
+ return { packageName, isResolved: resolved !== undefined };
98
+ if (resolved?.packageJsonPath) {
99
+ const installedName = loadPackageJson(resolved.packageJsonPath)?.name;
100
+ if (installedName && isDefinitelyTyped(installedName))
101
+ return { packageName: installedName, isResolved: true };
102
+ if (packageProvidesTypes(resolved.packageJsonPath, resolved.path))
103
+ return { packageName, isResolved: true };
104
+ }
105
+ const typesPackageName = getDefinitelyTypedFor(packageName);
106
+ if (_resolveDeclarationSync(typesPackageName, containingFile) || declaredDependencies.has(typesPackageName))
107
+ return { packageName: typesPackageName, isResolved: true };
108
+ return { packageName, isResolved: resolved !== undefined };
109
+ };
110
+ return (workspace, manifest) => {
111
+ const dependencies = [];
112
+ const declaredDependencies = new Set([
113
+ ...Object.keys(manifest.dependencies ?? {}),
114
+ ...Object.keys(manifest.devDependencies ?? {}),
115
+ ...Object.keys(manifest.peerDependencies ?? {}),
116
+ ...Object.keys(manifest.optionalDependencies ?? {}),
117
+ ]);
118
+ const files = getPublishedTypeEntryPaths(getPublishedTypeManifest(manifest), workspace.dir);
119
+ for (const filePath of files) {
120
+ let file;
121
+ try {
122
+ file = _getImportsAndExports(filePath, readFileSync(filePath, 'utf8'), resolveNothing, options, false, true, visitor, undefined);
123
+ }
124
+ catch {
125
+ continue;
126
+ }
127
+ for (const _import of file.imports.unresolved) {
128
+ const specifier = sanitizeSpecifier(_import.specifier);
129
+ const packageName = isStartsLikePackageName(specifier)
130
+ ? getPackageNameFromModuleSpecifier(specifier)
131
+ : undefined;
132
+ if (!packageName || packageName === workspace.pkgName) {
133
+ if (!packageName && !specifier.startsWith('.'))
134
+ continue;
135
+ const resolved = _resolveDeclarationSync(specifier, filePath);
136
+ const declarationPath = resolved && toDeclarationPath(resolved.path);
137
+ if (declarationPath && isInWorkspace(declarationPath, workspace.dir))
138
+ files.add(declarationPath);
139
+ continue;
140
+ }
141
+ const external = getExternalDependency(packageName, filePath, declaredDependencies);
142
+ dependencies.push({
143
+ containingFilePath: filePath,
144
+ packageName: external.packageName,
145
+ specifier,
146
+ pos: _import.pos,
147
+ line: _import.line,
148
+ col: _import.col,
149
+ isResolved: external.isResolved,
150
+ });
151
+ }
152
+ }
153
+ return dependencies;
154
+ };
155
+ };
@@ -221,6 +221,14 @@ export function handleCallExpression(node, s) {
221
221
  for (const arg of node.arguments) {
222
222
  if (arg.type === 'Identifier')
223
223
  markRefIfNs(arg.name);
224
+ else if (arg.type === 'ArrowFunctionExpression' && arg.expression) {
225
+ const body = arg.body.type === 'AwaitExpression' ? arg.body.argument : arg.body;
226
+ if (body.type === 'ImportExpression' && isStringLiteral(body.source)) {
227
+ s.handledImportExpressions.add(body.start);
228
+ const specifier = getStringValue(body.source);
229
+ s.addImport(specifier, undefined, undefined, undefined, body.source.start, IMPORT_FLAGS.DYNAMIC | IMPORT_FLAGS.LOADER);
230
+ }
231
+ }
224
232
  else if (arg.type === 'ArrayExpression') {
225
233
  for (const el of arg.elements ?? []) {
226
234
  if (el?.type === 'Identifier')
@@ -1,6 +1,11 @@
1
1
  import type { CatalogContainer } from '../CatalogCounselor.ts';
2
2
  import type { PackageJson } from '../types/package-json.ts';
3
3
  export declare const DEFAULT_CATALOG = "default";
4
+ export type CatalogReference = {
5
+ catalogName: string;
6
+ packageName: string;
7
+ };
8
+ export declare const getCatalogReference: (specifier: string) => CatalogReference | undefined;
4
9
  export declare const getCatalogContainer: (cwd: string, manifest: PackageJson, manifestPath: string, pnpmWorkspacePath?: string, pnpmWorkspace?: any) => Promise<CatalogContainer>;
5
10
  export declare const parseCatalog: (container: CatalogContainer) => Set<string>;
6
- export declare const extractCatalogReferences: (manifest: PackageJson) => Set<string>;
11
+ export declare const extractCatalogReferences: (manifest: PackageJson) => CatalogReference[];
@@ -1,7 +1,21 @@
1
1
  import { isFile } from './fs.js';
2
2
  import { _load } from './loader.js';
3
+ import { getPackageNameFromModuleSpecifier } from './modules.js';
3
4
  import { basename, join } from './path.js';
4
5
  export const DEFAULT_CATALOG = 'default';
6
+ const CATALOG_PROTOCOL = '@catalog:';
7
+ export const getCatalogReference = (specifier) => {
8
+ const protocolIndex = specifier.lastIndexOf(CATALOG_PROTOCOL);
9
+ if (protocolIndex <= 0)
10
+ return;
11
+ const packageName = specifier.slice(0, protocolIndex);
12
+ if (getPackageNameFromModuleSpecifier(packageName) !== packageName)
13
+ return;
14
+ return {
15
+ catalogName: specifier.slice(protocolIndex + CATALOG_PROTOCOL.length) || DEFAULT_CATALOG,
16
+ packageName,
17
+ };
18
+ };
5
19
  export const getCatalogContainer = async (cwd, manifest, manifestPath, pnpmWorkspacePath, pnpmWorkspace) => {
6
20
  const filePath = pnpmWorkspacePath ?? (isFile(cwd, '.yarnrc.yml') ? join(cwd, '.yarnrc.yml') : manifestPath);
7
21
  const yarnWorkspace = basename(filePath) === '.yarnrc.yml' ? await _load(filePath) : undefined;
@@ -43,14 +57,14 @@ export const parseCatalog = (container) => {
43
57
  return entries;
44
58
  };
45
59
  export const extractCatalogReferences = (manifest) => {
46
- const catalogReferences = new Set();
60
+ const catalogReferences = [];
47
61
  const checkDependencies = (dependencies) => {
48
62
  if (!dependencies)
49
63
  return;
50
64
  for (const [name, version] of Object.entries(dependencies)) {
51
65
  if (typeof version === 'string' && version.startsWith('catalog:')) {
52
66
  const catalogName = version.slice('catalog:'.length) || DEFAULT_CATALOG;
53
- catalogReferences.add([catalogName, name].join(':'));
67
+ catalogReferences.push({ catalogName, packageName: name });
54
68
  }
55
69
  }
56
70
  };
@@ -1,4 +1,4 @@
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\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, 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\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";
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
3
  export default function parseCLIArgs(): {
4
4
  cache?: boolean | undefined;
@@ -26,7 +26,7 @@ Scope
26
26
  -D, --directory [dir] Run process from a different directory (default: cwd)
27
27
  --include Include provided issue type(s), can be comma-separated or repeated (1)
28
28
  --exclude Exclude provided issue type(s) from report, can be comma-separated or repeated (1)
29
- --dependencies Shortcut for --include dependencies,unlisted,binaries,unresolved,catalog
29
+ --dependencies Shortcut for --include dependencies,unlisted,binaries,unresolved,catalog,catalogReferences
30
30
  --exports Shortcut for --include exports,nsExports,types,nsTypes,enumMembers,namespaceMembers,duplicates
31
31
  --files Shortcut for --include files
32
32
  --cycles Shortcut for --include cycles (circular dependencies)
@@ -63,9 +63,9 @@ Troubleshooting
63
63
  --trace-export [name] Show trace output for named export(s)
64
64
  --trace-file [file] Show trace output for exports in file
65
65
 
66
- (1) Issue types: files, dependencies, unlisted, unresolved, exports, nsExports, types, nsTypes, enumMembers, namespaceMembers, duplicates, catalog, cycles
66
+ (1) Issue types: files, dependencies, unlisted, unresolved, exports, nsExports, types, nsTypes, enumMembers, namespaceMembers, duplicates, catalog, catalogReferences, cycles
67
67
  (2) Fixable issue types: dependencies, exports, types, files, catalog
68
- (3) Built-in reporters: symbols (default), compact, codeowners, cycles, json, codeclimate, markdown, disclosure, github-actions
68
+ (3) Built-in reporters: symbols (default), compact, codeowners, cycles, json, codeclimate, markdown, disclosure, github-actions, sarif
69
69
 
70
70
  Examples:
71
71
 
@@ -12,7 +12,7 @@ export function parseCodeowners(content) {
12
12
  return { owners, match: picomatch(expandIgnorePatterns([pattern], true)) };
13
13
  });
14
14
  return (filePath) => {
15
- for (const matcher of [...matchers].reverse()) {
15
+ for (const matcher of matchers.toReversed()) {
16
16
  if (matcher.match(filePath)) {
17
17
  return matcher.owners;
18
18
  }
@@ -55,11 +55,17 @@ export const createInputHandler = (deputy, chief, isGitIgnored, addIssue, extern
55
55
  const isWorkspace = chief.workspacesByPkgName.has(packageName);
56
56
  const inputWorkspace = getWorkspaceFor(input, chief, workspace);
57
57
  if (inputWorkspace) {
58
- let isHandled = deputy.maybeAddReferencedExternalDependency(inputWorkspace, packageName, isConfig(input), input.isTypeOnly);
58
+ let isHandled = deputy.maybeAddReferencedExternalDependency(inputWorkspace, packageName, {
59
+ isDevOnly: isConfig(input),
60
+ isTypeOnly: input.isTypeOnly,
61
+ });
59
62
  if (input.isTypeOnly && input.containingFilePath) {
60
63
  const owningWorkspace = chief.findWorkspaceByFilePath(input.containingFilePath);
61
64
  if (owningWorkspace && owningWorkspace !== inputWorkspace) {
62
- const isOwnerHandled = deputy.maybeAddReferencedExternalDependency(owningWorkspace, packageName, isConfig(input), input.isTypeOnly);
65
+ const isOwnerHandled = deputy.maybeAddReferencedExternalDependency(owningWorkspace, packageName, {
66
+ isDevOnly: isConfig(input),
67
+ isTypeOnly: input.isTypeOnly,
68
+ });
63
69
  isHandled = isHandled || isOwnerHandled;
64
70
  }
65
71
  }
@@ -936,7 +936,7 @@ export declare const createOptions: (options: CreateOptions) => Promise<{
936
936
  project?: string | string[] | undefined;
937
937
  } | undefined;
938
938
  $schema?: string | undefined;
939
- rules?: Partial<Record<"binaries" | "catalog" | "cycles" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved", "error" | "off" | "warn">> | undefined;
939
+ rules?: Partial<Record<"binaries" | "catalog" | "catalogReferences" | "cycles" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved", "error" | "off" | "warn">> | undefined;
940
940
  entry?: string | string[] | undefined;
941
941
  project?: string | string[] | undefined;
942
942
  paths?: Record<string, string[]> | undefined;
@@ -947,7 +947,7 @@ export declare const createOptions: (options: CreateOptions) => Promise<{
947
947
  ignoreMembers?: (string | RegExp)[] | undefined;
948
948
  ignoreUnresolved?: (string | RegExp)[] | undefined;
949
949
  ignoreExportsUsedInFile?: boolean | Record<string, boolean | undefined> | undefined;
950
- ignoreIssues?: Record<string, ("binaries" | "catalog" | "cycles" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[]> | undefined;
950
+ ignoreIssues?: Record<string, ("binaries" | "catalog" | "catalogReferences" | "cycles" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[]> | undefined;
951
951
  cycles?: {
952
952
  allow?: string[][] | undefined;
953
953
  dynamicImports?: boolean | undefined;
@@ -960,8 +960,8 @@ export declare const createOptions: (options: CreateOptions) => Promise<{
960
960
  tags?: string[] | undefined;
961
961
  treatConfigHintsAsErrors?: boolean | undefined;
962
962
  treatTagHintsAsErrors?: boolean | undefined;
963
- include?: ("binaries" | "catalog" | "cycles" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[] | undefined;
964
- exclude?: ("binaries" | "catalog" | "cycles" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[] | undefined;
963
+ include?: ("binaries" | "catalog" | "catalogReferences" | "cycles" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[] | undefined;
964
+ exclude?: ("binaries" | "catalog" | "catalogReferences" | "cycles" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[] | undefined;
965
965
  workspaces?: Record<string, {
966
966
  angular?: string | boolean | string[] | {
967
967
  config?: string | string[] | undefined;
@@ -1863,13 +1863,14 @@ export declare const createOptions: (options: CreateOptions) => Promise<{
1863
1863
  ignoreMembers?: (string | RegExp)[] | undefined;
1864
1864
  ignoreUnresolved?: (string | RegExp)[] | undefined;
1865
1865
  ignoreExportsUsedInFile?: boolean | Record<string, boolean | undefined> | undefined;
1866
- ignoreIssues?: Record<string, ("binaries" | "catalog" | "cycles" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[]> | undefined;
1866
+ ignoreIssues?: Record<string, ("binaries" | "catalog" | "catalogReferences" | "cycles" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[]> | undefined;
1867
1867
  includeEntryExports?: boolean | undefined;
1868
1868
  }> | undefined;
1869
1869
  };
1870
1870
  rules: {
1871
1871
  binaries: import("../types/issues.ts").IssueSeverity;
1872
1872
  catalog: import("../types/issues.ts").IssueSeverity;
1873
+ catalogReferences: import("../types/issues.ts").IssueSeverity;
1873
1874
  cycles: import("../types/issues.ts").IssueSeverity;
1874
1875
  dependencies: import("../types/issues.ts").IssueSeverity;
1875
1876
  devDependencies: import("../types/issues.ts").IssueSeverity;
@@ -7,7 +7,7 @@ import { isDirectory, isFile } from './fs.js';
7
7
  import { dirname } from './path.js';
8
8
  export const mtimeMatches = (filePath, mtimeMs) => {
9
9
  try {
10
- return fs.statSync(filePath).mtimeMs === mtimeMs;
10
+ return fs.statSync(filePath, { throwIfNoEntry: false })?.mtimeMs === mtimeMs;
11
11
  }
12
12
  catch {
13
13
  return false;
@@ -1,4 +1,5 @@
1
1
  import { core } from 'zod/mini';
2
+ import { substringBefore } from './string.js';
2
3
  const isZodErrorLike = (error) => error instanceof core.$ZodError;
3
4
  export class ConfigurationError extends Error {
4
5
  }
@@ -13,11 +14,11 @@ export const formatCauseMessage = (error, cwd) => {
13
14
  let root = error;
14
15
  while (root.cause instanceof Error)
15
16
  root = root.cause;
16
- return root.message.split('\n', 1)[0].replace(`${cwd}/`, '');
17
+ return substringBefore(root.message, '\n').replace(`${cwd}/`, '');
17
18
  };
18
19
  export const getKnownErrors = (error) => {
19
20
  if (isZodErrorLike(error))
20
- return [...error.issues].map(error => {
21
+ return Array.from(error.issues, error => {
21
22
  let message = error.message;
22
23
  const details = [];
23
24
  if (error.path.length > 0)
@@ -31,12 +31,16 @@ export class FileEntryCache {
31
31
  return existing;
32
32
  let fstat;
33
33
  try {
34
- fstat = fs.statSync(filePath);
34
+ fstat = fs.statSync(filePath, { throwIfNoEntry: false });
35
35
  }
36
36
  catch (error) {
37
37
  this.removeEntry(filePath);
38
38
  return { key: filePath, notFound: true, err: error };
39
39
  }
40
+ if (!fstat) {
41
+ this.removeEntry(filePath);
42
+ return { key: filePath, notFound: true };
43
+ }
40
44
  let meta = this.cache.get(filePath);
41
45
  const cSize = fstat.size;
42
46
  const cTime = fstat.mtime.getTime();
package/dist/util/fs.js CHANGED
@@ -7,7 +7,7 @@ import { LoaderError } from './errors.js';
7
7
  import { extname, join, toPosix } from './path.js';
8
8
  export const isDirectory = (cwdOrPath, name) => {
9
9
  try {
10
- return statSync(name ? join(cwdOrPath, name) : cwdOrPath).isDirectory();
10
+ return statSync(name ? join(cwdOrPath, name) : cwdOrPath, { throwIfNoEntry: false })?.isDirectory() ?? false;
11
11
  }
12
12
  catch {
13
13
  return false;
@@ -15,7 +15,7 @@ export const isDirectory = (cwdOrPath, name) => {
15
15
  };
16
16
  export const isFile = (cwdOrPath, name) => {
17
17
  try {
18
- return statSync(name ? join(cwdOrPath, name) : cwdOrPath).isFile();
18
+ return statSync(name ? join(cwdOrPath, name) : cwdOrPath, { throwIfNoEntry: false })?.isFile() ?? false;
19
19
  }
20
20
  catch {
21
21
  return false;
@@ -4,7 +4,7 @@ export const defaultExcludedIssueTypes = ['nsExports', 'nsTypes', 'cycles'];
4
4
  const defaultAddOnIssueTypes = ['nsExports', 'nsTypes'];
5
5
  const defaultIssueTypes = ISSUE_TYPES.filter(type => !defaultExcludedIssueTypes.includes(type));
6
6
  const normalize = (values) => values.flatMap(value => value.split(','));
7
- export const shorthandDeps = ['dependencies', 'unlisted', 'binaries', 'unresolved', 'catalog'];
7
+ export const shorthandDeps = ['dependencies', 'unlisted', 'binaries', 'unresolved', 'catalog', 'catalogReferences'];
8
8
  export const shorthandExports = ['exports', 'types', 'enumMembers', 'namespaceMembers', 'duplicates'];
9
9
  export const shorthandFiles = ['files'];
10
10
  export const shorthandCycles = ['cycles'];
@@ -22,6 +22,7 @@ export const getIncludedIssueTypes = (options) => {
22
22
  if (options.isProduction) {
23
23
  _exclude.push('devDependencies');
24
24
  _exclude.push('catalog');
25
+ _exclude.push('catalogReferences');
25
26
  }
26
27
  else {
27
28
  if (_include.includes('dependencies'))
@@ -50,7 +50,10 @@ export const setCachedGitignore = (cwd, workspaceDirs, gitignoreFiles, ignores,
50
50
  for (const file of gitignoreFiles) {
51
51
  const abs = path.isAbsolute(file) ? file : path.resolve(cwd, file);
52
52
  try {
53
- mtimes.push(fs.statSync(abs).mtimeMs);
53
+ const stat = fs.statSync(abs, { throwIfNoEntry: false });
54
+ if (!stat)
55
+ continue;
56
+ mtimes.push(stat.mtimeMs);
54
57
  validFiles.push(file);
55
58
  }
56
59
  catch {
@@ -33,8 +33,8 @@ const statDirMtime = (dir) => {
33
33
  let mtime = dirMtimeCache.get(dir);
34
34
  if (mtime === undefined) {
35
35
  try {
36
- const stat = fs.statSync(dir);
37
- mtime = stat.isDirectory() ? stat.mtimeMs : Number.NaN;
36
+ const stat = fs.statSync(dir, { throwIfNoEntry: false });
37
+ mtime = stat?.isDirectory() ? stat.mtimeMs : Number.NaN;
38
38
  }
39
39
  catch {
40
40
  mtime = Number.NaN;
@@ -293,7 +293,9 @@ export async function getGitIgnoredHandler(options, workspaceDirs) {
293
293
  return () => false;
294
294
  const { ignores, unignores } = await _parseFindGitignores(options.cwd, workspaceDirs);
295
295
  gitignoreFingerprint = hashIgnores(ignores, unignores);
296
- const matcher = picomatch(expandIgnorePatterns(ignores), { ignore: expandIgnorePatterns(unignores) });
296
+ const ignoreMatcher = picomatch(expandIgnorePatterns(ignores));
297
+ const unignoreMatcher = unignores.size > 0 ? picomatch(expandIgnorePatterns(unignores)) : undefined;
298
+ const matcher = unignoreMatcher ? (path) => ignoreMatcher(path) && !unignoreMatcher(path) : ignoreMatcher;
297
299
  const cache = new Map();
298
300
  const isGitIgnored = (filePath) => {
299
301
  let result = cache.get(filePath);
@@ -1,6 +1,6 @@
1
1
  import type { IssueType } from '../types/issues.ts';
2
2
  import type { PluginName } from '../types/PluginNames.ts';
3
- type InputType = 'binary' | 'entry' | 'project' | 'config' | 'dependency' | 'deferResolve' | 'deferResolveEntry' | 'alias' | 'ignore';
3
+ type InputType = 'binary' | 'catalog' | 'entry' | 'project' | 'config' | 'dependency' | 'deferResolve' | 'deferResolveEntry' | 'alias' | 'ignore';
4
4
  export interface Input {
5
5
  type: InputType;
6
6
  specifier: string;
@@ -18,6 +18,10 @@ export interface ConfigInput extends Input {
18
18
  containingFilePath?: string;
19
19
  pluginName: PluginName;
20
20
  }
21
+ export interface CatalogInput extends Input {
22
+ type: 'catalog';
23
+ catalogName: string;
24
+ }
21
25
  interface AliasInput extends Input {
22
26
  type: 'alias';
23
27
  prefixes: string[];
@@ -36,6 +40,8 @@ type Options = {
36
40
  export declare const fromBinary: (input: Input) => string;
37
41
  export declare const toBinary: (specifier: string, options?: Options) => Input;
38
42
  export declare const isBinary: (input: Input) => boolean;
43
+ export declare const toCatalog: (specifier: string, catalogName: string) => CatalogInput;
44
+ export declare const isCatalog: (input: Input) => input is CatalogInput;
39
45
  export declare const toEntry: (specifier: string) => Input;
40
46
  export declare const isEntry: (input: Input) => boolean;
41
47
  export declare const toProject: (specifier: string) => Input;
@@ -6,6 +6,12 @@ export const toBinary = (specifier, options = {}) => ({
6
6
  ...options,
7
7
  });
8
8
  export const isBinary = (input) => input.type === 'binary';
9
+ export const toCatalog = (specifier, catalogName) => ({
10
+ type: 'catalog',
11
+ specifier,
12
+ catalogName,
13
+ });
14
+ export const isCatalog = (input) => input.type === 'catalog';
9
15
  export const toEntry = (specifier) => ({ type: 'entry', specifier });
10
16
  export const isEntry = (input) => input.type === 'entry' && !input.production;
11
17
  export const toProject = (specifier) => ({ type: 'project', specifier });