knip 5.46.3 → 5.46.5

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.
@@ -38,7 +38,7 @@ export declare class ConfigurationChief {
38
38
  availableWorkspaceNames: string[];
39
39
  availableWorkspacePkgNames: Set<string>;
40
40
  availableWorkspaceDirs: string[];
41
- workspaceGraph: WorkspaceGraph | undefined;
41
+ workspaceGraph: WorkspaceGraph;
42
42
  includedWorkspaces: Workspace[];
43
43
  resolvedConfigFilePath?: string;
44
44
  rawConfig?: any;
@@ -4,13 +4,14 @@ import { partitionCompilers } from './compilers/index.js';
4
4
  import { DEFAULT_EXTENSIONS, KNIP_CONFIG_LOCATIONS, ROOT_WORKSPACE_NAME } from './constants.js';
5
5
  import { knipConfigurationSchema } from './schema/configuration.js';
6
6
  import { pluginNames } from './types/PluginNames.js';
7
- import { arrayify, compact } from './util/array.js';
7
+ import { arrayify, compact, partition } from './util/array.js';
8
8
  import parsedArgValues from './util/cli-arguments.js';
9
9
  import { createWorkspaceGraph } from './util/create-workspace-graph.js';
10
10
  import { ConfigurationError } from './util/errors.js';
11
11
  import { findFile, isDirectory, isFile, loadJSON } from './util/fs.js';
12
12
  import { getIncludedIssueTypes } from './util/get-included-issue-types.js';
13
13
  import { _dirGlob } from './util/glob.js';
14
+ import { graphSequencer } from './util/graph-sequencer.js';
14
15
  import { defaultRules } from './util/issue-initializers.js';
15
16
  import { _load } from './util/loader.js';
16
17
  import mapWorkspaces from './util/map-workspaces.js';
@@ -63,7 +64,7 @@ export class ConfigurationChief {
63
64
  availableWorkspaceNames = [];
64
65
  availableWorkspacePkgNames = new Set();
65
66
  availableWorkspaceDirs = [];
66
- workspaceGraph;
67
+ workspaceGraph = new Map();
67
68
  includedWorkspaces = [];
68
69
  resolvedConfigFilePath;
69
70
  rawConfig;
@@ -205,7 +206,12 @@ export class ConfigurationChief {
205
206
  !picomatch.isMatch(name, this.ignoredWorkspacePatterns)));
206
207
  }
207
208
  getAvailableWorkspaceNames(names) {
208
- return [...names, ...this.additionalWorkspaceNames].filter(name => !picomatch.isMatch(name, this.ignoredWorkspacePatterns));
209
+ const availableWorkspaceNames = [];
210
+ for (const name of names) {
211
+ if (!picomatch.isMatch(name, this.ignoredWorkspacePatterns))
212
+ availableWorkspaceNames.push(name);
213
+ }
214
+ return availableWorkspaceNames;
209
215
  }
