knip 3.7.1 → 3.8.1

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.
@@ -334,8 +334,8 @@ export class ConfigurationChief {
334
334
  const project = workspaceConfig.project ? arrayify(workspaceConfig.project) : defaultConfig.project;
335
335
  const paths = workspaceConfig.paths ?? defaultConfig.paths;
336
336
  const ignore = arrayify(workspaceConfig.ignore);
337
- const ignoreBinaries = arrayify(workspaceConfig.ignoreBinaries);
338
- const ignoreDependencies = arrayify(workspaceConfig.ignoreDependencies);
337
+ const ignoreBinaries = arrayify(workspaceConfig.ignoreBinaries).map(toRegexOrString);
338
+ const ignoreDependencies = arrayify(workspaceConfig.ignoreDependencies).map(toRegexOrString);
339
339
  const isIncludeEntryExports = workspaceConfig.includeEntryExports ?? this.config.isIncludeEntryExports;
340
340
  const plugins = {};
341
341
  for (const [name, pluginConfig] of Object.entries(this.config.rootPluginConfigs)) {
@@ -1,7 +1,7 @@
1
1
  import { isBuiltin } from 'node:module';
2
2
  import { IGNORE_DEFINITELY_TYPED, IGNORED_DEPENDENCIES, IGNORED_GLOBAL_BINARIES, ROOT_WORKSPACE_NAME, } from './constants.js';
3
3
  import { isDefinitelyTyped, getDefinitelyTypedFor, getPackageFromDefinitelyTyped } from './util/modules.js';
4
- import { hasMatch, hasMatchInArray, hasMatchInSet } from './util/regex.js';
4
+ import { hasMatch, hasMatchInArray, hasMatchInSet, toRegexOrString, findKey } from './util/regex.js';
5
5
  export class DependencyDeputy {
6
6
  isProduction;
7
7
  isStrict;
@@ -49,8 +49,8 @@ export class DependencyDeputy {
49
49
  });
50
50
  }
51
51
  addIgnored(ignoreBinaries, ignoreDependencies) {
52
- this.ignoreBinaries = ignoreBinaries;
53
- this.ignoreDependencies = ignoreDependencies;
52
+ this.ignoreBinaries = ignoreBinaries.map(toRegexOrString);
53
+ this.ignoreDependencies = ignoreDependencies.map(toRegexOrString);
54
54
  }
55
55
  getWorkspaceManifest(workspaceName) {
56
56
  return this._manifests.get(workspaceName);
@@ -236,22 +236,32 @@ export class DependencyDeputy {
236
236
  const referencedDependencies = this.referencedDependencies.get(workspaceName);
237
237
  const referencedBinaries = this.referencedBinaries.get(workspaceName);
238
238
  const installedBinaries = this.getInstalledBinaries(workspaceName);
239
+ const ignoredBinaries = this._manifests.get(workspaceName)?.ignoreBinaries ?? [];
240
+ const ignoredDependencies = this._manifests.get(workspaceName)?.ignoreDependencies ?? [];
239
241
  referencedDependencies?.forEach(pkg => {
240
- for (const key of rootIgnoreDependencies.keys()) {
242
+ for (const key of [...ignoredDependencies, ...rootIgnoreDependencies.keys()]) {
241
243
  if ((typeof key === 'string' && key === pkg) || (key instanceof RegExp && key.test(pkg))) {
242
- rootIgnoreDependencies.set(key, rootIgnoreDependencies.get(key) + 1);
243
- return;
244
+ const rootKey = typeof key === 'string' ? key : findKey(rootIgnoreDependencies, key);
245
+ if (rootKey && rootIgnoreDependencies.has(rootKey)) {
246
+ rootIgnoreDependencies.set(rootKey, rootIgnoreDependencies.get(rootKey) + 1);
247
+ return;
248
+ }
244
249
  }
245
250
  }
246
251
  });
247
252
  referencedBinaries?.forEach(binaryName => {
248
- for (const key of rootIgnoreBinaries.keys()) {
253
+ for (const key of [...ignoredBinaries, ...rootIgnoreBinaries.keys()]) {
249
254
  if ((typeof key === 'string' && key === binaryName) || (key instanceof RegExp && key.test(binaryName))) {
250
- rootIgnoreBinaries.set(key, rootIgnoreBinaries.get(key) + 1);
251
- return;
255
+ const rootKey = typeof key === 'string' ? key : findKey(rootIgnoreBinaries, key);
256
+ if (rootKey && rootIgnoreBinaries.has(rootKey)) {
257
+ rootIgnoreBinaries.set(rootKey, rootIgnoreBinaries.get(rootKey) + 1);
258
+ return;
259
+ }
252
260
  }
253
261
  }
254
262
  });
263
+ if (workspaceName === ROOT_WORKSPACE_NAME)
264
+ continue;
255
265
  const dependencies = [
256
266
  ...this.getProductionDependencies(workspaceName),
257
267
  ...this.getDevDependencies(workspaceName),
@@ -261,11 +271,11 @@ export class DependencyDeputy {
261
271
  .filter(packageName => {
262
272
  if (hasMatchInArray(IGNORED_DEPENDENCIES, packageName))
263
273
  return true;
264
- if (workspaceName !== ROOT_WORKSPACE_NAME && this.ignoreDependencies.includes(packageName))
274
+ if (this.ignoreDependencies.includes(packageName))
265
275
  return true;
266
276
  const isReferenced = hasMatchInSet(referencedDependencies, packageName);
267
277
  const isListed = hasMatchInArray(dependencies, packageName) && !hasMatchInArray(peerDependencies, packageName);
268
- return isListed && isReferenced;
278
+ return (isListed && isReferenced) || (!isReferenced && !isListed);
269
279
  })
270
280
  .forEach(identifier => {
271
281
  configurationHints.add({ workspaceName, identifier, type: 'ignoreDependencies' });
@@ -274,11 +284,11 @@ export class DependencyDeputy {
274
284
  .filter(binaryName => {
275
285
  if (hasMatchInArray(IGNORED_GLOBAL_BINARIES, binaryName))
276
286
  return true;
277
- if (workspaceName !== ROOT_WORKSPACE_NAME && this.ignoreBinaries.includes(binaryName))
287
+ if (this.ignoreBinaries.includes(binaryName))
278
288
  return true;
279
289
  const isReferenced = hasMatchInSet(referencedBinaries, binaryName);
280
290
  const isInstalled = hasMatchInArray(Array.from(installedBinaries?.keys() ?? []), binaryName);
281
- return isReferenced && isInstalled;
291
+ return (isReferenced && isInstalled) || (!isInstalled && !isReferenced);
282
292
  })
283
293
  .forEach(identifier => configurationHints.add({ workspaceName, identifier, type: 'ignoreBinaries' }));
284
294
  }
package/dist/cli.js CHANGED
@@ -52,7 +52,8 @@ const run = async () => {
52
52
  if (isObservePerf) {
53
53
  await perfObserver.finalize();
54
54
  console.log('\n' + perfObserver.getTable());
55
- console.log('\nTotal running time:', prettyMilliseconds(perfObserver.getTotalTime()));
55
+ const mem = Math.round((perfObserver.getMemHeapUsage() / 1024 / 1024) * 100) / 100;
56
+ console.log('\nTotal running time:', prettyMilliseconds(perfObserver.getTotalTime()), `(mem: ${mem}MB)`);
56
57
  perfObserver.reset();
57
58
  }
58
59
  if (!noExitCode && totalErrorCount > Number(maxIssues)) {
package/dist/index.js CHANGED
@@ -183,7 +183,7 @@ export const main = async (unresolvedConfiguration) => {
183
183
  }
184
184
  }
185
185
  const principals = factory.getPrincipals();
186
- debugLog('*', `Installed ${principals.length} principals for ${workspaces.length} workspaces`);
186
+ debugLog('*', `Created ${principals.length} programs for ${workspaces.length} workspaces`);
187
187
  const analyzedFiles = new Set();
188
188
  const exportedSymbols = new Map();
189
189
  const importedSymbols = new Map();
@@ -6,6 +6,7 @@ import type { Rules } from './issues.js';
6
6
  export type RawConfiguration = z.infer<typeof ConfigurationValidator>;
7
7
  export type RawPluginConfiguration = z.infer<typeof pluginSchema>;
8
8
  type NormalizedGlob = string[];
9
+ type IgnorePatterns = (string | RegExp)[];
9
10
  export type PluginName = keyof typeof Plugins;
10
11
  export type PluginMap = typeof Plugins;
11
12
  export type EnsuredPluginConfiguration = {
@@ -20,8 +21,8 @@ interface BaseWorkspaceConfiguration {
20
21
  project: NormalizedGlob;
21
22
  paths: Record<string, string[]>;
22
23
  ignore: NormalizedGlob;
23
- ignoreBinaries: string[];
24
- ignoreDependencies: string[];
24
+ ignoreBinaries: IgnorePatterns;
25
+ ignoreDependencies: IgnorePatterns;
25
26
  isIncludeEntryExports: boolean;
26
27
  }
27
28
  export interface WorkspaceConfiguration extends BaseWorkspaceConfiguration, Partial<PluginsConfiguration> {
@@ -32,8 +33,8 @@ export interface Configuration {
32
33
  include: string[];
33
34
  exclude: string[];
34
35
  ignore: NormalizedGlob;
35
- ignoreBinaries: (string | RegExp)[];
36
- ignoreDependencies: (string | RegExp)[];
36
+ ignoreBinaries: IgnorePatterns;
37
+ ignoreDependencies: IgnorePatterns;
37
38
  ignoreExportsUsedInFile: boolean | Partial<Record<IgnorableExport, boolean>>;
38
39
  ignoreWorkspaces: string[];
39
40
  isIncludeEntryExports: boolean;
@@ -1,5 +1,7 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
+ /// <reference types="node" resolution-mode="require"/>
2
3
  import { PerformanceObserver, PerformanceEntry } from 'node:perf_hooks';
4
+ import { memoryUsage } from 'node:process';
3
5
  import type { TimerifyOptions } from 'node:perf_hooks';
4
6
  type Timerify = <T extends (...params: any[]) => any>(fn: T, options?: TimerifyOptions) => T;
5
7
  export declare const timerify: Timerify;
@@ -9,7 +11,10 @@ export declare class Performance {
9
11
  endTime: number;
10
12
  entries: PerformanceEntry[];
11
13
  instanceId?: number;
12
- observer?: PerformanceObserver;
14
+ fnObserver?: PerformanceObserver;
15
+ gcObserver?: PerformanceObserver;
16
+ memoryUsageStart?: ReturnType<typeof memoryUsage>;
17
+ memoryUsageEnd?: ReturnType<typeof memoryUsage>;
13
18
  constructor(isEnabled: boolean);
14
19
  private setMark;
15
20
  private clearMark;
@@ -17,6 +22,7 @@ export declare class Performance {
17
22
  private getEntriesByName;
18
23
  getTable(): string;
19
24
  getTotalTime(): number;
25
+ getMemHeapUsage(): number;
20
26
  finalize(): Promise<void>;
21
27
  reset(): void;
22
28
  }
@@ -1,7 +1,11 @@
1
1
  import { performance, PerformanceObserver, PerformanceEntry } from 'node:perf_hooks';
2
+ import { constants } from 'node:perf_hooks';
3
+ import { memoryUsage } from 'node:process';
2
4
  import EasyTable from 'easy-table';
5
+ import prettyMilliseconds from 'pretty-ms';
3
6
  import Summary from 'summary';
4
7
  import parsedArgValues from './cli-arguments.js';
8
+ import { debugLog } from './debug.js';
5
9
  const { performance: isEnabled = false } = parsedArgValues;
6
10
  export const timerify = fn => (isEnabled ? performance.timerify(fn) : fn);
7
11
  export class Performance {
@@ -10,17 +14,29 @@ export class Performance {
10
14
  endTime = 0;
11
15
  entries = [];
12
16
  instanceId;
13
- observer;
17
+ fnObserver;
18
+ gcObserver;
19
+ memoryUsageStart;
20
+ memoryUsageEnd;
14
21
  constructor(isEnabled) {
15
22
  if (isEnabled) {
16
23
  this.startTime = performance.now();
17
24
  this.instanceId = Math.floor(performance.now() * 100);
18
- this.observer = new PerformanceObserver(items => {
25
+ this.fnObserver = new PerformanceObserver(items => {
19
26
  items.getEntries().forEach(entry => {
20
27
  this.entries.push(entry);
21
28
  });
22
29
  });
23
- this.observer.observe({ entryTypes: ['function'] });
30
+ this.fnObserver.observe({ entryTypes: ['function'] });
31
+ this.gcObserver = new PerformanceObserver(items => {
32
+ for (const item of items.getEntries()) {
33
+ if (item.detail?.kind === constants.NODE_PERFORMANCE_GC_MAJOR) {
34
+ debugLog('*', `GC (after ${prettyMilliseconds(item.startTime)} in ${prettyMilliseconds(item.duration)})`);
35
+ }
36
+ }
37
+ });
38
+ this.gcObserver.observe({ entryTypes: ['gc'] });
39
+ this.memoryUsageStart = memoryUsage();
24
40
  }
25
41
  this.isEnabled = isEnabled;
26
42
  }
@@ -67,14 +83,18 @@ export class Performance {
67
83
  getTotalTime() {
68
84
  return this.endTime - this.startTime;
69
85
  }
86
+ getMemHeapUsage() {
87
+ return (this.memoryUsageEnd?.heapUsed ?? 0) - (this.memoryUsageStart?.heapUsed ?? 0);
88
+ }
70
89
  async finalize() {
71
90
  if (!this.isEnabled)
72
91
  return;
73
92
  this.endTime = performance.now();
93
+ this.memoryUsageEnd = memoryUsage();
74
94
  await this.flush();
75
95
  }
76
96
  reset() {
77
97
  this.entries = [];
78
- this.observer?.disconnect();
98
+ this.fnObserver?.disconnect();
79
99
  }
80
100
  }
@@ -1,4 +1,4 @@
1
- export declare const helpText = "\u2702\uFE0F Find unused files, dependencies and exports in your JavaScript and TypeScript projects\n\nUsage: knip [options]\n\nOptions:\n -c, --config [file] Configuration file path (default: [.]knip.json[c], knip.js, knip.ts or package.json#knip)\n -t, --tsConfig [file] TypeScript configuration path (default: tsconfig.json)\n --production Analyze only production source files (e.g. no tests, devDependencies, exported types)\n --strict Consider only direct dependencies of workspace (not devDependencies, not other workspaces)\n -W, --workspace [dir] Analyze a single workspace (default: analyze all configured workspaces)\n --directory [dir] Run process from a different directory (default: cwd)\n --no-gitignore Don't use .gitignore\n --include Report only 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\n --exports Shortcut for --include exports,nsExports,classMembers,types,nsTypes,enumMembers,duplicates\n --fix Fix issues\n --fix-type Fix only issues of type, can be comma-separated or repeated (2)\n --include-entry-exports Include entry files when reporting unused exports\n --isolate-workspaces Isolated workspaces in monorepo\n -n, --no-progress Don't show dynamic progress updates (automatically enabled in CI environments)\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: symbols, compact, codeowners, json, can be repeated (default: symbols)\n --reporter-options Pass extra options to the reporter (as JSON string, see example)\n --no-config-hints Suppress configuration hints\n --no-exit-code Always exit with code zero (0)\n --max-issues Maximum number of issues before non-zero exit code (default: 0)\n -d, --debug Show debug output\n --performance Measure count and running time of expensive functions and display stats table\n -h, --help Print this help text\n -V, --version Print version\n\n(1) Issue types: files, dependencies, unlisted, unresolved, exports, nsExports, classMembers, types, nsTypes, enumMembers, duplicates\n(2) Fixable issue types: dependencies, exports, types\n\nExamples:\n\n$ knip\n$ knip --production\n$ knip --workspace packages/client --include files,dependencies\n$ knip -c ./config/knip.json --reporter compact\n$ knip --reporter codeowners --reporter-options '{\"path\":\".github/CODEOWNERS\"}'\n\nWebsite: https://knip.dev";
1
+ export declare const helpText = "\u2702\uFE0F Find unused files, dependencies and exports in your JavaScript and TypeScript projects\n\nUsage: knip [options]\n\nOptions:\n -c, --config [file] Configuration file path (default: [.]knip.json[c], knip.js, knip.ts or package.json#knip)\n -t, --tsConfig [file] TypeScript configuration path (default: tsconfig.json)\n --production Analyze only production source files (e.g. no tests, devDependencies, exported types)\n --strict Consider only direct dependencies of workspace (not devDependencies, not other workspaces)\n -W, --workspace [dir] Analyze a single workspace (default: analyze all configured workspaces)\n --directory [dir] Run process from a different directory (default: cwd)\n --no-gitignore Don't use .gitignore\n --include Report only 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\n --exports Shortcut for --include exports,nsExports,classMembers,types,nsTypes,enumMembers,duplicates\n --fix Fix issues\n --fix-type Fix only issues of type, can be comma-separated or repeated (2)\n --include-entry-exports Include entry files when reporting unused exports\n --isolate-workspaces Isolate workspaces into separate programs (default: false)\n -n, --no-progress Don't show dynamic progress updates (automatically enabled in CI environments)\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: symbols, compact, codeowners, json, can be repeated (default: symbols)\n --reporter-options Pass extra options to the reporter (as JSON string, see example)\n --no-config-hints Suppress configuration hints\n --no-exit-code Always exit with code zero (0)\n --max-issues Maximum number of issues before non-zero exit code (default: 0)\n -d, --debug Show debug output\n --performance Measure count and running time of expensive functions and display stats table\n -h, --help Print this help text\n -V, --version Print version\n\n(1) Issue types: files, dependencies, unlisted, unresolved, exports, nsExports, classMembers, types, nsTypes, enumMembers, duplicates\n(2) Fixable issue types: dependencies, exports, types\n\nExamples:\n\n$ knip\n$ knip --production\n$ knip --workspace packages/client --include files,dependencies\n$ knip -c ./config/knip.json --reporter compact\n$ knip --reporter codeowners --reporter-options '{\"path\":\".github/CODEOWNERS\"}'\n\nWebsite: https://knip.dev";
2
2
  declare const _default: {
3
3
  config: string | undefined;
4
4
  debug: boolean | undefined;
@@ -18,7 +18,7 @@ Options:
18
18
  --fix Fix issues
19
19
  --fix-type Fix only issues of type, can be comma-separated or repeated (2)
20
20
  --include-entry-exports Include entry files when reporting unused exports
21
- --isolate-workspaces Isolated workspaces in monorepo
21
+ --isolate-workspaces Isolate workspaces into separate programs (default: false)
22
22
  -n, --no-progress Don't show dynamic progress updates (automatically enabled in CI environments)
23
23
  --preprocessor Preprocess the results before providing it to the reporter(s), can be repeated
24
24
  --preprocessor-options Pass extra options to the preprocessor (as JSON string, see --reporter-options example)
@@ -2,3 +2,4 @@ export declare const toRegexOrString: (value: string | RegExp) => string | RegEx
2
2
  export declare const hasMatch: (haystack: undefined | (string | RegExp)[], needle: string) => boolean | undefined;
3
3
  export declare const hasMatchInSet: (haystack: undefined | Set<string>, needle: string | RegExp) => boolean | undefined;
4
4
  export declare const hasMatchInArray: (haystack: string[], needle: string | RegExp) => boolean;
5
+ export declare const findKey: (map: Map<string | RegExp, unknown>, key: RegExp) => string | RegExp | undefined;
@@ -2,3 +2,4 @@ export const toRegexOrString = (value) => typeof value === 'string' && /[*+\\(|{
2
2
  export const hasMatch = (haystack, needle) => haystack && haystack.some(n => (typeof n === 'string' ? n === needle : n.test(needle)));
3
3
  export const hasMatchInSet = (haystack, needle) => haystack && (typeof needle === 'string' ? haystack.has(needle) : [...haystack].some(n => needle.test(n)));
4
4
  export const hasMatchInArray = (haystack, needle) => typeof needle === 'string' ? haystack.includes(needle) : haystack.some(n => needle.test(n));
5
+ export const findKey = (map, key) => [...map.keys()].find(k => k instanceof RegExp && k.source === key.source);
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "3.7.1";
1
+ export declare const version = "3.8.1";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '3.7.1';
1
+ export const version = '3.8.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "3.7.1",
3
+ "version": "3.8.1",
4
4
  "description": "Find unused files, dependencies and exports in your TypeScript and JavaScript projects",
5
5
  "homepage": "https://knip.dev",
6
6
  "repository": {
@@ -58,7 +58,7 @@
58
58
  "@npmcli/package-json": "5.0.0",
59
59
  "@pkgjs/parseargs": "0.11.0",
60
60
  "@pnpm/logger": "5.0.0",
61
- "@pnpm/workspace.pkgs-graph": "^2.0.11",
61
+ "@pnpm/workspace.pkgs-graph": "^2.0.12",
62
62
  "@snyk/github-codeowners": "1.1.0",
63
63
  "chalk": "^5.3.0",
64
64
  "easy-table": "1.2.0",
@@ -94,8 +94,8 @@
94
94
  "c8": "8.0.1",
95
95
  "eslint": "^8.55.0",
96
96
  "playwright": "^1.40.1",
97
- "prettier": "^3.1.0",
98
- "release-it": "^17.0.0",
97
+ "prettier": "^3.1.1",
98
+ "release-it": "^17.0.1",
99
99
  "tsx": "^4.6.2",
100
100
  "type-fest": "^4.8.3",
101
101
  "typescript": "5.3.3"