knip 6.23.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 (128) hide show
  1. package/LICENSE +1 -1
  2. package/dist/ConfigurationChief.d.ts +13 -1
  3. package/dist/ConfigurationChief.js +3 -0
  4. package/dist/ProjectPrincipal.d.ts +2 -0
  5. package/dist/ProjectPrincipal.js +7 -6
  6. package/dist/WorkspaceWorker.d.ts +3 -1
  7. package/dist/WorkspaceWorker.js +5 -2
  8. package/dist/binaries/bash-parser.js +55 -83
  9. package/dist/binaries/index.js +3 -0
  10. package/dist/binaries/resolvers/yarn.js +4 -3
  11. package/dist/compilers/compilers.d.ts +2 -0
  12. package/dist/compilers/compilers.js +15 -4
  13. package/dist/compilers/index.d.ts +30 -6
  14. package/dist/compilers/index.js +25 -10
  15. package/dist/compilers/less.d.ts +7 -0
  16. package/dist/compilers/less.js +37 -0
  17. package/dist/compilers/mdx.d.ts +1 -2
  18. package/dist/compilers/mdx.js +14 -8
  19. package/dist/compilers/scss.d.ts +3 -2
  20. package/dist/compilers/scss.js +17 -14
  21. package/dist/compilers/shared.d.ts +6 -0
  22. package/dist/compilers/shared.js +9 -0
  23. package/dist/compilers/style-preprocessors.d.ts +2 -0
  24. package/dist/compilers/style-preprocessors.js +41 -0
  25. package/dist/compilers/stylus.d.ts +7 -0
  26. package/dist/compilers/stylus.js +36 -0
  27. package/dist/constants.d.ts +5 -1
  28. package/dist/constants.js +7 -0
  29. package/dist/graph/analyze.js +37 -2
  30. package/dist/graph/build.js +23 -1
  31. package/dist/graph-explorer/explorer.d.ts +1 -0
  32. package/dist/graph-explorer/explorer.js +2 -0
  33. package/dist/graph-explorer/operations/find-all-cycles.d.ts +3 -0
  34. package/dist/graph-explorer/operations/find-all-cycles.js +43 -0
  35. package/dist/graph-explorer/operations/find-cycles.js +3 -9
  36. package/dist/graph-explorer/utils.d.ts +10 -1
  37. package/dist/graph-explorer/utils.js +53 -0
  38. package/dist/plugins/astro/compiler-mdx.js +11 -2
  39. package/dist/plugins/astro/compiler.js +8 -4
  40. package/dist/plugins/bun/index.js +28 -14
  41. package/dist/plugins/eve/index.d.ts +3 -0
  42. package/dist/plugins/eve/index.js +22 -0
  43. package/dist/plugins/execa/visitors/execa.js +10 -18
  44. package/dist/plugins/fumadocs/index.d.ts +3 -0
  45. package/dist/plugins/fumadocs/index.js +18 -0
  46. package/dist/plugins/github-actions/index.js +3 -2
  47. package/dist/plugins/graphql-codegen/index.js +9 -7
  48. package/dist/plugins/index.d.ts +2 -0
  49. package/dist/plugins/index.js +4 -0
  50. package/dist/plugins/jest/index.js +13 -3
  51. package/dist/plugins/next/index.js +23 -10
  52. package/dist/plugins/node/index.js +4 -1
  53. package/dist/plugins/nuxt/index.js +4 -2
  54. package/dist/plugins/pnpm/index.js +21 -1
  55. package/dist/plugins/pnpm/types.d.ts +6 -0
  56. package/dist/plugins/pnpm/types.js +1 -0
  57. package/dist/plugins/prettier/index.js +19 -3
  58. package/dist/plugins/prettier/types.d.ts +6 -1
  59. package/dist/plugins/prisma/compiler.js +3 -0
  60. package/dist/plugins/rspack/index.js +5 -0
  61. package/dist/plugins/serverless-framework/index.js +12 -1
  62. package/dist/plugins/serverless-framework/types.d.ts +5 -2
  63. package/dist/plugins/stencil/index.js +59 -1
  64. package/dist/plugins/storybook/index.js +7 -0
  65. package/dist/plugins/svelte/compiler.d.ts +2 -1
  66. package/dist/plugins/svelte/compiler.js +8 -1
  67. package/dist/plugins/tailwind/compiler.js +3 -0
  68. package/dist/plugins/tailwind/index.js +5 -1
  69. package/dist/plugins/typedoc/index.js +9 -3
  70. package/dist/plugins/typedoc/types.d.ts +2 -0
  71. package/dist/plugins/vite/visitors/importMetaGlob.js +1 -8
  72. package/dist/plugins/vue/compiler.d.ts +2 -1
  73. package/dist/plugins/vue/compiler.js +8 -1
  74. package/dist/plugins/webpack/visitors/requireContext.js +2 -13
  75. package/dist/plugins/yarn/index.js +13 -4
  76. package/dist/plugins/yarn/types.d.ts +10 -0
  77. package/dist/plugins/yarn/types.js +1 -0
  78. package/dist/plugins/zx/visitors/zx.js +4 -4
  79. package/dist/reporters/compact.js +1 -2
  80. package/dist/reporters/cycles.d.ts +3 -0
  81. package/dist/reporters/cycles.js +79 -0
  82. package/dist/reporters/index.d.ts +1 -0
  83. package/dist/reporters/index.js +2 -0
  84. package/dist/reporters/json.d.ts +3 -0
  85. package/dist/reporters/json.js +2 -1
  86. package/dist/reporters/symbols.js +1 -2
  87. package/dist/reporters/util/util.d.ts +3 -1
  88. package/dist/reporters/util/util.js +5 -1
  89. package/dist/schema/configuration.d.ts +40 -6
  90. package/dist/schema/configuration.js +6 -0
  91. package/dist/schema/plugins.d.ts +10 -0
  92. package/dist/schema/plugins.js +2 -0
  93. package/dist/types/PluginNames.d.ts +2 -2
  94. package/dist/types/PluginNames.js +2 -0
  95. package/dist/types/config.d.ts +10 -0
  96. package/dist/types/issues.d.ts +3 -0
  97. package/dist/types/module-graph.d.ts +7 -0
  98. package/dist/types/package-json.d.ts +4 -1
  99. package/dist/types.d.ts +1 -1
  100. package/dist/typescript/SourceFileManager.js +3 -2
  101. package/dist/typescript/ast-nodes.d.ts +4 -1
  102. package/dist/typescript/ast-nodes.js +60 -2
  103. package/dist/typescript/comments.js +22 -3
  104. package/dist/typescript/get-imports-and-exports.js +10 -0
  105. package/dist/typescript/glob-imports.d.ts +3 -0
  106. package/dist/typescript/glob-imports.js +49 -0
  107. package/dist/typescript/resolve-module-names.d.ts +2 -0
  108. package/dist/typescript/resolve-module-names.js +35 -1
  109. package/dist/typescript/visitors/calls.js +4 -6
  110. package/dist/typescript/visitors/imports.js +8 -8
  111. package/dist/typescript/visitors/script-visitors.js +4 -4
  112. package/dist/typescript/visitors/walk.js +6 -1
  113. package/dist/util/cli-arguments.d.ts +2 -1
  114. package/dist/util/cli-arguments.js +4 -2
  115. package/dist/util/create-options.d.ts +32 -5
  116. package/dist/util/create-options.js +4 -1
  117. package/dist/util/get-included-issue-types.d.ts +1 -0
  118. package/dist/util/get-included-issue-types.js +4 -2
  119. package/dist/util/issue-initializers.js +1 -1
  120. package/dist/util/load-tsconfig.js +17 -4
  121. package/dist/util/module-graph.js +1 -0
  122. package/dist/util/modules.js +25 -4
  123. package/dist/util/scripts.d.ts +7 -0
  124. package/dist/util/scripts.js +75 -0
  125. package/dist/version.d.ts +1 -1
  126. package/dist/version.js +1 -1
  127. package/package.json +1 -1
  128. package/schema.json +40 -1
