knip 6.24.0 → 6.25.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 (92) hide show
  1. package/dist/ConfigurationChief.d.ts +13 -1
  2. package/dist/ConfigurationChief.js +3 -0
  3. package/dist/ProjectPrincipal.d.ts +2 -0
  4. package/dist/ProjectPrincipal.js +4 -1
  5. package/dist/binaries/bash-parser.js +55 -83
  6. package/dist/binaries/index.js +3 -0
  7. package/dist/compilers/index.d.ts +29 -5
  8. package/dist/constants.d.ts +5 -1
  9. package/dist/constants.js +5 -0
  10. package/dist/graph/analyze.js +37 -2
  11. package/dist/graph/build.js +9 -0
  12. package/dist/graph-explorer/explorer.d.ts +1 -0
  13. package/dist/graph-explorer/explorer.js +2 -0
  14. package/dist/graph-explorer/operations/find-all-cycles.d.ts +3 -0
  15. package/dist/graph-explorer/operations/find-all-cycles.js +43 -0
  16. package/dist/graph-explorer/operations/find-cycles.js +3 -9
  17. package/dist/graph-explorer/utils.d.ts +10 -1
  18. package/dist/graph-explorer/utils.js +53 -0
  19. package/dist/plugins/bun/index.js +28 -14
  20. package/dist/plugins/eve/index.d.ts +3 -0
  21. package/dist/plugins/eve/index.js +22 -0
  22. package/dist/plugins/execa/visitors/execa.js +10 -18
  23. package/dist/plugins/fumadocs/index.d.ts +3 -0
  24. package/dist/plugins/fumadocs/index.js +18 -0
  25. package/dist/plugins/graphql-codegen/index.js +9 -7
  26. package/dist/plugins/index.d.ts +2 -0
  27. package/dist/plugins/index.js +4 -0
  28. package/dist/plugins/jest/index.js +13 -3
  29. package/dist/plugins/next/index.js +23 -10
  30. package/dist/plugins/node/index.js +4 -1
  31. package/dist/plugins/pnpm/index.js +21 -1
  32. package/dist/plugins/pnpm/types.d.ts +6 -0
  33. package/dist/plugins/pnpm/types.js +1 -0
  34. package/dist/plugins/prettier/index.js +19 -3
  35. package/dist/plugins/prettier/types.d.ts +6 -1
  36. package/dist/plugins/rspack/index.js +5 -0
  37. package/dist/plugins/serverless-framework/index.js +12 -1
  38. package/dist/plugins/serverless-framework/types.d.ts +5 -2
  39. package/dist/plugins/storybook/index.js +7 -0
  40. package/dist/plugins/typedoc/index.js +9 -3
  41. package/dist/plugins/typedoc/types.d.ts +2 -0
  42. package/dist/plugins/vite/visitors/importMetaGlob.js +1 -8
  43. package/dist/plugins/webpack/visitors/requireContext.js +2 -13
  44. package/dist/plugins/yarn/index.js +13 -4
  45. package/dist/plugins/yarn/types.d.ts +10 -0
  46. package/dist/plugins/yarn/types.js +1 -0
  47. package/dist/plugins/zx/visitors/zx.js +4 -4
  48. package/dist/reporters/cycles.d.ts +3 -0
  49. package/dist/reporters/cycles.js +79 -0
  50. package/dist/reporters/index.d.ts +1 -0
  51. package/dist/reporters/index.js +2 -0
  52. package/dist/reporters/json.d.ts +3 -0
  53. package/dist/reporters/json.js +2 -1
  54. package/dist/reporters/util/util.d.ts +3 -1
  55. package/dist/reporters/util/util.js +5 -1
  56. package/dist/schema/configuration.d.ts +40 -6
  57. package/dist/schema/configuration.js +6 -0
  58. package/dist/schema/plugins.d.ts +10 -0
  59. package/dist/schema/plugins.js +2 -0
  60. package/dist/types/PluginNames.d.ts +2 -2
  61. package/dist/types/PluginNames.js +2 -0
  62. package/dist/types/config.d.ts +9 -0
  63. package/dist/types/issues.d.ts +3 -0
  64. package/dist/types/module-graph.d.ts +7 -0
  65. package/dist/types/package-json.d.ts +4 -1
  66. package/dist/typescript/ast-nodes.d.ts +3 -1
  67. package/dist/typescript/ast-nodes.js +9 -1
  68. package/dist/typescript/get-imports-and-exports.js +10 -0
  69. package/dist/typescript/glob-imports.d.ts +3 -0
  70. package/dist/typescript/glob-imports.js +49 -0
  71. package/dist/typescript/resolve-module-names.d.ts +2 -0
  72. package/dist/typescript/resolve-module-names.js +35 -1
  73. package/dist/typescript/visitors/calls.js +4 -6
  74. package/dist/typescript/visitors/imports.js +8 -8
  75. package/dist/typescript/visitors/script-visitors.js +4 -4
  76. package/dist/typescript/visitors/walk.js +2 -3
  77. package/dist/util/cli-arguments.d.ts +2 -1
  78. package/dist/util/cli-arguments.js +4 -2
  79. package/dist/util/create-options.d.ts +32 -5
  80. package/dist/util/create-options.js +4 -1
  81. package/dist/util/get-included-issue-types.d.ts +1 -0
  82. package/dist/util/get-included-issue-types.js +4 -2
  83. package/dist/util/issue-initializers.js +1 -1
  84. package/dist/util/load-tsconfig.js +17 -4
  85. package/dist/util/module-graph.js +1 -0
  86. package/dist/util/modules.js +25 -4
  87. package/dist/util/scripts.d.ts +7 -0
  88. package/dist/util/scripts.js +75 -0
  89. package/dist/version.d.ts +1 -1
  90. package/dist/version.js +1 -1
  91. package/package.json +1 -1
  92. package/schema.json +40 -1
