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.
- package/LICENSE +1 -1
- package/dist/ConfigurationChief.d.ts +13 -1
- package/dist/ConfigurationChief.js +3 -0
- package/dist/ProjectPrincipal.d.ts +2 -0
- package/dist/ProjectPrincipal.js +7 -6
- package/dist/WorkspaceWorker.d.ts +3 -1
- package/dist/WorkspaceWorker.js +5 -2
- package/dist/binaries/bash-parser.js +55 -83
- package/dist/binaries/index.js +3 -0
- package/dist/binaries/resolvers/yarn.js +4 -3
- package/dist/compilers/compilers.d.ts +2 -0
- package/dist/compilers/compilers.js +15 -4
- package/dist/compilers/index.d.ts +30 -6
- package/dist/compilers/index.js +25 -10
- package/dist/compilers/less.d.ts +7 -0
- package/dist/compilers/less.js +37 -0
- package/dist/compilers/mdx.d.ts +1 -2
- package/dist/compilers/mdx.js +14 -8
- package/dist/compilers/scss.d.ts +3 -2
- package/dist/compilers/scss.js +17 -14
- package/dist/compilers/shared.d.ts +6 -0
- package/dist/compilers/shared.js +9 -0
- package/dist/compilers/style-preprocessors.d.ts +2 -0
- package/dist/compilers/style-preprocessors.js +41 -0
- package/dist/compilers/stylus.d.ts +7 -0
- package/dist/compilers/stylus.js +36 -0
- package/dist/constants.d.ts +5 -1
- package/dist/constants.js +7 -0
- package/dist/graph/analyze.js +37 -2
- package/dist/graph/build.js +23 -1
- package/dist/graph-explorer/explorer.d.ts +1 -0
- package/dist/graph-explorer/explorer.js +2 -0
- package/dist/graph-explorer/operations/find-all-cycles.d.ts +3 -0
- package/dist/graph-explorer/operations/find-all-cycles.js +43 -0
- package/dist/graph-explorer/operations/find-cycles.js +3 -9
- package/dist/graph-explorer/utils.d.ts +10 -1
- package/dist/graph-explorer/utils.js +53 -0
- package/dist/plugins/astro/compiler-mdx.js +11 -2
- package/dist/plugins/astro/compiler.js +8 -4
- package/dist/plugins/bun/index.js +28 -14
- package/dist/plugins/eve/index.d.ts +3 -0
- package/dist/plugins/eve/index.js +22 -0
- package/dist/plugins/execa/visitors/execa.js +10 -18
- package/dist/plugins/fumadocs/index.d.ts +3 -0
- package/dist/plugins/fumadocs/index.js +18 -0
- package/dist/plugins/github-actions/index.js +3 -2
- package/dist/plugins/graphql-codegen/index.js +9 -7
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.js +4 -0
- package/dist/plugins/jest/index.js +13 -3
- package/dist/plugins/next/index.js +23 -10
- package/dist/plugins/node/index.js +4 -1
- package/dist/plugins/nuxt/index.js +4 -2
- package/dist/plugins/pnpm/index.js +21 -1
- package/dist/plugins/pnpm/types.d.ts +6 -0
- package/dist/plugins/pnpm/types.js +1 -0
- package/dist/plugins/prettier/index.js +19 -3
- package/dist/plugins/prettier/types.d.ts +6 -1
- package/dist/plugins/prisma/compiler.js +3 -0
- package/dist/plugins/rspack/index.js +5 -0
- package/dist/plugins/serverless-framework/index.js +12 -1
- package/dist/plugins/serverless-framework/types.d.ts +5 -2
- package/dist/plugins/stencil/index.js +59 -1
- package/dist/plugins/storybook/index.js +7 -0
- package/dist/plugins/svelte/compiler.d.ts +2 -1
- package/dist/plugins/svelte/compiler.js +8 -1
- package/dist/plugins/tailwind/compiler.js +3 -0
- package/dist/plugins/tailwind/index.js +5 -1
- package/dist/plugins/typedoc/index.js +9 -3
- package/dist/plugins/typedoc/types.d.ts +2 -0
- package/dist/plugins/vite/visitors/importMetaGlob.js +1 -8
- package/dist/plugins/vue/compiler.d.ts +2 -1
- package/dist/plugins/vue/compiler.js +8 -1
- package/dist/plugins/webpack/visitors/requireContext.js +2 -13
- package/dist/plugins/yarn/index.js +13 -4
- package/dist/plugins/yarn/types.d.ts +10 -0
- package/dist/plugins/yarn/types.js +1 -0
- package/dist/plugins/zx/visitors/zx.js +4 -4
- package/dist/reporters/compact.js +1 -2
- package/dist/reporters/cycles.d.ts +3 -0
- package/dist/reporters/cycles.js +79 -0
- package/dist/reporters/index.d.ts +1 -0
- package/dist/reporters/index.js +2 -0
- package/dist/reporters/json.d.ts +3 -0
- package/dist/reporters/json.js +2 -1
- package/dist/reporters/symbols.js +1 -2
- package/dist/reporters/util/util.d.ts +3 -1
- package/dist/reporters/util/util.js +5 -1
- package/dist/schema/configuration.d.ts +40 -6
- package/dist/schema/configuration.js +6 -0
- package/dist/schema/plugins.d.ts +10 -0
- package/dist/schema/plugins.js +2 -0
- package/dist/types/PluginNames.d.ts +2 -2
- package/dist/types/PluginNames.js +2 -0
- package/dist/types/config.d.ts +10 -0
- package/dist/types/issues.d.ts +3 -0
- package/dist/types/module-graph.d.ts +7 -0
- package/dist/types/package-json.d.ts +4 -1
- package/dist/types.d.ts +1 -1
- package/dist/typescript/SourceFileManager.js +3 -2
- package/dist/typescript/ast-nodes.d.ts +4 -1
- package/dist/typescript/ast-nodes.js +60 -2
- package/dist/typescript/comments.js +22 -3
- package/dist/typescript/get-imports-and-exports.js +10 -0
- package/dist/typescript/glob-imports.d.ts +3 -0
- package/dist/typescript/glob-imports.js +49 -0
- package/dist/typescript/resolve-module-names.d.ts +2 -0
- package/dist/typescript/resolve-module-names.js +35 -1
- package/dist/typescript/visitors/calls.js +4 -6
- package/dist/typescript/visitors/imports.js +8 -8
- package/dist/typescript/visitors/script-visitors.js +4 -4
- package/dist/typescript/visitors/walk.js +6 -1
- package/dist/util/cli-arguments.d.ts +2 -1
- package/dist/util/cli-arguments.js +4 -2
- package/dist/util/create-options.d.ts +32 -5
- package/dist/util/create-options.js +4 -1
- package/dist/util/get-included-issue-types.d.ts +1 -0
- package/dist/util/get-included-issue-types.js +4 -2
- package/dist/util/issue-initializers.js +1 -1
- package/dist/util/load-tsconfig.js +17 -4
- package/dist/util/module-graph.js +1 -0
- package/dist/util/modules.js +25 -4
- package/dist/util/scripts.d.ts +7 -0
- package/dist/util/scripts.js +75 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- 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 };
|
package/dist/compilers/mdx.d.ts
CHANGED
package/dist/compilers/mdx.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fencedCodeBlockMatcher, frontmatterMatcher, inlineCodeMatcher } from './compilers.js';
|
|
2
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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 };
|
package/dist/compilers/scss.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { CompilerSync
|
|
1
|
+
import type { CompilerSync } from './types.ts';
|
|
2
|
+
export declare const compiler: CompilerSync;
|
|
2
3
|
declare const _default: {
|
|
3
|
-
|
|
4
|
+
dependencies: string[];
|
|
4
5
|
compiler: CompilerSync;
|
|
5
6
|
};
|
|
6
7
|
export default _default;
|
package/dist/compilers/scss.js
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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 {
|
|
42
|
+
export default { dependencies, compiler };
|
|
@@ -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,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,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 };
|
package/dist/constants.d.ts
CHANGED
|
@@ -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
|
};
|
package/dist/graph/analyze.js
CHANGED
|
@@ -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 {
|
|
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
|
: [];
|
package/dist/graph/build.js
CHANGED
|
@@ -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,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
|
|
11
|
+
if (!node)
|
|
11
12
|
return;
|
|
12
|
-
const
|
|
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;
|