@@ -0,0 +1,37 @@
1
+ import { isScopedPackage, isTildePackage, splitSpec } from './shared.js';
2
+ const dependencies = ['less'];
3
+ const importMatcher = /@import\s+(?:\([^)]*\)\s+)?(?:url\(\s*['"]?([^'")\s]+)['"]?\s*\)|['"]([^'"]+)['"])/g;
4
+ const isExternalUrl = (s) => s.startsWith('//') || s.startsWith('http://') || s.startsWith('https://');
5
+ const candidates = (specifier) => {
6
+ const { dir, name } = splitSpec(specifier);
7
+ if (name.endsWith('.less') || name.endsWith('.css'))
8
+ return [`${dir}/${name}`];
9
+ return [`${dir}/${name}.less`];
10
+ };
11
+ export const compiler = text => {
12
+ if (!text.includes('@import'))
13
+ return '';
14
+ const out = [];
15
+ let i = 0;
16
+ let match;
17
+ importMatcher.lastIndex = 0;
18
+ while ((match = importMatcher.exec(text))) {
19
+ let spec = match[1] ?? match[2];
20
+ if (!spec || isExternalUrl(spec))
21
+ continue;
22
+ let isBare = isScopedPackage(spec);
23
+ if (isTildePackage(spec)) {
24
+ spec = spec.slice(1);
25
+ isBare = true;
26
+ }
27
+ if (isBare) {
28
+ out.push(`import _$${i++} from '${spec}';`);
29
+ }
30
+ else {
31
+ for (const s of candidates(spec))
32
+ out.push(`import _$${i++} from '${s}';`);
33
+ }
34
+ }
35
+ return out.join('\n');
36
+ };
37
+ export default { dependencies, compiler };
@@ -1,6 +1,5 @@
1
- import type { HasDependency } from './types.ts';
2
1
  declare const _default: {
3
- condition: (hasDependency: HasDependency) => boolean;
2
+ dependencies: string[];
4
3
  compiler: (text: string) => string;
5
4
  };