@@ -207,6 +207,11 @@ export declare class ConfigurationChief {
207
207
  entry?: string | string[] | undefined;
208
208
  project?: string | string[] | undefined;
209
209
  } | undefined;
210
+ eve?: string | boolean | string[] | {
211
+ config?: string | string[] | undefined;
212
+ entry?: string | string[] | undefined;
213
+ project?: string | string[] | undefined;
214
+ } | undefined;
210
215
  execa?: string | boolean | string[] | {
211
216
  config?: string | string[] | undefined;
212
217
  entry?: string | string[] | undefined;
@@ -227,6 +232,11 @@ export declare class ConfigurationChief {
227
232
  entry?: string | string[] | undefined;
228
233
  project?: string | string[] | undefined;
229
234
  } | undefined;
235
+ fumadocs?: string | boolean | string[] | {
236
+ config?: string | string[] | undefined;
237
+ entry?: string | string[] | undefined;
238
+ project?: string | string[] | undefined;
239
+ } | undefined;
230
240
  gatsby?: string | boolean | string[] | {
231
241
  config?: string | string[] | undefined;
232
242
  entry?: string | string[] | undefined;
@@ -852,7 +862,7 @@ export declare class ConfigurationChief {
852
862
  ignoreMembers?: (string | RegExp)[] | undefined;
853
863
  ignoreUnresolved?: (string | RegExp)[] | undefined;
854
864
  ignoreExportsUsedInFile?: boolean | Record<string, boolean | undefined> | undefined;
855
- ignoreIssues?: Record<string, ("binaries" | "catalog" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[]> | undefined;
865
+ ignoreIssues?: Record<string, ("binaries" | "catalog" | "cycles" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[]> | undefined;
856
866
  includeEntryExports?: boolean | undefined;
857
867
  };
858
868
  getIgnores(workspaceName: string): {
@@ -899,10 +909,12 @@ export declare class ConfigurationChief {
899
909
  drizzle?: (boolean | import("./types/config.ts").EnsuredPluginConfiguration) | undefined;
900
910
  eleventy?: (boolean | import("./types/config.ts").EnsuredPluginConfiguration) | undefined;
901
911
  eslint?: (boolean | import("./types/config.ts").EnsuredPluginConfiguration) | undefined;
912
+ eve?: (boolean | import("./types/config.ts").EnsuredPluginConfiguration) | undefined;
902
913
  execa?: (boolean | import("./types/config.ts").EnsuredPluginConfiguration) | undefined;
903
914
  expo?: (boolean | import("./types/config.ts").EnsuredPluginConfiguration) | undefined;
904
915
  "expressive-code"?: (boolean | import("./types/config.ts").EnsuredPluginConfiguration) | undefined;
905
916
  fast?: (boolean | import("./types/config.ts").EnsuredPluginConfiguration) | undefined;
917
+ fumadocs?: (boolean | import("./types/config.ts").EnsuredPluginConfiguration) | undefined;
906
918
  gatsby?: (boolean | import("./types/config.ts").EnsuredPluginConfiguration) | undefined;
907
919
  "github-action"?: (boolean | import("./types/config.ts").EnsuredPluginConfiguration) | undefined;
908
920
  "github-actions"?: (boolean | import("./types/config.ts").EnsuredPluginConfiguration) | undefined;
@@ -34,6 +34,7 @@ const defaultConfig = {
34
34
  ignoreBinaries: [],
35
35
  ignoreDependencies: [],
36
36
  ignoreFiles: [],
37
+ cycles: {},
37
38
  ignoreIssues: {},
38
39
  ignoreMembers: [],
39
40
  ignoreUnresolved: [],
@@ -103,6 +104,7 @@ export class ConfigurationChief {
103
104
  const ignoreMembers = rawConfig.ignoreMembers ?? [];
104
105
  const ignoreUnresolved = rawConfig.ignoreUnresolved ?? [];
105
106
  const ignoreExportsUsedInFile = rawConfig.ignoreExportsUsedInFile ?? false;
107
+ const cycles = rawConfig.cycles ?? {};
106
108
  const ignoreIssues = rawConfig.ignoreIssues ?? {};
107
109
  const ignoreWorkspaces = rawConfig.ignoreWorkspaces ?? defaultConfig.ignoreWorkspaces;
108
110
  const isIncludeEntryExports = rawConfig.includeEntryExports ?? this.isIncludeEntryExports;
@@ -116,6 +118,7 @@ export class ConfigurationChief {
116
118
  return {
117
119
  ignore,
118
120
  ignoreFiles,
121
+ cycles,
119
122
  ignoreBinaries,
120
123
  ignoreDependencies,
121
124
  ignoreMembers,
@@ -4,6 +4,7 @@ import type { AsyncCompilers, SyncCompilers } from './compilers/types.ts';
4
4
  import type { GetImportsAndExportsOptions, IgnoreExportsUsedInFile, PluginVisitorContext, PluginVisitorObject } from './types/config.ts';
5
5
  import type { FileNode, ModuleGraph } from './types/module-graph.ts';
6
6
  import type { Paths } from './types/project.ts';
7
+ import type { ResolveGlobPattern } from './typescript/resolve-module-names.ts';
7
8
  import { SourceFileManager } from './typescript/SourceFileManager.ts';
8
9
  import type { MainOptions } from './util/create-options.ts';
9
10
  import type { ToSourceFilePath, WorkspaceManifestHandler } from './util/to-source-path.ts';
@@ -27,6 +28,7 @@ export declare class ProjectPrincipal {
27
28
  private findWorkspaceManifestImports;
28
29
  fileManager: SourceFileManager;
29
30
  private resolveModule;
31
+ resolveGlobPattern: ResolveGlobPattern;
30
32
  resolvedFiles: Set<string>;
31
33
  deletedFiles: Set<string>;
32
34
  private onPathAdded;
@@ -6,7 +6,7 @@ import { DEFAULT_EXTENSIONS } from './constants.js';
6
6
  import { _getImportsAndExports } from './typescript/get-imports-and-exports.js';
7
7
  import { createBunShellVisitor } from './typescript/visitors/script-visitors.js';
8
8
  import { buildVisitor } from './typescript/visitors/walk.js';
9
- import { createCustomModuleResolver } from './typescript/resolve-module-names.js';
9
+ import { createCustomModuleResolver, createGlobAliasResolver } from './typescript/resolve-module-names.js';
10
10
  import { SourceFileManager } from './typescript/SourceFileManager.js';
11
11
  import { compact } from './util/array.js';
12
12
  import { timerify } from './util/Performance.js';
@@ -21,6 +21,7 @@ export class ProjectPrincipal {
21
21
  sourceText: '',
22
22
  addScript: () => { },
23
23
  addImport: () => { },
24
+ addImportGlob: () => { },
24
25
  markExportRegistered: () => { },
25
26
  };
26
27
  pluginVisitorObjects = [];
@@ -37,6 +38,7 @@ export class ProjectPrincipal {
37
38
  findWorkspaceManifestImports;
38
39
  fileManager;
39
40
  resolveModule = () => undefined;
41
+ resolveGlobPattern = pattern => [pattern];
40
42
  resolvedFiles = new Set();
41
43
  deletedFiles = new Set();
42
44
  onPathAdded;
@@ -90,6 +92,7 @@ export class ProjectPrincipal {
90
92
  const scopedPaths = this.paths.size > 0 ? Array.from(this.paths, ([scope, paths]) => ({ scope, paths })) : undefined;
91
93
  const scopedRootDirs = this.rootDirs.size > 0 ? Array.from(this.rootDirs, ([scope, rootDirs]) => ({ scope, rootDirs })) : undefined;
92
94
  this.resolveModule = createCustomModuleResolver({ scopedPaths, scopedRootDirs }, customCompilerExtensions, this.toSourceFilePath, this.findWorkspaceManifestImports, this.tsConfigFile);
95
+ this.resolveGlobPattern = createGlobAliasResolver(scopedPaths);
93
96
  }
94
97
  readFile(filePath) {
95
98
  return this.fileManager.readFile(filePath);
@@ -5,6 +5,7 @@ import { toBinary, toDeferResolve } from '../util/input.js';
5
5
  import { extractBinary, isValidBinary } from '../util/modules.js';
6
6
  import { relative } from '../util/path.js';
7
7
  import { truncate } from '../util/string.js';
8
+ import { walkCommands } from '../util/scripts.js';
8
9
  import { resolve as fallbackResolve } from './fallback.js';
9
10
  import KnownResolvers from './resolvers/index.js';
10
11
  import { resolve as resolverFromPlugins } from './plugins.js';
@@ -46,93 +47,64 @@ export const getDependenciesFromScript = (script, options) => {
46
47
  const processScript = (s) => {
47
48
  collectFunctionNames(s.commands);
48
49
  const pending = [];
49
- const mainDeps = getDependenciesFromStatements(s.commands, pending);
50
+ const mainDeps = [];
51
+ for (const statement of s.commands) {
52
+ for (const command of walkCommands(statement.command))
53
+ mainDeps.push(...processCommand(command, pending));
54
+ }
50
55
  const expansionDeps = pending.flatMap(inner => processScript(inner));
51
56
  return [...mainDeps, ...expansionDeps];
52
57
  };
53
- const getDependenciesFromStatements = (statements, pending) => statements.flatMap(stmt => getDependenciesFromNode(stmt.command, pending));
54
- const getDependenciesFromNode = (node, pending) => {
55
- switch (node.type) {
56
- case 'Command': {
57
- const text = node.name?.value;
58
- const binary = text ? extractBinary(text) : text;
59
- if (node.name)
60
- collectExpansionScripts(node.name, pending);
61
- for (const prefix of node.prefix)
62
- if (prefix.value)
63
- collectExpansionScripts(prefix.value, pending);
64
- for (const suffix of node.suffix)
65
- collectExpansionScripts(suffix, pending);
66
- if (!binary || binary === '.' || binary === 'source' || binary === '[')
67
- return [];
68
- if (binary.startsWith('-') || binary.startsWith('..'))
69
- return [];
70
- if (definedFunctions.has(binary))
71
- return [];
72
- const args = node.suffix.map(w => w.value);
73
- if (['!', 'test'].includes(binary))
74
- return fromArgs(args);
75
- const fromNodeOptions = node.prefix
76
- .filter(a => a.name === 'NODE_OPTIONS' && a.value)
77
- .map(a => a.value.value)
78
- .map(arg => parseNodeArgs(arg.split(' ')))
79
- .filter(args => args.require)
80
- .flatMap(arg => arg.require)
81
- .map(id => toDeferResolve(id));
82
- if (binary in KnownResolvers) {
83
- const resolver = KnownResolvers[binary];
84
- return resolver(binary, args, { ...options, fromArgs });
85
- }
86
- if (pluginArgsMap.has(binary)) {
87
- return [...resolverFromPlugins(binary, args, { ...options, fromArgs }), ...fromNodeOptions];
88
- }
89
- if (spawningBinaries.includes(binary)) {
90
- const rest = node.suffix
91
- .filter(w => w.text !== '--')
92
- .map(w => w.text)
93
- .join(' ');
94
- return [toBinary(binary), ...getDependenciesFromScript(rest, options)];
95
- }
96
- if (binary in Plugins) {
97
- const inputs = fallbackResolve(binary, args, { ...options, fromArgs });
98
- if (options.knownBinsOnly)
99
- for (const input of inputs)
100
- input.optional = true;
101
- return [...inputs, ...fromNodeOptions];
102
- }
103
- if (options.knownBinsOnly && !text?.startsWith('.'))
104
- return [];
105
- return [...fallbackResolve(binary, args, { ...options, fromArgs }), ...fromNodeOptions];
106
- }
107
- case 'AndOr':
108
- case 'Pipeline':
109
- return node.commands.flatMap(n => getDependenciesFromNode(n, pending));
110
- case 'If':
111
- return [
112
- ...getDependenciesFromStatements(node.clause.commands, pending),
113
- ...getDependenciesFromStatements(node.then.commands, pending),
114
- ...(node.else ? getDependenciesFromNode(node.else, pending) : []),
115
- ];
116
- case 'While':
117
- return [
118
- ...getDependenciesFromStatements(node.clause.commands, pending),
119
- ...getDependenciesFromStatements(node.body.commands, pending),
120
- ];
121
- case 'For':
122
- case 'Select':
123
- case 'Subshell':
124
- case 'BraceGroup':
125
- return getDependenciesFromStatements(node.body.commands, pending);
126
- case 'CompoundList':
127
- return getDependenciesFromStatements(node.commands, pending);
128
- case 'Function':
129
- case 'Coproc':
130
- return getDependenciesFromNode(node.body, pending);
131
- case 'Statement':
132
- return getDependenciesFromNode(node.command, pending);
133
- default:
134
- return [];
58
+ const processCommand = (node, pending) => {
59
+ const text = node.name?.value;
60
+ const binary = text ? extractBinary(text) : text;
61
+ if (node.name)
62
+ collectExpansionScripts(node.name, pending);
63
+ for (const prefix of node.prefix)
64
+ if (prefix.value)
65
+ collectExpansionScripts(prefix.value, pending);
66
+ for (const suffix of node.suffix)
67
+ collectExpansionScripts(suffix, pending);
68
+ if (!binary || binary === '.' || binary === 'source' || binary === '[')
69
+ return [];
70
+ if (binary.startsWith('-') || binary.startsWith('..'))
71
+ return [];
72
+ if (definedFunctions.has(binary))
73
+ return [];
74
+ const args = node.suffix.map(w => w.value);
75
+ if (['!', 'test'].includes(binary))
76
+ return fromArgs(args);
77
+ const fromNodeOptions = node.prefix
78
+ .filter(a => a.name === 'NODE_OPTIONS' && a.value)
79
+ .map(a => a.value.value)
80
+ .map(arg => parseNodeArgs(arg.split(' ')))
81
+ .filter(args => args.require)
82
+ .flatMap(arg => arg.require)
83
+ .map(id => toDeferResolve(id));
84
+ if (binary in KnownResolvers) {
85
+ const resolver = KnownResolvers[binary];
86
+ return resolver(binary, args, { ...options, fromArgs });
135
87
  }
88
+ if (pluginArgsMap.has(binary)) {
89
+ return [...resolverFromPlugins(binary, args, { ...options, fromArgs }), ...fromNodeOptions];
90
+ }
91
+ if (spawningBinaries.includes(binary)) {
92
+ const rest = node.suffix
93
+ .filter(w => w.text !== '--')
94
+ .map(w => w.text)
95
+ .join(' ');
96
+ return [toBinary(binary), ...getDependenciesFromScript(rest, options)];
97
+ }
98
+ if (binary in Plugins) {
99
+ const inputs = fallbackResolve(binary, args, { ...options, fromArgs });
100
+ if (options.knownBinsOnly)
101
+ for (const input of inputs)
102
+ input.optional = true;
103
+ return [...inputs, ...fromNodeOptions];
104
+ }
105
+ if (options.knownBinsOnly && !text?.startsWith('.'))
106
+ return [];
107
+ return [...fallbackResolve(binary, args, { ...options, fromArgs }), ...fromNodeOptions];
136
108
  };
137
109
  try {
138
110
  const parsed = parse(script);
@@ -1,3 +1,4 @@
1
+ import { SCRIPT_INTERPOLATION } from '../constants.js';
1
2
  import { fromBinary, isBinary, isDependency } from '../util/input.js';
2
3
  import { timerify } from '../util/Performance.js';
3
4
  import { getDependenciesFromScript } from './bash-parser.js';
@@ -8,6 +9,8 @@ const getInputsFromScripts = (npmScripts, options) => {
8
9
  for (const input of results) {
9
10
  if (!input.specifier)
10
11
  continue;
12
+ if (input.specifier.includes(SCRIPT_INTERPOLATION))
13
+ continue;
11
14
  if (isDependency(input) && input.specifier.startsWith('http'))
12
15
  continue;
13
16
  if (isBinary(input) && !/^\b/.test(fromBinary(input)))
@@ -153,6 +153,11 @@ export declare const partitionCompilers: (rawLocalConfig: RawConfiguration) => {
153
153
  entry?: string | string[] | undefined;
154
154
  project?: string | string[] | undefined;
155
155
  } | undefined;
156
+ eve?: string | boolean | string[] | {
157
+ config?: string | string[] | undefined;
158
+ entry?: string | string[] | undefined;
159
+ project?: string | string[] | undefined;
160
+ } | undefined;
156
161
  execa?: string | boolean | string[] | {
157
162
  config?: string | string[] | undefined;
158
163
  entry?: string | string[] | undefined;
@@ -173,6 +178,11 @@ export declare const partitionCompilers: (rawLocalConfig: RawConfiguration) => {
173
178
  entry?: string | string[] | undefined;
174
179
  project?: string | string[] | undefined;
175
180
  } | undefined;
181
+ fumadocs?: string | boolean | string[] | {
182
+ config?: string | string[] | undefined;
183
+ entry?: string | string[] | undefined;
184
+ project?: string | string[] | undefined;
185
+ } | undefined;
176
186
  gatsby?: string | boolean | string[] | {
177
187
  config?: string | string[] | undefined;
178
188
  entry?: string | string[] | undefined;
@@ -789,7 +799,7 @@ export declare const partitionCompilers: (rawLocalConfig: RawConfiguration) => {
789
799
  project?: string | string[] | undefined;
790
800
  } | undefined;
791
801
  $schema?: string | undefined;
792
- rules?: Partial<Record<"binaries" | "catalog" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved", "error" | "off" | "warn">> | undefined;
802
+ rules?: Partial<Record<"binaries" | "catalog" | "cycles" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved", "error" | "off" | "warn">> | undefined;
793
803
  entry?: string | string[] | undefined;
794
804
  project?: string | string[] | undefined;
795
805
  paths?: Record<string, string[]> | undefined;
@@ -800,15 +810,19 @@ export declare const partitionCompilers: (rawLocalConfig: RawConfiguration) => {
800
810
  ignoreMembers?: (string | RegExp)[] | undefined;
801
811
  ignoreUnresolved?: (string | RegExp)[] | undefined;
802
812
  ignoreExportsUsedInFile?: boolean | Record<string, boolean | undefined> | undefined;
803
- ignoreIssues?: Record<string, ("binaries" | "catalog" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[]> | undefined;
813
+ ignoreIssues?: Record<string, ("binaries" | "catalog" | "cycles" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[]> | undefined;
814
+ cycles?: {
815
+ allow?: string[][] | undefined;
816
+ dynamicImports?: boolean | undefined;
817
+ } | undefined;
804
818
  ignoreWorkspaces?: string[] | undefined;
805
819
  includeEntryExports?: boolean | undefined;
806
820
  compilers?: Record<string, true | CompilerAsync | CompilerSync> | undefined;
807
821
  tags?: string[] | undefined;
808
822
  treatConfigHintsAsErrors?: boolean | undefined;
809
823
  treatTagHintsAsErrors?: boolean | undefined;
810
- include?: ("binaries" | "catalog" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[] | undefined;
811
- exclude?: ("binaries" | "catalog" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[] | undefined;
824
+ include?: ("binaries" | "catalog" | "cycles" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[] | undefined;
825
+ exclude?: ("binaries" | "catalog" | "cycles" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[] | undefined;
812
826
  workspaces?: Record<string, {
813
827
  angular?: string | boolean | string[] | {
814
828
  config?: string | string[] | undefined;
@@ -960,6 +974,11 @@ export declare const partitionCompilers: (rawLocalConfig: RawConfiguration) => {
960
974
  entry?: string | string[] | undefined;
961
975
  project?: string | string[] | undefined;
962
976
  } | undefined;
977
+ eve?: string | boolean | string[] | {
978
+ config?: string | string[] | undefined;
979
+ entry?: string | string[] | undefined;
980
+ project?: string | string[] | undefined;
981
+ } | undefined;
963
982
  execa?: string | boolean | string[] | {
964
983
  config?: string | string[] | undefined;
965
984
  entry?: string | string[] | undefined;
@@ -980,6 +999,11 @@ export declare const partitionCompilers: (rawLocalConfig: RawConfiguration) => {
980
999
  entry?: string | string[] | undefined;
981
1000
  project?: string | string[] | undefined;
982
1001
  } | undefined;
1002
+ fumadocs?: string | boolean | string[] | {
1003
+ config?: string | string[] | undefined;
1004
+ entry?: string | string[] | undefined;
1005
+ project?: string | string[] | undefined;
1006
+ } | undefined;
983
1007
  gatsby?: string | boolean | string[] | {
984
1008
  config?: string | string[] | undefined;
985
1009
  entry?: string | string[] | undefined;
@@ -1605,7 +1629,7 @@ export declare const partitionCompilers: (rawLocalConfig: RawConfiguration) => {
1605
1629
  ignoreMembers?: (string | RegExp)[] | undefined;
1606
1630
  ignoreUnresolved?: (string | RegExp)[] | undefined;
1607
1631
  ignoreExportsUsedInFile?: boolean | Record<string, boolean | undefined> | undefined;
1608
- ignoreIssues?: Record<string, ("binaries" | "catalog" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[]> | undefined;
1632
+ ignoreIssues?: Record<string, ("binaries" | "catalog" | "cycles" | "dependencies" | "devDependencies" | "duplicates" | "enumMembers" | "exports" | "files" | "namespaceMembers" | "nsExports" | "nsTypes" | "optionalPeerDependencies" | "types" | "unlisted" | "unresolved")[]> | undefined;
1609
1633
  includeEntryExports?: boolean | undefined;
1610
1634
  }> | undefined;
1611
1635
  syncCompilers: Record<string, true | CompilerSync>;
@@ -16,7 +16,7 @@ export declare const IGNORED_DEPENDENCIES: Set<string>;
16
16
  export declare const IGNORED_RUNTIME_DEPENDENCIES: Set<string>;
17
17
  export declare const FOREIGN_FILE_EXTENSIONS: Set<string>;
18
18
  export declare const IGNORE_DEFINITELY_TYPED: Set<string>;
19
- export declare const ISSUE_TYPES: readonly ['files', 'dependencies', 'devDependencies', 'optionalPeerDependencies', 'unlisted', 'binaries', 'unresolved', 'exports', 'nsExports', 'types', 'nsTypes', 'enumMembers', 'namespaceMembers', 'duplicates', 'catalog'];
19
+ export declare const ISSUE_TYPES: readonly ['files', 'dependencies', 'devDependencies', 'optionalPeerDependencies', 'unlisted', 'binaries', 'unresolved', 'exports', 'nsExports', 'types', 'nsTypes', 'enumMembers', 'namespaceMembers', 'duplicates', 'catalog', 'cycles'];
20
20
  export declare const ISSUE_TYPE_TITLE: {
21
21
  readonly files: 'Unused files';
22
22
  readonly dependencies: 'Unused dependencies';
@@ -33,6 +33,7 @@ export declare const ISSUE_TYPE_TITLE: {
33
33
  readonly namespaceMembers: 'Unused exported namespace members';
34
34
  readonly duplicates: 'Duplicate exports';
35
35
  readonly catalog: 'Unused catalog entries';
36
+ readonly cycles: 'Circular dependencies';
36
37
  };
37
38
  export declare const SYMBOL_TYPE: {
38
39
  readonly CLASS: 'class';
@@ -53,6 +54,7 @@ export declare const FIX_FLAGS: {
53
54
  };
54
55
  export declare const SIDE_EFFECTS = "__side-effects";
55
56
  export declare const OPAQUE = "__opaque";
57
+ export declare const SCRIPT_INTERPOLATION = "$__knip__";
56
58
  export declare const IMPORT_FLAGS: {
57
59
  readonly NONE: 0;
58
60
  readonly RE_EXPORT: number;
@@ -62,4 +64,6 @@ export declare const IMPORT_FLAGS: {
62
64
  readonly OPTIONAL: number;
63
65
  readonly SIDE_EFFECTS: number;
64
66
  readonly OPAQUE: number;
67
+ readonly AUGMENT: number;
68
+ readonly DYNAMIC: number;
65
69
  };
package/dist/constants.js CHANGED
@@ -186,6 +186,7 @@ export const ISSUE_TYPES = [
186
186
  'namespaceMembers',
187
187
  'duplicates',
188
188
  'catalog',
189
+ 'cycles',
189
190
  ];
190
191
  export const ISSUE_TYPE_TITLE = {
191
192
  files: 'Unused files',
@@ -203,6 +204,7 @@ export const ISSUE_TYPE_TITLE = {
203
204
  namespaceMembers: 'Unused exported namespace members',
204
205
  duplicates: 'Duplicate exports',
205
206
  catalog: 'Unused catalog entries',
207
+ cycles: 'Circular dependencies',
206
208
  };
207
209
  export const SYMBOL_TYPE = {
208
210
  CLASS: 'class',
@@ -223,6 +225,7 @@ export const FIX_FLAGS = {
223
225
  };
224
226
  export const SIDE_EFFECTS = '__side-effects';
225
227
  export const OPAQUE = '__opaque';
228
+ export const SCRIPT_INTERPOLATION = '$__knip__';
226
229
  export const IMPORT_FLAGS = {
227
230
  NONE: 0,
228
231
  RE_EXPORT: 1 << 0,
@@ -232,4 +235,6 @@ export const IMPORT_FLAGS = {
232
235
  OPTIONAL: 1 << 4,
233
236
  SIDE_EFFECTS: 1 << 5,
234
237
  OPAQUE: 1 << 6,
238
+ AUGMENT: 1 << 7,
239
+ DYNAMIC: 1 << 8,
235
240
  };
@@ -1,12 +1,27 @@
1
1
  import { createGraphExplorer } from '../graph-explorer/explorer.js';
2
- import { getIssueType, hasStrictlyEnumReferences } from '../graph-explorer/utils.js';
2
+ import { getIgnoredCycleImportFlags, getIssueType, getRuntimeImport, hasStrictlyEnumReferences, } from '../graph-explorer/utils.js';
3
3
  import traceReporter from '../reporters/trace.js';
4
4
  import { shouldCountRefs } from '../typescript/ast-nodes.js';
5
5
  import { getPackageNameFromModuleSpecifier } from '../util/modules.js';
6
- import { perfObserver } from '../util/Performance.js';
6
+ import { relative } from '../util/path.js';
7
+ import { perfObserver, timerify } from '../util/Performance.js';
7
8
  import { findMatch } from '../util/regex.js';
8
9
  import { getShouldIgnoreHandler, getShouldIgnoreTagHandler, isAlwaysIgnored } from '../util/tag.js';
9
10
  import { INTERNAL_TAG } from '../constants.js';
11
+ const toCyclePathKey = (path) => {
12
+ const length = path.length > 1 && path[0] === path[path.length - 1] ? path.length - 1 : path.length;
13
+ return path.slice(0, length).join('\0');
14
+ };
15
+ const getAllowedCyclePaths = (cycles) => {
16
+ if (!cycles.allow?.length)
17
+ return;
18
+ const paths = new Set();
19
+ for (const path of cycles.allow)
20
+ if (path.length > 0)
21
+ paths.add(toCyclePathKey(path));
22
+ return paths;
23
+ };
24
+ const isAllowedCycle = (symbols, paths) => paths?.has(toCyclePathKey(symbols.map(s => s.symbol))) ?? false;
10
25
  export const analyze = async ({ analyzedFiles, counselor, chief, collector, deputy, entryPaths, graph, streamer, unreferencedFiles, options, }) => {
11
26
  const shouldIgnore = getShouldIgnoreHandler(options.isProduction);
12
27
  const shouldIgnoreTags = getShouldIgnoreTagHandler(options.tags);
@@ -210,6 +225,26 @@ export const analyze = async ({ analyzedFiles, counselor, chief, collector, depu
210
225
  }
211
226
  }
212
227
  }
228
+ if (options.isReportCycles) {
229
+ const cyclesConfig = chief.config.cycles;
230
+ const allowedCyclePaths = getAllowedCyclePaths(cyclesConfig);
231
+ const ignoredFlags = getIgnoredCycleImportFlags(cyclesConfig.dynamicImports ?? false);
232
+ for (const cycle of timerify(explorer.findAllCycles, 'findAllCycles')(ignoredFlags)) {
233
+ const filePath = cycle[0];
234
+ const ws = chief.findWorkspaceByFilePath(filePath);
235
+ if (!ws)
236
+ continue;
237
+ const symbols = cycle.slice(0, -1).map((file, index) => {
238
+ const node = graph.get(file);
239
+ const edge = node && getRuntimeImport(node, cycle[index + 1], ignoredFlags);
240
+ return edge ? { symbol: relative(options.cwd, file), ...edge } : { symbol: relative(options.cwd, file) };
241
+ });
242
+ if (isAllowedCycle(symbols, allowedCyclePaths))
243
+ continue;
244
+ const symbol = symbols.map(s => s.symbol).join(' → ');
245
+ collector.addIssue({ type: 'cycles', filePath, workspace: ws.name, symbol, symbols, fixes: [] });
246
+ }
247
+ }
213
248
  const unusedFiles = options.isReportFiles
214
249
  ? [...unreferencedFiles].filter(filePath => !analyzedFiles.has(filePath))
215
250
  : [];
@@ -10,6 +10,7 @@ import { tryRealpath } from '../util/fs.js';
10
10
  import { createManifest } from '../util/package-json.js';
11
11
  import { _glob, _syncGlob, negate, prependDirToPattern as prependDir } from '../util/glob.js';
12
12
  import { isAlias, isConfig, isDeferResolveEntry, isDeferResolveProductionEntry, isEntry, isIgnore, isProductionEntry, isProject, toProductionEntry, } from '../util/input.js';
13
+ import { resolveImportGlobs } from '../typescript/glob-imports.js';
13
14
  import { loadTSConfig } from '../util/load-tsconfig.js';
14
15
  import { createFileNode, updateImportMap } from '../util/module-graph.js';
15
16
  import { getPackageNameFromModuleSpecifier, isStartsLikePackageName, sanitizeSpecifier } from '../util/modules.js';
@@ -382,6 +383,13 @@ export async function build({ chief, collector, counselor, deputy, principal, is
382
383
  pp.addEntryPath(specifierFilePath, { skipExportsAnalysis: true });
383
384
  }
384
385
  }
386
+ if (file.importGlobs.length > 0) {
387
+ const globbed = resolveImportGlobs(file.importGlobs, filePath, pp.resolveGlobPattern, workspace.dir);
388
+ for (const importedFilePath of globbed) {
389
+ if (!isGitIgnored(importedFilePath))
390
+ pp.addEntryPath(importedFilePath, { skipExportsAnalysis: true });
391
+ }
392
+ }
385
393
  file.imports.unresolved = unresolvedImports;
386
394
  const pluginRefs = externalRefsFromInputs?.get(filePath);
387
395
  if (pluginRefs)
@@ -393,6 +401,7 @@ export async function build({ chief, collector, counselor, deputy, principal, is
393
401
  node.exports = file.exports;
394
402
  node.duplicates = file.duplicates;
395
403
  node.scripts = file.scripts;
404
+ node.importGlobs = file.importGlobs;
396
405
  updateImportMap(node, file.imports.internal, graph);
397
406
  node.internalImportCache = file.imports.internal;
398
407
  }
@@ -13,6 +13,7 @@ export declare const createGraphExplorer: (graph: ModuleGraph, entryPaths: Set<s
13
13
  resolveDefinition: (filePath: string, identifier: string) => import("./operations/resolve-definition.ts").DefinitionResult | null;
14
14
  getUsage: (filePath: string, identifier: string) => import("./operations/get-usage.ts").UsageResult;
15
15
  findCycles: (filePath: string, maxDepth?: number) => import("../session/types.ts").Cycle[];
16
+ findAllCycles: (ignoredFlags?: number) => import("../session/types.ts").Cycle[];
16
17
  getContention: (filePath: string) => Map<string, import("../session/types.ts").ContentionDetails>;
17
18
  invalidateCache: () => void;
18
19
  };
@@ -1,5 +1,6 @@
1
1
  import { invalidateCache as invalidateCacheInternal } from './cache.js';
2
2
  import { buildExportsTree } from './operations/build-exports-tree.js';
3
+ import { findAllCycles } from './operations/find-all-cycles.js';
3
4
  import { findCycles } from './operations/find-cycles.js';
4
5
  import { getContention } from './operations/get-contention.js';
5
6
  import { getDependencyUsage } from './operations/get-dependency-usage.js';
@@ -16,6 +17,7 @@ export const createGraphExplorer = (graph, entryPaths) => {
16
17
  resolveDefinition: (filePath, identifier) => resolveDefinition(graph, filePath, identifier),
17
18
  getUsage: (filePath, identifier) => getUsage(graph, entryPaths, filePath, identifier),
18
19
  findCycles: (filePath, maxDepth) => findCycles(graph, filePath, maxDepth),
20
+ findAllCycles: (ignoredFlags) => findAllCycles(graph, ignoredFlags),
19
21
  getContention: (filePath) => getContention(graph, filePath),
20
22
  invalidateCache: () => invalidateCacheInternal(graph),
21
23
  };
@@ -0,0 +1,3 @@
1
+ import type { Cycle } from '../../session/types.ts';
2
+ import type { ModuleGraph } from '../../types/module-graph.ts';
3
+ export declare const findAllCycles: (graph: ModuleGraph, ignoredFlags?: number) => Cycle[];
@@ -0,0 +1,43 @@
1
+ import { getRuntimeSuccessors } from '../utils.js';
2
+ const UNVISITED = undefined;
3
+ const ON_STACK = 1;
4
+ const DONE = 2;
5
+ export const findAllCycles = (graph, ignoredFlags) => {
6
+ const cycles = [];
7
+ const state = new Map();
8
+ for (const root of graph.keys()) {
9
+ if (state.get(root) !== UNVISITED)
10
+ continue;
11
+ const path = [];
12
+ const indexOnPath = new Map();
13
+ const successorsStack = [];
14
+ const push = (filePath) => {
15
+ state.set(filePath, ON_STACK);
16
+ indexOnPath.set(filePath, path.length);
17
+ path.push(filePath);
18
+ const node = graph.get(filePath);
19
+ successorsStack.push((node ? getRuntimeSuccessors(node, ignoredFlags) : new Set()).values());
20
+ };
21
+ push(root);
22
+ while (path.length > 0) {
23
+ const current = path[path.length - 1];
24
+ const next = successorsStack[successorsStack.length - 1].next();
25
+ if (next.done) {
26
+ state.set(current, DONE);
27
+ indexOnPath.delete(current);
28
+ path.pop();
29
+ successorsStack.pop();
30
+ continue;
31
+ }
32
+ const successor = next.value;
33
+ const successorState = state.get(successor);
34
+ if (successorState === ON_STACK) {
35
+ cycles.push([...path.slice(indexOnPath.get(successor)), successor]);
36
+ }
37
+ else if (successorState === UNVISITED) {
38
+ push(successor);
39
+ }
40
+ }
41
+ }
42
+ return cycles;
43
+ };
@@ -1,3 +1,4 @@
1
+ import { getRuntimeSuccessors } from '../utils.js';
1
2
  export const findCycles = (graph, filePath, maxDepth = 16) => {
2
3
  const cycles = [];
3
4
  const visited = new Set();
@@ -7,16 +8,9 @@ export const findCycles = (graph, filePath, maxDepth = 16) => {
7
8
  if (path.length > maxDepth)
8
9
  return;
9
10
  const node = graph.get(currentPath);
10
- if (!node?.imports?.internal)
11
+ if (!node)
11
12
  return;
12
- const nonTypeOnlyImports = new Set();
13
- for (const _import of node.imports.imports) {
14
- if (_import.filePath && !_import.isTypeOnly)
15
- nonTypeOnlyImports.add(_import.filePath);
16
- }
17
- for (const [importedPath] of node.imports.internal) {
18
- if (!nonTypeOnlyImports.has(importedPath))
19
- continue;
13
+ for (const importedPath of getRuntimeSuccessors(node)) {
20
14
  if (importedPath === filePath) {
21
15
  cycles.push([...path, importedPath]);
22
16
  continue;