210
216
  getIncludedWorkspaces() {
211
217
  if (this.workspace) {
@@ -237,9 +243,9 @@ export class ConfigurationChief {
237
243
  const workspaceDirsWithDependents = new Set(initialWorkspaces);
238
244
  const addDependents = (dir) => {
239
245
  seen.add(dir);
240
- if (!graph[dir] || graph[dir].size === 0)
246
+ const dirs = graph.get(dir);
247
+ if (!dirs || dirs.size === 0)
241
248
  return;
242
- const dirs = graph[dir];
243
249
  if (initialWorkspaces.some(dir => dirs.has(dir)))
244
250
  workspaceDirsWithDependents.add(dir);
245
251
  for (const dir of dirs)
@@ -281,7 +287,9 @@ export class ConfigurationChief {
281
287
  return this.workspacePackages.get(name)?.manifest;
282
288
  }
283
289
  getWorkspaces() {
284
- return this.includedWorkspaces;
290
+ const sorted = graphSequencer(this.workspaceGraph, this.includedWorkspaces.map(workspace => workspace.dir));
291
+ const [root, rest] = partition(sorted.chunks.flat(), dir => dir === this.cwd);
292
+ return [...root, ...rest.reverse()].map(dir => this.includedWorkspaces.find(w => w.dir === dir));
285
293
  }
286
294
  getDescendentWorkspaces(name) {
287
295
  return this.availableWorkspaceNames
@@ -22,16 +22,14 @@ export class PrincipalFactory {
22
22
  options.compilerOptions = mergePaths(cwd, compilerOptions, paths);
23
23
  if (isFile && compilerOptions.module !== ts.ModuleKind.CommonJS)
24
24
  compilerOptions.moduleResolution ??= ts.ModuleResolutionKind.Bundler;
25
- const principal = this.findReusablePrincipal(cwd, compilerOptions);
25
+ const principal = this.findReusablePrincipal(compilerOptions);
26
26
  if (!isIsolateWorkspaces && principal) {
27
27
  this.linkPrincipal(principal, cwd, compilerOptions, pkgName, compilers);
28
28
  return principal.principal;
29
29
  }
30
30
  return this.addNewPrincipal(options);
31
31
  }
32
- findReusablePrincipal(cwd, compilerOptions) {
33
- if (compilerOptions.rootDir && cwd !== compilerOptions.rootDir)
34
- return;
32
+ findReusablePrincipal(compilerOptions) {
35
33
  const workspacePaths = compilerOptions?.paths ? Object.keys(compilerOptions.paths) : [];
36
34
  const principal = Array.from(this.principals).find(principal => {
37
35
  if (compilerOptions.pathsBasePath && principal.principal.compilerOptions.pathsBasePath)
@@ -63,7 +61,7 @@ export class PrincipalFactory {
63
61
  return principal;
64
62
  }
65
63
  getPrincipals() {
66
- return Array.from(this.principals, p => p.principal).reverse();
64
+ return Array.from(this.principals, p => p.principal);
67
65
  }
68
66
  getPrincipalByPackageName(packageName) {
69
67
  return Array.from(this.principals).find(principal => principal.pkgNames.has(packageName))?.principal;
@@ -123,7 +123,8 @@ export class WorkspaceWorker {
123
123
  return patterns;
124
124
  }
125
125
  getPluginEntryFilePatterns(patterns) {
126
- return [patterns, this.ignoredWorkspacePatterns.map(negate)].flat();
126
+ const negateWorkspaces = patterns.some(pattern => pattern.startsWith('**/')) ? this.negatedWorkspacePatterns : [];
127
+ return [patterns, negateWorkspaces, this.ignoredWorkspacePatterns.map(negate)].flat();
127
128
  }
128
129
  getProductionEntryFilePatterns(negatedTestFilePatterns) {
129
130
  const entry = this.config.entry.filter(hasProductionSuffix);
@@ -2,4 +2,4 @@ import type { SyncCompilerFn } from './types.js';
2
2
  export declare const fencedCodeBlockMatcher: RegExp;
3
3
  export declare const importMatcher: RegExp;
4
4
  export declare const importsWithinScripts: SyncCompilerFn;
5
- export declare const tsScriptBodies: SyncCompilerFn;
5
+ export declare const scriptBodies: SyncCompilerFn;
@@ -11,11 +11,11 @@ export const importsWithinScripts = (text) => {
11
11
  }
12
12
  return scripts.join(';\n');
13
13
  };
14
- const tsScriptExtractor = /<script\b[^>]*lang="ts"[^>]*>(?<body>[\s\S]*?)<\/script>/gm;
15
- export const tsScriptBodies = (text) => {
14
+ const scriptBodyExtractor = /<script\b[^>]*>(?<body>[\s\S]*?)<\/script>/gm;
15
+ export const scriptBodies = (text) => {
16
16
  const scripts = [];
17
17
  let scriptMatch;
18
- while ((scriptMatch = tsScriptExtractor.exec(text))) {
18
+ while ((scriptMatch = scriptBodyExtractor.exec(text))) {
19
19
  if (scriptMatch.groups?.body)
20
20
  scripts.push(scriptMatch.groups.body);
21
21
  }
@@ -1,4 +1,4 @@
1
- import { tsScriptBodies } from './compilers.js';
1
+ import { scriptBodies } from './compilers.js';
2
2
  const condition = (hasDependency) => hasDependency('vue') || hasDependency('nuxt');
3
- const compiler = tsScriptBodies;
3
+ const compiler = scriptBodies;
4
4
  export default { condition, compiler };
@@ -1,3 +1,3 @@
1
1
  import type { WorkspacePackage } from '../types/package-json.js';
2
- export type WorkspaceGraph = Record<string, Set<string>>;
2
+ export type WorkspaceGraph = Map<string, Set<string>>;
3
3
  export declare function createWorkspaceGraph(cwd: string, wsNames: string[], wsPkgNames: Set<string>, wsPackages: Map<string, WorkspacePackage>): WorkspaceGraph;
@@ -1,7 +1,7 @@
1
1
  import { join } from './path.js';
2
2
  const types = ['peerDependencies', 'devDependencies', 'optionalDependencies', 'dependencies'];
3
3
  export function createWorkspaceGraph(cwd, wsNames, wsPkgNames, wsPackages) {
4
- const graph = {};
4
+ const graph = new Map();
5
5
  const packages = Array.from(wsPackages.values());
6
6
  const getWorkspaceDirs = (pkg) => {
7
7
  const dirs = new Set();
@@ -21,7 +21,7 @@ export function createWorkspaceGraph(cwd, wsNames, wsPkgNames, wsPackages) {
21
21
  for (const name of wsNames) {
22
22
  const pkg = wsPackages.get(name);
23
23
  if (pkg)
24
- graph[join(cwd, name)] = getWorkspaceDirs(pkg);
24
+ graph.set(join(cwd, name), getWorkspaceDirs(pkg));
25
25
  }
26
26
  return graph;
27
27
  }
@@ -0,0 +1,9 @@
1
+ type Graph<T> = Map<T, Set<T>>;
2
+ type Groups<T> = T[][];
3
+ interface Result<T> {
4
+ safe: boolean;
5
+ chunks: Groups<T>;
6
+ cycles: Groups<T>;
7
+ }
8
+ export declare function graphSequencer<T>(graph: Graph<T>, includedNodes?: T[]): Result<T>;
9
+ export {};
@@ -0,0 +1,94 @@
1
+ export function graphSequencer(graph, includedNodes = [...graph.keys()]) {
2
+ const reverseGraph = new Map();
3
+ for (const key of graph.keys()) {
4
+ reverseGraph.set(key, []);
5
+ }
6
+ const nodes = new Set(includedNodes);
7
+ const visited = new Set();
8
+ const outDegree = new Map();
9
+ for (const [from, edges] of graph.entries()) {
10
+ outDegree.set(from, 0);
11
+ for (const to of edges) {
12
+ if (nodes.has(from) && nodes.has(to)) {
13
+ changeOutDegree(from, 1);
14
+ reverseGraph.get(to).push(from);
15
+ }
16
+ }
17
+ if (!nodes.has(from)) {
18
+ visited.add(from);
19
+ }
20
+ }
21
+ const chunks = [];
22
+ const cycles = [];
23
+ let safe = true;
24
+ while (nodes.size) {
25
+ const chunk = [];
26
+ let minDegree = Number.MAX_SAFE_INTEGER;
27
+ for (const node of nodes) {
28
+ const degree = outDegree.get(node);
29
+ if (degree === 0) {
30
+ chunk.push(node);
31
+ }
32
+ minDegree = Math.min(minDegree, degree);
33
+ }
34
+ if (minDegree === 0) {
35
+ chunk.forEach(removeNode);
36
+ chunks.push(chunk);
37
+ }
38
+ else {
39
+ const cycleNodes = [];
40
+ for (const node of nodes) {
41
+ const cycle = findCycle(node);
42
+ if (cycle.length) {
43
+ cycles.push(cycle);
44
+ cycle.forEach(removeNode);
45
+ cycleNodes.push(...cycle);
46
+ if (cycle.length > 1) {
47
+ safe = false;
48
+ }
49
+ }
50
+ }
51
+ chunks.push(cycleNodes);
52
+ }
53
+ }
54
+ return { safe, chunks, cycles };
55
+ function changeOutDegree(node, value) {
56
+ const degree = outDegree.get(node) ?? 0;
57
+ outDegree.set(node, degree + value);
58
+ }
59
+ function removeNode(node) {
60
+ for (const from of reverseGraph.get(node)) {
61
+ changeOutDegree(from, -1);
62
+ }
63
+ visited.add(node);
64
+ nodes.delete(node);
65
+ }
66
+ function findCycle(startNode) {
67
+ const queue = [[startNode, [startNode]]];
68
+ const cycleVisited = new Set();
69
+ const cycles = [];
70
+ while (queue.length) {
71
+ const [id, cycle] = queue.shift();
72
+ const nodes = graph.get(id);
73
+ if (!nodes)
74
+ continue;
75
+ for (const to of nodes) {
76
+ if (to === startNode) {
77
+ cycleVisited.add(to);
78
+ cycles.push([...cycle]);
79
+ continue;
80
+ }
81
+ if (visited.has(to) || cycleVisited.has(to)) {
82
+ continue;
83
+ }
84
+ cycleVisited.add(to);
85
+ queue.push([to, [...cycle, to]]);
86
+ }
87
+ }
88
+ if (!cycles.length) {
89
+ return [];
90
+ }
91
+ cycles.sort((a, b) => b.length - a.length);
92
+ return cycles[0];
93
+ }
94
+ }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "5.46.3";
1
+ export declare const version = "5.46.5";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = '5.46.3';
1
+ export const version = '5.46.5';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knip",
3
- "version": "5.46.3",
3
+ "version": "5.46.5",
4
4
  "description": "Find and fix unused files, dependencies and exports in your TypeScript and JavaScript projects",
5
5
  "homepage": "https://knip.dev",
6
6
  "repository": {
@@ -62,7 +62,7 @@
62
62
  "@nodelib/fs.walk": "3.0.1",
63
63
  "@snyk/github-codeowners": "1.1.0",
64
64
  "easy-table": "1.2.0",
65
- "enhanced-resolve": "^5.18.0",
65
+ "enhanced-resolve": "^5.18.1",
66
66
  "fast-glob": "^3.3.3",
67
67
  "jiti": "^2.4.2",
68
68
  "js-yaml": "^4.1.0",
@@ -83,7 +83,7 @@
83
83
  "devDependencies": {
84
84
  "@jest/types": "^29.6.3",
85
85
  "@release-it/bumper": "^7.0.2",
86
- "@types/bun": "1.2.4",
86
+ "@types/bun": "1.2.8",
87
87
  "@types/js-yaml": "^4.0.9",
88
88
  "@types/minimist": "^1.2.5",
89
89
  "@types/picomatch": "3.0.1",