6
5
  export default _default;
@@ -1,5 +1,5 @@
1
1
  import { fencedCodeBlockMatcher, frontmatterMatcher, inlineCodeMatcher } from './compilers.js';
2
- const mdxDependencies = [
2
+ const dependencies = [
3
3
  '@mdx-js/esbuild',
4
4
  '@mdx-js/loader',
5
5
  '@mdx-js/mdx',
@@ -10,13 +10,19 @@ const mdxDependencies = [
10
10
  '@mdx-js/vue',
11
11
  'remark-mdx',
12
12
  ];
13
- const condition = (hasDependency) => mdxDependencies.some(hasDependency);
14
13
  const mdxImportMatcher = /^import[^'"]+['"][^'"]+['"]/gm;
15
- const compiler = (text) => [
16
- ...text
14
+ const compiler = (text) => {
15
+ if (!text.includes('import'))
16
+ return '';
17
+ const imports = [];
18
+ const source = text
17
19
  .replace(frontmatterMatcher, '')
18
20
  .replace(fencedCodeBlockMatcher, '')
19
- .replace(inlineCodeMatcher, '')
20
- .matchAll(mdxImportMatcher),
21
- ].join('\n');
22
- export default { condition, compiler };
21
+ .replace(inlineCodeMatcher, '');
22
+ let match;
23
+ mdxImportMatcher.lastIndex = 0;
24
+ while ((match = mdxImportMatcher.exec(source)))
25
+ imports.push(match[0]);
26
+ return imports.join('\n');
27
+ };
28
+ export default { dependencies, compiler };
@@ -1,6 +1,7 @@
1
- import type { CompilerSync, HasDependency } from './types.ts';
1
+ import type { CompilerSync } from './types.ts';
2
+ export declare const compiler: CompilerSync;
2
3
  declare const _default: {
3
- condition: (hasDependency: HasDependency) => boolean;
4
+ dependencies: string[];
4
5
  compiler: CompilerSync;
5
6
  };
6
7
  export default _default;
@@ -1,13 +1,8 @@
1
- import { basename, dirname } from '../util/path.js';
2
- const condition = (hasDependency) => hasDependency('sass') || hasDependency('sass-embedded') || hasDependency('node-sass');
1
+ import { isScopedPackage, isTildePackage, splitSpec } from './shared.js';
2
+ const dependencies = ['sass', 'sass-embedded', 'node-sass'];
3
3
  const importMatcher = /@(?:use|import|forward)\s+['"](pkg:)?([^'"]+)['"]/g;
4
- const isAlias = (s) => (s.charCodeAt(0) === 64 && s.charCodeAt(1) === 47) || s.charCodeAt(0) === 126 || s.charCodeAt(0) === 35;
5
- const isScopedPackage = (s) => s.charCodeAt(0) === 64 && s.charCodeAt(1) !== 47;
6
- const isTildePackage = (s) => s.charCodeAt(0) === 126 && s.charCodeAt(1) !== 47;
7
4
  const candidates = (specifier) => {
8
- const spec = specifier.startsWith('.') || isAlias(specifier) ? specifier : `./${specifier}`;
9
- const name = basename(spec);
10
- const dir = dirname(spec);
5
+ const { dir, name } = splitSpec(specifier);
11
6
  const hasExt = name.endsWith('.scss') || name.endsWith('.sass');
12
7
  const bases = hasExt ? [name] : [`${name}.scss`, `${name}.sass`];
13
8
  const out = [];
@@ -18,10 +13,14 @@ const candidates = (specifier) => {
18
13
  }
19
14
  return out;
20
15
  };
21
- const compiler = text => {
16
+ export const compiler = text => {
17
+ if (!text.includes('@use') && !text.includes('@import') && !text.includes('@forward'))
18
+ return '';
22
19
  const out = [];
23
20
  let i = 0;
24
- for (const match of text.matchAll(importMatcher)) {
21
+ let match;
22
+ importMatcher.lastIndex = 0;
23
+ while ((match = importMatcher.exec(text))) {
25
24
  let spec = match[2];
26
25
  if (!spec || spec.startsWith('sass:'))
27
26
  continue;
@@ -30,10 +29,14 @@ const compiler = text => {
30
29
  spec = spec.slice(1);
31
30
  isBare = true;
32
31
  }
33
- const specs = isBare ? [spec] : candidates(spec);
34
- for (const s of specs)
35
- out.push(`import _$${i++} from '${s}';`);
32
+ if (isBare) {
33
+ out.push(`import _$${i++} from '${spec}';`);
34
+ }
35
+ else {
36
+ for (const s of candidates(spec))
37
+ out.push(`import _$${i++} from '${s}';`);
38
+ }
36
39
  }
37
40
  return out.join('\n');
38
41
  };
39
- export default { condition, compiler };
42
+ export default { dependencies, compiler };
@@ -0,0 +1,6 @@
1
+ export declare const isScopedPackage: (s: string) => boolean;
2
+ export declare const isTildePackage: (s: string) => boolean;
3
+ export declare const splitSpec: (specifier: string) => {
4
+ dir: string;
5
+ name: string;
6
+ };
@@ -0,0 +1,9 @@
1
+ import { basename, dirname } from '../util/path.js';
2
+ const isAlias = (s) => (s.charCodeAt(0) === 64 && s.charCodeAt(1) === 47) || s.charCodeAt(0) === 126 || s.charCodeAt(0) === 35;
3
+ export const isScopedPackage = (s) => s.charCodeAt(0) === 64 && s.charCodeAt(1) !== 47;
4
+ export const isTildePackage = (s) => s.charCodeAt(0) === 126 && s.charCodeAt(1) !== 47;
5
+ const ensureRelative = (spec) => (spec.startsWith('.') || isAlias(spec) ? spec : `./${spec}`);
6
+ export const splitSpec = (specifier) => {
7
+ const spec = ensureRelative(specifier);
8
+ return { dir: dirname(spec), name: basename(spec) };
9
+ };
@@ -0,0 +1,2 @@
1
+ import type { CompilerSync } from './types.ts';
2
+ export declare const stylePreprocessorImports: CompilerSync;
@@ -0,0 +1,41 @@
1
+ import { getStyleLang, styleExtractor } from './compilers.js';
2
+ import { compiler as lessCompiler } from './less.js';
3
+ import { compiler as scssCompiler } from './scss.js';
4
+ import { compiler as stylusCompiler } from './stylus.js';
5
+ export const stylePreprocessorImports = (text, path) => {
6
+ let scss = '';
7
+ let less = '';
8
+ let stylus = '';
9
+ styleExtractor.lastIndex = 0;
10
+ let match;
11
+ while ((match = styleExtractor.exec(text))) {
12
+ const attrs = match[1];
13
+ const body = match[2];
14
+ if (!body)
15
+ continue;
16
+ switch (getStyleLang(attrs)) {
17
+ case 'scss':
18
+ case 'sass':
19
+ scss = scss ? `${scss}\n${body}` : body;
20
+ break;
21
+ case 'less':
22
+ less = less ? `${less}\n${body}` : body;
23
+ break;
24
+ case 'styl':
25
+ case 'stylus':
26
+ stylus = stylus ? `${stylus}\n${body}` : body;
27
+ }
28
+ }
29
+ let out = scss ? scssCompiler(scss, path) : '';
30
+ if (less) {
31
+ const imports = lessCompiler(less, path);
32
+ if (imports)
33
+ out = out ? `${out}\n${imports}` : imports;
34
+ }
35
+ if (stylus) {
36
+ const imports = stylusCompiler(stylus, path);
37
+ if (imports)
38
+ out = out ? `${out}\n${imports}` : imports;
39
+ }
40
+ return out;
41
+ };
@@ -0,0 +1,7 @@
1
+ import type { CompilerSync } from './types.ts';
2
+ export declare const compiler: CompilerSync;
3
+ declare const _default: {
4
+ dependencies: string[];
5
+ compiler: CompilerSync;
6
+ };
7
+ export default _default;
@@ -0,0 +1,36 @@
1
+ import { isScopedPackage, isTildePackage, splitSpec } from './shared.js';
2
+ const dependencies = ['stylus'];
3
+ const importMatcher = /@(?:import|require)\s+['"]([^'"]+)['"]/g;
4
+ const candidates = (specifier) => {
5
+ const { dir, name } = splitSpec(specifier);
6
+ if (name.endsWith('.styl') || name.endsWith('.stylus') || name.endsWith('.css'))
7
+ return [`${dir}/${name}`];
8
+ return [`${dir}/${name}.styl`, `${dir}/${name}/index.styl`];
9
+ };
10
+ export const compiler = text => {
11
+ if (!text.includes('@import') && !text.includes('@require'))
12
+ return '';
13
+ const out = [];
14
+ let i = 0;
15
+ let match;
16
+ importMatcher.lastIndex = 0;
17
+ while ((match = importMatcher.exec(text))) {
18
+ let spec = match[1];
19
+ if (!spec || spec.includes('*'))
20
+ continue;
21
+ let isBare = isScopedPackage(spec);
22
+ if (isTildePackage(spec)) {
23
+ spec = spec.slice(1);
24
+ isBare = true;
25
+ }
26
+ if (isBare) {
27
+ out.push(`import _$${i++} from '${spec}';`);
28
+ }
29
+ else {
30
+ for (const s of candidates(spec))
31
+ out.push(`import _$${i++} from '${s}';`);
32
+ }
33
+ }
34
+ return out.join('\n');
35
+ };
36
+ export default { dependencies, compiler };
@@ -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
@@ -155,6 +155,8 @@ export const FOREIGN_FILE_EXTENSIONS = new Set([
155
155
  '.sass',
156
156
  '.scss',
157
157
  '.sh',
158
+ '.styl',
159
+ '.stylus',
158
160
  '.svg',
159
161
  '.ttf',
160
162
  '.webp',
@@ -184,6 +186,7 @@ export const ISSUE_TYPES = [
184
186
  'namespaceMembers',
185
187
  'duplicates',
186
188
  'catalog',
189
+ 'cycles',
187
190
  ];
188
191
  export const ISSUE_TYPE_TITLE = {
189
192
  files: 'Unused files',
@@ -201,6 +204,7 @@ export const ISSUE_TYPE_TITLE = {
201
204
  namespaceMembers: 'Unused exported namespace members',
202
205
  duplicates: 'Duplicate exports',
203
206
  catalog: 'Unused catalog entries',
207
+ cycles: 'Circular dependencies',
204
208
  };
205
209
  export const SYMBOL_TYPE = {
206
210
  CLASS: 'class',
@@ -221,6 +225,7 @@ export const FIX_FLAGS = {
221
225
  };
222
226
  export const SIDE_EFFECTS = '__side-effects';
223
227
  export const OPAQUE = '__opaque';
228
+ export const SCRIPT_INTERPOLATION = '$__knip__';
224
229
  export const IMPORT_FLAGS = {
225
230
  NONE: 0,
226
231
  RE_EXPORT: 1 << 0,
@@ -230,4 +235,6 @@ export const IMPORT_FLAGS = {
230
235
  OPTIONAL: 1 << 4,
231
236
  SIDE_EFFECTS: 1 << 5,
232
237
  OPAQUE: 1 << 6,
238
+ AUGMENT: 1 << 7,
239
+ DYNAMIC: 1 << 8,
233
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';
@@ -28,6 +29,17 @@ export async function build({ chief, collector, counselor, deputy, principal, is
28
29
  const handleInput = createInputHandler(deputy, chief, isGitIgnored, addIssue, externalRefsFromInputs, options);
29
30
  const rawRootManifest = chief.getManifestForWorkspace('.');
30
31
  const rootManifest = rawRootManifest ? createManifest(rawRootManifest) : undefined;
32
+ const manifestsByWorkspaceName = new Map();
33
+ const getManifest = (dir) => {
34
+ const workspace = chief.findWorkspaceByFilePath(`${dir}/`);
35
+ if (!workspace)
36
+ return undefined;
37
+ if (!manifestsByWorkspaceName.has(workspace.name)) {
38
+ const raw = chief.getManifestForWorkspace(workspace.name);
39
+ manifestsByWorkspaceName.set(workspace.name, raw ? createManifest(raw) : undefined);
40
+ }
41
+ return manifestsByWorkspaceName.get(workspace.name);
42
+ };
31
43
  for (const workspace of workspaces) {
32
44
  const { name, dir, manifestPath, manifestStr } = workspace;
33
45
  const manifest = chief.getManifestForWorkspace(name);
@@ -70,6 +82,7 @@ export async function build({ chief, collector, counselor, deputy, principal, is
70
82
  rootManifest,
71
83
  handleInput: (input) => handleInput(input, workspace),
72
84
  findWorkspaceByFilePath: chief.findWorkspaceByFilePath.bind(chief),
85
+ getManifest,
73
86
  negatedWorkspacePatterns: chief.getNegatedWorkspacePatterns(name),
74
87
  ignoredWorkspacePatterns: chief.getIgnoredWorkspacesFor(name),
75
88
  enabledPluginsInAncestors: ancestors.flatMap(ancestor => enabledPluginsStore.get(ancestor) ?? []),
@@ -78,7 +91,7 @@ export async function build({ chief, collector, counselor, deputy, principal, is
78
91
  options,
79
92
  });
80
93
  await worker.init();
81
- const compilers = getIncludedCompilers(chief.config.syncCompilers, chief.config.asyncCompilers, dependencies);
94
+ const compilers = getIncludedCompilers(chief.config.syncCompilers, chief.config.asyncCompilers, dependencies, dep => deputy.addReferencedDependency(name, dep));
82
95
  const registerCompiler = async ({ extension, compiler }) => {
83
96
  const ext = normalizeCompilerExtension(extension);
84
97
  if (compilers[0].has(ext))
@@ -359,6 +372,7 @@ export async function build({ chief, collector, counselor, deputy, principal, is
359
372
  dependencies,
360
373
  manifest: createManifest(manifest),
361
374
  rootManifest,
375
+ getManifest,
362
376
  };
363
377
  const inputs = _getInputsFromScripts(file.scripts, opts);
364
378
  for (const input of inputs) {
@@ -369,6 +383,13 @@ export async function build({ chief, collector, counselor, deputy, principal, is
369
383
  pp.addEntryPath(specifierFilePath, { skipExportsAnalysis: true });
370
384
  }
371
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
+ }
372
393
  file.imports.unresolved = unresolvedImports;
373
394
  const pluginRefs = externalRefsFromInputs?.get(filePath);
374
395
  if (pluginRefs)
@@ -380,6 +401,7 @@ export async function build({ chief, collector, counselor, deputy, principal, is
380
401
  node.exports = file.exports;
381
402
  node.duplicates = file.duplicates;
382
403
  node.scripts = file.scripts;
404
+ node.importGlobs = file.importGlobs;
383
405
  updateImportMap(node, file.imports.internal, graph);
384
406
  node.internalImportCache = file.imports.internal;
385
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;
@@ -1,5 +1,14 @@
1
- import type { Import, ImportMaps, ModuleGraph } from '../types/module-graph.ts';
1
+ import type { FileNode, Import, ImportMaps, ModuleGraph } from '../types/module-graph.ts';
2
+ export declare const getIgnoredCycleImportFlags: (includeDynamicImports: boolean) => number;
2
3
  export declare const getExportedIdentifiers: (graph: ModuleGraph, filePath: string, visited?: Set<string>) => Map<string, boolean>;
4
+ export declare const getRuntimeSuccessors: (node: FileNode, ignoredFlags?: number) => Set<string>;
5
+ export declare const getRuntimeImport: (node: FileNode, filePath: string, ignoredFlags?: number) => {
6
+ kind: string;
7
+ specifier: string;
8
+ pos: number;
9
+ line: number;
10
+ col: number;
11
+ } | undefined;
3
12
  export declare const hasStrictlyEnumReferences: (importsForExport: ImportMaps | undefined, identifier: string) => boolean;
4
13
  export declare const getIssueType: (hasOnlyNsReference: boolean, isType: boolean) => "exports" | "nsExports" | "nsTypes" | "types";
5
14
  export declare const findImportRef: (graph: ModuleGraph, importingFile: string, importedFile: string, identifier: string) => Import | undefined;
@@ -1,5 +1,8 @@
1
+ import { IMPORT_FLAGS, IMPORT_STAR, OPAQUE, SIDE_EFFECTS } from '../constants.js';
1
2
  import { getCachedExportedIdentifiers, setCachedExportedIdentifiers } from './cache.js';
2
3
  import { forEachAliasReExport, forEachNamespaceReExport, forEachPassThroughReExport, getStarReExportSources, } from './visitors.js';
4
+ const IGNORED_CYCLE_IMPORT_FLAGS = IMPORT_FLAGS.ENTRY | IMPORT_FLAGS.DYNAMIC;
5
+ export const getIgnoredCycleImportFlags = (includeDynamicImports) => includeDynamicImports ? IMPORT_FLAGS.ENTRY : IGNORED_CYCLE_IMPORT_FLAGS;
3
6
  export const getExportedIdentifiers = (graph, filePath, visited = new Set()) => {
4
7
  if (visited.has(filePath))
5
8
  return new Map();
@@ -49,6 +52,56 @@ export const getExportedIdentifiers = (graph, filePath, visited = new Set()) =>
49
52
  setCachedExportedIdentifiers(graph, filePath, identifiers);
50
53
  return identifiers;
51
54
  };
55
+ export const getRuntimeSuccessors = (node, ignoredFlags = IGNORED_CYCLE_IMPORT_FLAGS) => {
56
+ const successors = new Set();
57
+ for (const _import of node.imports.imports) {
58
+ if (_import.filePath &&
59
+ !_import.isTypeOnly &&
60
+ !(_import.modifiers & ignoredFlags) &&
61
+ node.imports.internal.has(_import.filePath)) {
62
+ successors.add(_import.filePath);
63
+ }
64
+ }
65
+ return successors;
66
+ };
67
+ const getImportKind = (importMaps, identifier, modifiers) => {
68
+ if (modifiers & IMPORT_FLAGS.DYNAMIC)
69
+ return 'dynamicImport';
70
+ if (identifier === IMPORT_STAR && importMaps.reExport.has(IMPORT_STAR))
71
+ return 'reExportStar';
72
+ if (identifier && importMaps.reExportNs.has(identifier))
73
+ return 'reExportNS';
74
+ if (identifier && importMaps.reExportAs.has(identifier))
75
+ return 'reExportAs';
76
+ if (identifier && importMaps.reExport.has(identifier))
77
+ return 'reExport';
78
+ if (identifier === IMPORT_STAR && importMaps.importNs.size > 0)
79
+ return 'importNS';
80
+ if (identifier && importMaps.importAs.has(identifier))
81
+ return 'importAs';
82
+ if (identifier === OPAQUE)
83
+ return 'dynamicImport';
84
+ if (identifier === SIDE_EFFECTS)
85
+ return 'sideEffectImport';
86
+ return 'import';
87
+ };
88
+ export const getRuntimeImport = (node, filePath, ignoredFlags = IGNORED_CYCLE_IMPORT_FLAGS) => {
89
+ const importMaps = node.imports.internal.get(filePath);
90
+ if (!importMaps)
91
+ return;
92
+ let result;
93
+ for (const _import of node.imports.imports) {
94
+ if (_import.filePath !== filePath || _import.isTypeOnly || _import.modifiers & ignoredFlags)
95
+ continue;
96
+ if (!result || _import.line < result.line || (_import.line === result.line && _import.col < result.col)) {
97
+ result = _import;
98
+ }
99
+ }
100
+ if (!result)
101
+ return;
102
+ const { specifier, pos, line, col, identifier, modifiers } = result;
103
+ return { kind: getImportKind(importMaps, identifier, modifiers), specifier, pos, line, col };
104
+ };
52
105
  export const hasStrictlyEnumReferences = (importsForExport, identifier) => {
53
106
  if (!importsForExport || !importsForExport.refs.has(identifier))
54
107
  return false;