knip 3.6.1 → 3.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ConfigurationChief.js +3 -1
- package/dist/IssueFixer.d.ts +21 -0
- package/dist/IssueFixer.js +75 -0
- package/dist/ProjectPrincipal.d.ts +3 -1
- package/dist/ProjectPrincipal.js +3 -1
- package/dist/cli.js +3 -1
- package/dist/index.js +15 -2
- package/dist/plugins/eslint/helpers.js +14 -4
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/exports.d.ts +3 -0
- package/dist/typescript/getImportsAndExports.d.ts +4 -1
- package/dist/typescript/getImportsAndExports.js +2 -2
- package/dist/typescript/visitors/exports/exportAssignment.js +3 -2
- package/dist/typescript/visitors/exports/exportDeclaration.js +3 -8
- package/dist/typescript/visitors/exports/exportKeyword.js +67 -12
- package/dist/typescript/visitors/exports/moduleExportsAccessExpression.js +20 -5
- package/dist/typescript/visitors/imports/jsDocType.js +8 -25
- package/dist/util/cli-arguments.d.ts +3 -1
- package/dist/util/cli-arguments.js +5 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
|
@@ -218,7 +218,9 @@ export class ConfigurationChief {
|
|
|
218
218
|
!micromatch.isMatch(name, this.ignoredWorkspacePatterns)));
|
|
219
219
|
}
|
|
220
220
|
getAvailableWorkspaceNames() {
|
|
221
|
-
return [
|
|
221
|
+
return [
|
|
222
|
+
...new Set([ROOT_WORKSPACE_NAME, ...this.manifestWorkspaces.keys(), ...this.additionalWorkspaceNames]),
|
|
223
|
+
].filter(name => !micromatch.isMatch(name, this.ignoredWorkspacePatterns));
|
|
222
224
|
}
|
|
223
225
|
getAvailableWorkspaceManifests(availableWorkspaceDirs) {
|
|
224
226
|
return availableWorkspaceDirs.map(dir => {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ExportPos } from './types/exports.js';
|
|
2
|
+
import type { Issues } from './types/issues.js';
|
|
3
|
+
export declare class IssueFixer {
|
|
4
|
+
isEnabled: boolean;
|
|
5
|
+
cwd: string;
|
|
6
|
+
isFixDependencies: boolean;
|
|
7
|
+
isFixUnusedTypes: boolean;
|
|
8
|
+
isFixUnusedExports: boolean;
|
|
9
|
+
unusedTypeNodes: Map<string, Set<[number, number]>>;
|
|
10
|
+
unusedExportNodes: Map<string, Set<[number, number]>>;
|
|
11
|
+
constructor({ isEnabled, cwd, fixTypes }: {
|
|
12
|
+
isEnabled: boolean;
|
|
13
|
+
cwd: string;
|
|
14
|
+
fixTypes: string[];
|
|
15
|
+
});
|
|
16
|
+
addUnusedTypeNode(filePath: string, fix: ExportPos): void;
|
|
17
|
+
addUnusedExportNode(filePath: string, fix: ExportPos): void;
|
|
18
|
+
fixIssues(issues: Issues): Promise<void>;
|
|
19
|
+
private removeUnusedExportKeywords;
|
|
20
|
+
private removeUnusedDependencies;
|
|
21
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
2
|
+
import NPMCliPackageJson from '@npmcli/package-json';
|
|
3
|
+
import { dirname, join } from './util/path.js';
|
|
4
|
+
export class IssueFixer {
|
|
5
|
+
isEnabled = false;
|
|
6
|
+
cwd = process.cwd();
|
|
7
|
+
isFixDependencies = true;
|
|
8
|
+
isFixUnusedTypes = true;
|
|
9
|
+
isFixUnusedExports = true;
|
|
10
|
+
unusedTypeNodes = new Map();
|
|
11
|
+
unusedExportNodes = new Map();
|
|
12
|
+
constructor({ isEnabled, cwd, fixTypes = [] }) {
|
|
13
|
+
this.isEnabled = isEnabled;
|
|
14
|
+
this.cwd = cwd;
|
|
15
|
+
this.isFixDependencies = fixTypes.length === 0 || fixTypes.includes('dependencies');
|
|
16
|
+
this.isFixUnusedTypes = fixTypes.length === 0 || fixTypes.includes('types');
|
|
17
|
+
this.isFixUnusedExports = fixTypes.length === 0 || fixTypes.includes('exports');
|
|
18
|
+
}
|
|
19
|
+
addUnusedTypeNode(filePath, fix) {
|
|
20
|
+
if (fix.length === 0)
|
|
21
|
+
return;
|
|
22
|
+
if (this.unusedTypeNodes.has(filePath))
|
|
23
|
+
this.unusedTypeNodes.get(filePath).add(fix);
|
|
24
|
+
else
|
|
25
|
+
this.unusedTypeNodes.set(filePath, new Set([fix]));
|
|
26
|
+
}
|
|
27
|
+
addUnusedExportNode(filePath, fix) {
|
|
28
|
+
if (fix.length === 0)
|
|
29
|
+
return;
|
|
30
|
+
if (this.unusedExportNodes.has(filePath))
|
|
31
|
+
this.unusedExportNodes.get(filePath).add(fix);
|
|
32
|
+
else
|
|
33
|
+
this.unusedExportNodes.set(filePath, new Set([fix]));
|
|
34
|
+
}
|
|
35
|
+
async fixIssues(issues) {
|
|
36
|
+
await this.removeUnusedExportKeywords();
|
|
37
|
+
await this.removeUnusedDependencies(issues);
|
|
38
|
+
}
|
|
39
|
+
async removeUnusedExportKeywords() {
|
|
40
|
+
const filePaths = new Set([...this.unusedTypeNodes.keys(), ...this.unusedExportNodes.keys()]);
|
|
41
|
+
for (const filePath of filePaths) {
|
|
42
|
+
const exportPositions = [
|
|
43
|
+
...(this.isFixUnusedTypes ? this.unusedTypeNodes.get(filePath) ?? [] : []),
|
|
44
|
+
...(this.isFixUnusedExports ? this.unusedExportNodes.get(filePath) ?? [] : []),
|
|
45
|
+
].sort((a, b) => b[0] - a[0]);
|
|
46
|
+
if (exportPositions.length > 0) {
|
|
47
|
+
const text = await readFile(filePath, 'utf-8');
|
|
48
|
+
const sourceFileText = exportPositions.reduce((text, [start, end]) => text.substring(0, start) + text.substring(end), text);
|
|
49
|
+
await writeFile(filePath, sourceFileText);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async removeUnusedDependencies(issues) {
|
|
54
|
+
if (!this.isFixDependencies)
|
|
55
|
+
return;
|
|
56
|
+
const filePaths = new Set([...Object.keys(issues.dependencies), ...Object.keys(issues.devDependencies)]);
|
|
57
|
+
for (const filePath of filePaths) {
|
|
58
|
+
const manifest = await NPMCliPackageJson.load(dirname(join(this.cwd, filePath)));
|
|
59
|
+
const pkg = manifest.content;
|
|
60
|
+
if (filePath in issues.dependencies) {
|
|
61
|
+
Object.keys(issues.dependencies[filePath]).forEach(dependency => {
|
|
62
|
+
if (pkg.dependencies)
|
|
63
|
+
delete pkg.dependencies[dependency];
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (filePath in issues.devDependencies) {
|
|
67
|
+
Object.keys(issues.devDependencies[filePath]).forEach(dependency => {
|
|
68
|
+
if (pkg.devDependencies)
|
|
69
|
+
delete pkg.devDependencies[dependency];
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
await manifest.save();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -38,8 +38,10 @@ export declare class ProjectPrincipal {
|
|
|
38
38
|
getUsedResolvedFiles(): string[];
|
|
39
39
|
private getProgramSourceFiles;
|
|
40
40
|
getUnreferencedFiles(): string[];
|
|
41
|
-
analyzeSourceFile(filePath: string, { skipTypeOnly }: {
|
|
41
|
+
analyzeSourceFile(filePath: string, { skipTypeOnly, isFixExports, isFixTypes }: {
|
|
42
42
|
skipTypeOnly: boolean;
|
|
43
|
+
isFixExports: boolean;
|
|
44
|
+
isFixTypes: boolean;
|
|
43
45
|
}): {
|
|
44
46
|
imports: {
|
|
45
47
|
internal: import("./types/imports.js").Imports;
|
package/dist/ProjectPrincipal.js
CHANGED
|
@@ -113,7 +113,7 @@ export class ProjectPrincipal {
|
|
|
113
113
|
const sourceFiles = this.getProgramSourceFiles();
|
|
114
114
|
return Array.from(this.projectPaths).filter(filePath => !sourceFiles.has(filePath));
|
|
115
115
|
}
|
|
116
|
-
analyzeSourceFile(filePath, { skipTypeOnly }) {
|
|
116
|
+
analyzeSourceFile(filePath, { skipTypeOnly, isFixExports, isFixTypes }) {
|
|
117
117
|
const sourceFile = this.backend.fileManager.getSourceFile(filePath);
|
|
118
118
|
if (!sourceFile)
|
|
119
119
|
throw new Error(`Unable to find ${filePath}`);
|
|
@@ -124,6 +124,8 @@ export class ProjectPrincipal {
|
|
|
124
124
|
const { imports, exports, scripts } = _getImportsAndExports(sourceFile, getResolvedModule, {
|
|
125
125
|
skipTypeOnly,
|
|
126
126
|
skipExports,
|
|
127
|
+
isFixExports,
|
|
128
|
+
isFixTypes,
|
|
127
129
|
});
|
|
128
130
|
const { internal, unresolved, external } = imports;
|
|
129
131
|
const unresolvedImports = new Set();
|
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import { Performance } from './util/Performance.js';
|
|
|
7
7
|
import { runPreprocessors, runReporters } from './util/reporter.js';
|
|
8
8
|
import { version } from './version.js';
|
|
9
9
|
import { main } from './index.js';
|
|
10
|
-
const { debug: isDebug = false, help: isHelp, 'max-issues': maxIssues = '0', 'no-config-hints': noConfigHints = false, 'no-exit-code': noExitCode = false, 'no-gitignore': isNoGitIgnore = false, 'no-progress': isNoProgress = false, 'include-entry-exports': isIncludeEntryExports = false, 'isolate-workspaces': isIsolateWorkspaces = false, performance: isObservePerf = false, production: isProduction = false, 'reporter-options': reporterOptions = '', 'preprocessor-options': preprocessorOptions = '', strict: isStrict = false, tsConfig, version: isVersion, } = parsedArgValues;
|
|
10
|
+
const { debug: isDebug = false, help: isHelp, 'max-issues': maxIssues = '0', 'no-config-hints': noConfigHints = false, 'no-exit-code': noExitCode = false, 'no-gitignore': isNoGitIgnore = false, 'no-progress': isNoProgress = false, 'include-entry-exports': isIncludeEntryExports = false, 'isolate-workspaces': isIsolateWorkspaces = false, performance: isObservePerf = false, production: isProduction = false, 'reporter-options': reporterOptions = '', 'preprocessor-options': preprocessorOptions = '', strict: isStrict = false, fix: isFix = false, 'fix-type': fixTypes = [], tsConfig, version: isVersion, } = parsedArgValues;
|
|
11
11
|
if (isHelp) {
|
|
12
12
|
console.log(helpText);
|
|
13
13
|
process.exit(0);
|
|
@@ -29,6 +29,8 @@ const run = async () => {
|
|
|
29
29
|
isShowProgress,
|
|
30
30
|
isIncludeEntryExports,
|
|
31
31
|
isIsolateWorkspaces,
|
|
32
|
+
isFix,
|
|
33
|
+
fixTypes: fixTypes.flatMap(type => type.split(',')),
|
|
32
34
|
});
|
|
33
35
|
const initialData = {
|
|
34
36
|
report,
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { ConfigurationChief } from './ConfigurationChief.js';
|
|
|
5
5
|
import { ConsoleStreamer } from './ConsoleStreamer.js';
|
|
6
6
|
import { DependencyDeputy } from './DependencyDeputy.js';
|
|
7
7
|
import { IssueCollector } from './IssueCollector.js';
|
|
8
|
+
import { IssueFixer } from './IssueFixer.js';
|
|
8
9
|
import { PrincipalFactory } from './PrincipalFactory.js';
|
|
9
10
|
import { ProjectPrincipal } from './ProjectPrincipal.js';
|
|
10
11
|
import { debugLogObject, debugLogArray, debugLog } from './util/debug.js';
|
|
@@ -16,7 +17,7 @@ import { _resolveSpecifier } from './util/require.js';
|
|
|
16
17
|
import { loadTSConfig } from './util/tsconfig-loader.js';
|
|
17
18
|
import { WorkspaceWorker } from './WorkspaceWorker.js';
|
|
18
19
|
export const main = async (unresolvedConfiguration) => {
|
|
19
|
-
const { cwd, tsConfigFile, gitignore, isStrict, isProduction, isShowProgress, isIncludeEntryExports, isIsolateWorkspaces, } = unresolvedConfiguration;
|
|
20
|
+
const { cwd, tsConfigFile, gitignore, isStrict, isProduction, isShowProgress, isIncludeEntryExports, isIsolateWorkspaces, isFix, fixTypes, } = unresolvedConfiguration;
|
|
20
21
|
debugLogObject('*', 'Unresolved configuration (from CLI arguments)', unresolvedConfiguration);
|
|
21
22
|
const chief = new ConfigurationChief({ cwd, isProduction, isStrict, isIncludeEntryExports });
|
|
22
23
|
const deputy = new DependencyDeputy({ isProduction, isStrict });
|
|
@@ -30,6 +31,7 @@ export const main = async (unresolvedConfiguration) => {
|
|
|
30
31
|
const report = chief.getIssueTypesToReport();
|
|
31
32
|
const rules = chief.getRules();
|
|
32
33
|
const filters = chief.getFilters();
|
|
34
|
+
const fixer = new IssueFixer({ isEnabled: isFix || fixTypes.length > 0, cwd, fixTypes });
|
|
33
35
|
const isReportDependencies = report.dependencies || report.unlisted || report.unresolved;
|
|
34
36
|
const isReportValues = report.exports || report.nsExports || report.classMembers;
|
|
35
37
|
const isReportTypes = report.types || report.nsTypes || report.enumMembers;
|
|
@@ -190,7 +192,11 @@ export const main = async (unresolvedConfiguration) => {
|
|
|
190
192
|
const analyzeSourceFile = (filePath, _principal = principal) => {
|
|
191
193
|
const workspace = chief.findWorkspaceByFilePath(filePath);
|
|
192
194
|
if (workspace) {
|
|
193
|
-
const { imports, exports, scripts } = _principal.analyzeSourceFile(filePath, {
|
|
195
|
+
const { imports, exports, scripts } = _principal.analyzeSourceFile(filePath, {
|
|
196
|
+
skipTypeOnly: isStrict,
|
|
197
|
+
isFixExports: fixer.isFixUnusedExports,
|
|
198
|
+
isFixTypes: fixer.isFixUnusedTypes,
|
|
199
|
+
});
|
|
194
200
|
const { internal, external, unresolved } = imports;
|
|
195
201
|
const { exported, duplicate } = exports;
|
|
196
202
|
if (exported.size > 0)
|
|
@@ -351,6 +357,10 @@ export const main = async (unresolvedConfiguration) => {
|
|
|
351
357
|
symbolType: exportedItem.type,
|
|
352
358
|
...principal.getPos(exportedItem.node, exportedItem.pos),
|
|
353
359
|
});
|
|
360
|
+
if (isType)
|
|
361
|
+
fixer.addUnusedTypeNode(filePath, exportedItem.fix);
|
|
362
|
+
else
|
|
363
|
+
fixer.addUnusedExportNode(filePath, exportedItem.fix);
|
|
354
364
|
}
|
|
355
365
|
}
|
|
356
366
|
}
|
|
@@ -374,6 +384,9 @@ export const main = async (unresolvedConfiguration) => {
|
|
|
374
384
|
const unusedIgnoredWorkspaces = chief.getUnusedIgnoredWorkspaces();
|
|
375
385
|
unusedIgnoredWorkspaces.forEach(identifier => collector.addConfigurationHint({ type: 'ignoreWorkspaces', identifier }));
|
|
376
386
|
const { issues, counters, configurationHints } = collector.getIssues();
|
|
387
|
+
if (isFix) {
|
|
388
|
+
await fixer.fixIssues(issues);
|
|
389
|
+
}
|
|
377
390
|
streamer.clear();
|
|
378
391
|
return { report, issues, counters, rules, configurationHints };
|
|
379
392
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { compact } from '../../util/array.js';
|
|
2
|
-
import { getPackageNameFromModuleSpecifier } from '../../util/modules.js';
|
|
3
|
-
import { basename, isInternal, dirname, toAbsolute } from '../../util/path.js';
|
|
2
|
+
import { getPackageNameFromFilePath, getPackageNameFromModuleSpecifier } from '../../util/modules.js';
|
|
3
|
+
import { basename, isInternal, dirname, toAbsolute, isAbsolute } from '../../util/path.js';
|
|
4
4
|
import { load } from '../../util/plugin.js';
|
|
5
5
|
import { _resolve } from '../../util/require.js';
|
|
6
6
|
import { getDependenciesFromConfig } from '../babel/index.js';
|
|
@@ -72,10 +72,20 @@ const getDependenciesFromSettings = (settings = {}) => {
|
|
|
72
72
|
if (settingKey === 'import/resolver') {
|
|
73
73
|
return (typeof settings === 'string' ? [settings] : Object.keys(settings))
|
|
74
74
|
.filter(key => key !== 'node')
|
|
75
|
-
.map(key =>
|
|
75
|
+
.map(key => {
|
|
76
|
+
if (isInternal(key))
|
|
77
|
+
return key;
|
|
78
|
+
if (isAbsolute(key))
|
|
79
|
+
return getPackageNameFromFilePath(key);
|
|
80
|
+
return `eslint-import-resolver-${key}`;
|
|
81
|
+
});
|
|
76
82
|
}
|
|
77
83
|
if (settingKey === 'import/parsers') {
|
|
78
|
-
return typeof settings === 'string' ? [settings] : Object.keys(settings)
|
|
84
|
+
return (typeof settings === 'string' ? [settings] : Object.keys(settings)).map(key => {
|
|
85
|
+
if (isAbsolute(key))
|
|
86
|
+
return getPackageNameFromFilePath(key);
|
|
87
|
+
return key;
|
|
88
|
+
});
|
|
79
89
|
}
|
|
80
90
|
});
|
|
81
91
|
};
|
package/dist/types/cli.d.ts
CHANGED
package/dist/types/exports.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import ts from 'typescript';
|
|
|
2
2
|
import type { SymbolType } from './issues.js';
|
|
3
3
|
type FilePath = string;
|
|
4
4
|
type Identifier = string;
|
|
5
|
+
export type ExportPos = [number, number] | [];
|
|
5
6
|
export type ExportItem = {
|
|
6
7
|
node: ts.Node;
|
|
7
8
|
pos: number;
|
|
@@ -9,12 +10,14 @@ export type ExportItem = {
|
|
|
9
10
|
type: SymbolType;
|
|
10
11
|
members?: ExportItemMember[];
|
|
11
12
|
jsDocTags?: Set<string>;
|
|
13
|
+
fix: ExportPos;
|
|
12
14
|
};
|
|
13
15
|
export type ExportItemMember = {
|
|
14
16
|
node: ts.Node;
|
|
15
17
|
identifier: Identifier;
|
|
16
18
|
pos: number;
|
|
17
19
|
type: SymbolType;
|
|
20
|
+
fix: ExportPos;
|
|
18
21
|
};
|
|
19
22
|
export type ExportItems = Map<string, Required<ExportItem>>;
|
|
20
23
|
export type Exports = Map<FilePath, ExportItems>;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import type { BoundSourceFile, GetResolvedModule } from './SourceFile.js';
|
|
3
|
-
import type { ExportItems as Exports, ExportItem } from '../types/exports.js';
|
|
3
|
+
import type { ExportItems as Exports, ExportItem, ExportPos } from '../types/exports.js';
|
|
4
4
|
import type { Imports, UnresolvedImport } from '../types/imports.js';
|
|
5
5
|
import type { IssueSymbol } from '../types/issues.js';
|
|
6
6
|
export type GetImportsAndExportsOptions = {
|
|
7
7
|
skipTypeOnly: boolean;
|
|
8
8
|
skipExports: boolean;
|
|
9
|
+
isFixExports: boolean;
|
|
10
|
+
isFixTypes: boolean;
|
|
9
11
|
};
|
|
10
12
|
export type AddImportOptions = {
|
|
11
13
|
specifier: string;
|
|
@@ -16,6 +18,7 @@ export type AddImportOptions = {
|
|
|
16
18
|
};
|
|
17
19
|
export type AddExportOptions = ExportItem & {
|
|
18
20
|
identifier: string;
|
|
21
|
+
fix: ExportPos;
|
|
19
22
|
};
|
|
20
23
|
export declare const _getImportsAndExports: (sourceFile: BoundSourceFile, getResolvedModule: GetResolvedModule, options: GetImportsAndExportsOptions) => {
|
|
21
24
|
imports: {
|
|
@@ -95,7 +95,7 @@ const getImportsAndExports = (sourceFile, getResolvedModule, options) => {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
};
|
|
98
|
-
const addExport = ({ node, identifier, type, pos, posDecl, members = [] }) => {
|
|
98
|
+
const addExport = ({ node, identifier, type, pos, posDecl, members = [], fix }) => {
|
|
99
99
|
if (options.skipExports)
|
|
100
100
|
return;
|
|
101
101
|
const jsDocTags = getJSDocTags(node);
|
|
@@ -106,7 +106,7 @@ const getImportsAndExports = (sourceFile, getResolvedModule, options) => {
|
|
|
106
106
|
exports.set(identifier, { ...item, members: crew, jsDocTags: tags });
|
|
107
107
|
}
|
|
108
108
|
else {
|
|
109
|
-
exports.set(identifier, { node, type, members, jsDocTags, pos, posDecl: posDecl ?? pos });
|
|
109
|
+
exports.set(identifier, { node, type, members, jsDocTags, pos, posDecl: posDecl ?? pos, fix });
|
|
110
110
|
}
|
|
111
111
|
if (!jsDocTags.has('@alias')) {
|
|
112
112
|
if (ts.isExportAssignment(node))
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import { SymbolType } from '../../../types/issues.js';
|
|
3
3
|
import { exportVisitor as visit } from '../index.js';
|
|
4
|
-
export default visit(() => true, node => {
|
|
4
|
+
export default visit(() => true, (node, { isFixExports }) => {
|
|
5
5
|
if (ts.isExportAssignment(node)) {
|
|
6
6
|
const pos = node.getChildAt(1).getStart();
|
|
7
|
-
|
|
7
|
+
const fix = isFixExports ? [node.getStart(), node.getEnd() + 1] : [];
|
|
8
|
+
return { node, identifier: 'default', type: SymbolType.UNKNOWN, pos, fix };
|
|
8
9
|
}
|
|
9
10
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import { SymbolType } from '../../../types/issues.js';
|
|
3
3
|
import { exportVisitor as visit } from '../index.js';
|
|
4
|
-
export default visit(() => true, node => {
|
|
4
|
+
export default visit(() => true, (node, { isFixExports, isFixTypes }) => {
|
|
5
5
|
if (ts.isExportDeclaration(node)) {
|
|
6
6
|
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
7
7
|
const type = node.isTypeOnly ? SymbolType.TYPE : SymbolType.UNKNOWN;
|
|
@@ -13,13 +13,8 @@ export default visit(() => true, node => {
|
|
|
13
13
|
const pos = element.name.pos;
|
|
14
14
|
const name = ts.getNameOfDeclaration(declaration);
|
|
15
15
|
const posDecl = name?.pos ?? declaration?.pos ?? pos;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
identifier,
|
|
19
|
-
type,
|
|
20
|
-
pos,
|
|
21
|
-
posDecl,
|
|
22
|
-
};
|
|
16
|
+
const fix = isFixExports || isFixTypes ? [element.getStart(), element.getEnd()] : [];
|
|
17
|
+
return { node: element, identifier, type, pos, posDecl, fix };
|
|
23
18
|
});
|
|
24
19
|
}
|
|
25
20
|
}
|
|
@@ -3,19 +3,21 @@ import { SymbolType } from '../../../types/issues.js';
|
|
|
3
3
|
import { compact } from '../../../util/array.js';
|
|
4
4
|
import { isGetOrSetAccessorDeclaration, isPrivateMember, stripQuotes } from '../../ast-helpers.js';
|
|
5
5
|
import { exportVisitor as visit } from '../index.js';
|
|
6
|
-
export default visit(() => true, node => {
|
|
7
|
-
const
|
|
8
|
-
if (
|
|
6
|
+
export default visit(() => true, (node, { isFixExports, isFixTypes }) => {
|
|
7
|
+
const exportKeyword = node.modifiers?.find(mod => mod.kind === ts.SyntaxKind.ExportKeyword);
|
|
8
|
+
if (exportKeyword) {
|
|
9
9
|
if (ts.isVariableStatement(node)) {
|
|
10
10
|
return node.declarationList.declarations.flatMap(declaration => {
|
|
11
11
|
if (ts.isObjectBindingPattern(declaration.name)) {
|
|
12
12
|
return compact(declaration.name.elements.map(element => {
|
|
13
13
|
if (ts.isIdentifier(element.name)) {
|
|
14
|
+
const fix = isFixExports ? [element.getStart(), element.getEnd()] : [];
|
|
14
15
|
return {
|
|
15
16
|
node: element,
|
|
16
17
|
identifier: element.name.escapedText.toString(),
|
|
17
18
|
type: SymbolType.UNKNOWN,
|
|
18
19
|
pos: element.name.getStart(),
|
|
20
|
+
fix,
|
|
19
21
|
};
|
|
20
22
|
}
|
|
21
23
|
}));
|
|
@@ -23,28 +25,47 @@ export default visit(() => true, node => {
|
|
|
23
25
|
else if (ts.isArrayBindingPattern(declaration.name)) {
|
|
24
26
|
return compact(declaration.name.elements.map(element => {
|
|
25
27
|
if (ts.isBindingElement(element)) {
|
|
28
|
+
const fix = isFixExports ? [element.getStart(), element.getEnd()] : [];
|
|
26
29
|
return {
|
|
27
30
|
node: element,
|
|
28
31
|
identifier: element.getText(),
|
|
29
32
|
type: SymbolType.UNKNOWN,
|
|
30
33
|
pos: element.getStart(),
|
|
34
|
+
fix,
|
|
31
35
|
};
|
|
32
36
|
}
|
|
33
37
|
}));
|
|
34
38
|
}
|
|
35
39
|
else {
|
|
36
40
|
const identifier = declaration.name.getText();
|
|
37
|
-
|
|
41
|
+
const fix = isFixExports ? [exportKeyword.getStart(), exportKeyword.getEnd() + 1] : [];
|
|
42
|
+
return {
|
|
43
|
+
node: declaration,
|
|
44
|
+
identifier,
|
|
45
|
+
type: SymbolType.UNKNOWN,
|
|
46
|
+
pos: declaration.name.getStart(),
|
|
47
|
+
fix,
|
|
48
|
+
};
|
|
38
49
|
}
|
|
39
50
|
});
|
|
40
51
|
}
|
|
52
|
+
const defaultKeyword = node.modifiers?.find(mod => mod.kind === ts.SyntaxKind.DefaultKeyword);
|
|
41
53
|
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
42
|
-
const identifier =
|
|
54
|
+
const identifier = defaultKeyword ? 'default' : node.name.getText();
|
|
43
55
|
const pos = (node.name ?? node.body ?? node).getStart();
|
|
44
|
-
|
|
56
|
+
const fix = isFixExports
|
|
57
|
+
? [exportKeyword.getStart(), (defaultKeyword ?? exportKeyword).getEnd() + 1]
|
|
58
|
+
: [];
|
|
59
|
+
return {
|
|
60
|
+
node,
|
|
61
|
+
identifier,
|
|
62
|
+
pos,
|
|
63
|
+
type: SymbolType.FUNCTION,
|
|
64
|
+
fix,
|
|
65
|
+
};
|
|
45
66
|
}
|
|
46
67
|
if (ts.isClassDeclaration(node) && node.name) {
|
|
47
|
-
const identifier =
|
|
68
|
+
const identifier = defaultKeyword ? 'default' : node.name.getText();
|
|
48
69
|
const pos = (node.name ?? node).getStart();
|
|
49
70
|
const members = node.members
|
|
50
71
|
.filter((member) => (ts.isPropertyDeclaration(member) ||
|
|
@@ -56,25 +77,59 @@ export default visit(() => true, node => {
|
|
|
56
77
|
identifier: member.name.getText(),
|
|
57
78
|
pos: member.name.getStart(),
|
|
58
79
|
type: SymbolType.MEMBER,
|
|
80
|
+
fix: [],
|
|
59
81
|
}));
|
|
60
|
-
|
|
82
|
+
const fix = isFixExports
|
|
83
|
+
? [exportKeyword.getStart(), (defaultKeyword ?? exportKeyword).getEnd() + 1]
|
|
84
|
+
: [];
|
|
85
|
+
return {
|
|
86
|
+
node,
|
|
87
|
+
identifier,
|
|
88
|
+
type: SymbolType.CLASS,
|
|
89
|
+
pos,
|
|
90
|
+
members,
|
|
91
|
+
fix,
|
|
92
|
+
};
|
|
61
93
|
}
|
|
62
94
|
if (ts.isTypeAliasDeclaration(node)) {
|
|
63
|
-
|
|
95
|
+
const fix = isFixTypes ? [exportKeyword.getStart(), exportKeyword.getEnd() + 1] : [];
|
|
96
|
+
return {
|
|
97
|
+
node,
|
|
98
|
+
identifier: node.name.getText(),
|
|
99
|
+
type: SymbolType.TYPE,
|
|
100
|
+
pos: node.name.getStart(),
|
|
101
|
+
fix,
|
|
102
|
+
};
|
|
64
103
|
}
|
|
65
104
|
if (ts.isInterfaceDeclaration(node)) {
|
|
66
|
-
|
|
105
|
+
const fix = isFixTypes ? [exportKeyword.getStart(), exportKeyword.getEnd() + 1] : [];
|
|
106
|
+
return {
|
|
107
|
+
node,
|
|
108
|
+
identifier: node.name.getText(),
|
|
109
|
+
type: SymbolType.INTERFACE,
|
|
110
|
+
pos: node.name.getStart(),
|
|
111
|
+
fix,
|
|
112
|
+
};
|
|
67
113
|
}
|
|
68
114
|
if (ts.isEnumDeclaration(node)) {
|
|
69
|
-
const identifier =
|
|
115
|
+
const identifier = defaultKeyword ? 'default' : node.name.getText();
|
|
70
116
|
const pos = node.name.getStart();
|
|
71
117
|
const members = node.members.map(member => ({
|
|
72
118
|
node: member,
|
|
73
119
|
identifier: stripQuotes(member.name.getText()),
|
|
74
120
|
pos: member.name.getStart(),
|
|
75
121
|
type: SymbolType.MEMBER,
|
|
122
|
+
fix: [],
|
|
76
123
|
}));
|
|
77
|
-
|
|
124
|
+
const fix = isFixTypes ? [exportKeyword.getStart(), exportKeyword.getEnd() + 1] : [];
|
|
125
|
+
return {
|
|
126
|
+
node,
|
|
127
|
+
identifier,
|
|
128
|
+
type: SymbolType.ENUM,
|
|
129
|
+
pos,
|
|
130
|
+
members,
|
|
131
|
+
fix,
|
|
132
|
+
};
|
|
78
133
|
}
|
|
79
134
|
}
|
|
80
135
|
});
|
|
@@ -4,7 +4,7 @@ import { stripQuotes } from '../../ast-helpers.js';
|
|
|
4
4
|
import { isJS } from '../helpers.js';
|
|
5
5
|
import { exportVisitor as visit } from '../index.js';
|
|
6
6
|
const isModuleExportsAccess = (node) => ts.isIdentifier(node.expression) && node.expression.escapedText === 'module' && node.name.escapedText === 'exports';
|
|
7
|
-
export default visit(isJS, node => {
|
|
7
|
+
export default visit(isJS, (node, { isFixExports }) => {
|
|
8
8
|
if (ts.isExpressionStatement(node)) {
|
|
9
9
|
if (ts.isBinaryExpression(node.expression)) {
|
|
10
10
|
if (ts.isPropertyAccessExpression(node.expression.left)) {
|
|
@@ -12,17 +12,25 @@ export default visit(isJS, node => {
|
|
|
12
12
|
isModuleExportsAccess(node.expression.left.expression)) {
|
|
13
13
|
const identifier = node.expression.left.name.getText();
|
|
14
14
|
const pos = node.expression.left.name.pos;
|
|
15
|
-
|
|
15
|
+
const fix = isFixExports ? [node.getStart(), node.getEnd()] : [];
|
|
16
|
+
return {
|
|
17
|
+
node: node.expression.left.name,
|
|
18
|
+
identifier,
|
|
19
|
+
type: SymbolType.UNKNOWN,
|
|
20
|
+
pos,
|
|
21
|
+
fix,
|
|
22
|
+
};
|
|
16
23
|
}
|
|
17
24
|
else if (isModuleExportsAccess(node.expression.left)) {
|
|
18
25
|
const expr = node.expression.right;
|
|
19
26
|
if (ts.isObjectLiteralExpression(expr) && expr.properties.every(ts.isShorthandPropertyAssignment)) {
|
|
20
27
|
return expr.properties.map(node => {
|
|
21
|
-
|
|
28
|
+
const fix = isFixExports ? [node.getStart(), node.getEnd()] : [];
|
|
29
|
+
return { node, identifier: node.getText(), type: SymbolType.UNKNOWN, pos: node.pos, fix };
|
|
22
30
|
});
|
|
23
31
|
}
|
|
24
32
|
else {
|
|
25
|
-
return { node, identifier: 'default', type: SymbolType.UNKNOWN, pos: expr.pos };
|
|
33
|
+
return { node, identifier: 'default', type: SymbolType.UNKNOWN, pos: expr.pos, fix: [] };
|
|
26
34
|
}
|
|
27
35
|
}
|
|
28
36
|
}
|
|
@@ -32,7 +40,14 @@ export default visit(isJS, node => {
|
|
|
32
40
|
isModuleExportsAccess(node.expression.left.expression)) {
|
|
33
41
|
const identifier = stripQuotes(node.expression.left.argumentExpression.getText());
|
|
34
42
|
const pos = node.expression.left.argumentExpression.pos;
|
|
35
|
-
|
|
43
|
+
const fix = isFixExports ? [node.getStart(), node.getEnd()] : [];
|
|
44
|
+
return {
|
|
45
|
+
node: node.expression.left.argumentExpression,
|
|
46
|
+
identifier,
|
|
47
|
+
type: SymbolType.UNKNOWN,
|
|
48
|
+
pos,
|
|
49
|
+
fix,
|
|
50
|
+
};
|
|
36
51
|
}
|
|
37
52
|
}
|
|
38
53
|
}
|
|
@@ -1,42 +1,25 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import { importVisitor as visit } from '../index.js';
|
|
3
3
|
const extractImportSpecifiers = (node) => {
|
|
4
|
-
const
|
|
4
|
+
const imports = [];
|
|
5
5
|
function visit(node) {
|
|
6
|
-
if (ts.
|
|
7
|
-
const
|
|
8
|
-
if (ts.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const importClause = arg.argument;
|
|
12
|
-
if (ts.isLiteralTypeNode(importClause) && ts.isStringLiteral(importClause.literal)) {
|
|
13
|
-
importSpecifiers.push(importClause.literal.text);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
if (ts.isJSDocTypeTag(node)) {
|
|
20
|
-
const typeNode = node.typeExpression?.type;
|
|
21
|
-
if (ts.isImportTypeNode(typeNode)) {
|
|
22
|
-
const importClause = typeNode.argument;
|
|
23
|
-
if (ts.isLiteralTypeNode(importClause) && ts.isStringLiteral(importClause.literal)) {
|
|
24
|
-
importSpecifiers.push(importClause.literal.text);
|
|
25
|
-
}
|
|
6
|
+
if (ts.isImportTypeNode(node)) {
|
|
7
|
+
const importClause = node.argument;
|
|
8
|
+
if (ts.isLiteralTypeNode(importClause) && ts.isStringLiteral(importClause.literal)) {
|
|
9
|
+
const identifier = node.qualifier && ts.isIdentifier(node.qualifier) ? String(node.qualifier.escapedText) : 'default';
|
|
10
|
+
imports.push({ specifier: importClause.literal.text, identifier });
|
|
26
11
|
}
|
|
27
12
|
}
|
|
28
13
|
ts.forEachChild(node, visit);
|
|
29
14
|
}
|
|
30
15
|
visit(node);
|
|
31
|
-
return
|
|
16
|
+
return imports;
|
|
32
17
|
};
|
|
33
18
|
export default visit(() => true, node => {
|
|
34
19
|
if ('jsDoc' in node && node.jsDoc) {
|
|
35
20
|
const jsDoc = node.jsDoc;
|
|
36
21
|
if (jsDoc.length > 0 && jsDoc[0].parent.parent === node.parent) {
|
|
37
|
-
return jsDoc
|
|
38
|
-
.flatMap(jsDoc => (jsDoc.tags ?? []).flatMap(extractImportSpecifiers))
|
|
39
|
-
.map(specifier => ({ specifier }));
|
|
22
|
+
return jsDoc.flatMap(jsDoc => (jsDoc.tags ?? []).flatMap(extractImportSpecifiers));
|
|
40
23
|
}
|
|
41
24
|
}
|
|
42
25
|
return [];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const helpText = "\u2702\uFE0F Find unused files, dependencies and exports in your JavaScript and TypeScript projects\n\nUsage: knip [options]\n\nOptions:\n -c, --config [file] Configuration file path (default: [.]knip.json[c], knip.js, knip.ts or package.json#knip)\n -t, --tsConfig [file] TypeScript configuration path (default: tsconfig.json)\n --production Analyze only production source files (e.g. no tests, devDependencies, exported types)\n --strict Consider only direct dependencies of workspace (not devDependencies, not other workspaces)\n -W, --workspace [dir] Analyze a single workspace (default: analyze all configured workspaces)\n --directory [dir] Run process from a different directory (default: cwd)\n --no-gitignore Don't use .gitignore\n --include Report only provided issue type(s), can be comma-separated or repeated (1)\n --exclude Exclude provided issue type(s) from report, can be comma-separated or repeated (1)\n --dependencies Shortcut for --include dependencies,unlisted,binaries,unresolved\n --exports Shortcut for --include exports,nsExports,classMembers,types,nsTypes,enumMembers,duplicates\n --include-entry-exports Include entry files when reporting unused exports\n --isolate-workspaces Isolated workspaces in monorepo\n -n, --no-progress Don't show dynamic progress updates (automatically enabled in CI environments)\n --preprocessor Preprocess the results before providing it to the reporter(s), can be repeated\n --preprocessor-options Pass extra options to the preprocessor (as JSON string, see --reporter-options example)\n --reporter Select reporter: symbols, compact, codeowners, json, can be repeated (default: symbols)\n --reporter-options Pass extra options to the reporter (as JSON string, see example)\n --no-config-hints Suppress configuration hints\n --no-exit-code Always exit with code zero (0)\n --max-issues Maximum number of issues before non-zero exit code (default: 0)\n -d, --debug Show debug output\n --performance Measure count and running time of expensive functions and display stats table\n -h, --help Print this help text\n -V, --version Print version\n\n(1) Issue types: files, dependencies, unlisted, unresolved, exports, nsExports, classMembers, types, nsTypes, enumMembers, duplicates\n\nExamples:\n\n$ knip\n$ knip --production\n$ knip --workspace packages/client --include files,dependencies\n$ knip -c ./config/knip.json --reporter compact\n$ knip --reporter codeowners --reporter-options '{\"path\":\".github/CODEOWNERS\"}'\n\nWebsite: https://knip.dev";
|
|
1
|
+
export declare const helpText = "\u2702\uFE0F Find unused files, dependencies and exports in your JavaScript and TypeScript projects\n\nUsage: knip [options]\n\nOptions:\n -c, --config [file] Configuration file path (default: [.]knip.json[c], knip.js, knip.ts or package.json#knip)\n -t, --tsConfig [file] TypeScript configuration path (default: tsconfig.json)\n --production Analyze only production source files (e.g. no tests, devDependencies, exported types)\n --strict Consider only direct dependencies of workspace (not devDependencies, not other workspaces)\n -W, --workspace [dir] Analyze a single workspace (default: analyze all configured workspaces)\n --directory [dir] Run process from a different directory (default: cwd)\n --no-gitignore Don't use .gitignore\n --include Report only provided issue type(s), can be comma-separated or repeated (1)\n --exclude Exclude provided issue type(s) from report, can be comma-separated or repeated (1)\n --dependencies Shortcut for --include dependencies,unlisted,binaries,unresolved\n --exports Shortcut for --include exports,nsExports,classMembers,types,nsTypes,enumMembers,duplicates\n --fix Fix issues\n --fix-type Fix only issues of type, can be comma-separated or repeated (2)\n --include-entry-exports Include entry files when reporting unused exports\n --isolate-workspaces Isolated workspaces in monorepo\n -n, --no-progress Don't show dynamic progress updates (automatically enabled in CI environments)\n --preprocessor Preprocess the results before providing it to the reporter(s), can be repeated\n --preprocessor-options Pass extra options to the preprocessor (as JSON string, see --reporter-options example)\n --reporter Select reporter: symbols, compact, codeowners, json, can be repeated (default: symbols)\n --reporter-options Pass extra options to the reporter (as JSON string, see example)\n --no-config-hints Suppress configuration hints\n --no-exit-code Always exit with code zero (0)\n --max-issues Maximum number of issues before non-zero exit code (default: 0)\n -d, --debug Show debug output\n --performance Measure count and running time of expensive functions and display stats table\n -h, --help Print this help text\n -V, --version Print version\n\n(1) Issue types: files, dependencies, unlisted, unresolved, exports, nsExports, classMembers, types, nsTypes, enumMembers, duplicates\n(2) Fixable issue types: dependencies, exports, types\n\nExamples:\n\n$ knip\n$ knip --production\n$ knip --workspace packages/client --include files,dependencies\n$ knip -c ./config/knip.json --reporter compact\n$ knip --reporter codeowners --reporter-options '{\"path\":\".github/CODEOWNERS\"}'\n\nWebsite: https://knip.dev";
|
|
2
2
|
declare const _default: {
|
|
3
3
|
config: string | undefined;
|
|
4
4
|
debug: boolean | undefined;
|
|
@@ -6,6 +6,8 @@ declare const _default: {
|
|
|
6
6
|
directory: string | undefined;
|
|
7
7
|
exclude: string[] | undefined;
|
|
8
8
|
exports: boolean | undefined;
|
|
9
|
+
fix: boolean | undefined;
|
|
10
|
+
'fix-type': string[] | undefined;
|
|
9
11
|
help: boolean | undefined;
|
|
10
12
|
'ignore-internal': boolean | undefined;
|
|
11
13
|
include: string[] | undefined;
|
|
@@ -15,6 +15,8 @@ Options:
|
|
|
15
15
|
--exclude Exclude provided issue type(s) from report, can be comma-separated or repeated (1)
|
|
16
16
|
--dependencies Shortcut for --include dependencies,unlisted,binaries,unresolved
|
|
17
17
|
--exports Shortcut for --include exports,nsExports,classMembers,types,nsTypes,enumMembers,duplicates
|
|
18
|
+
--fix Fix issues
|
|
19
|
+
--fix-type Fix only issues of type, can be comma-separated or repeated (2)
|
|
18
20
|
--include-entry-exports Include entry files when reporting unused exports
|
|
19
21
|
--isolate-workspaces Isolated workspaces in monorepo
|
|
20
22
|
-n, --no-progress Don't show dynamic progress updates (automatically enabled in CI environments)
|
|
@@ -31,6 +33,7 @@ Options:
|
|
|
31
33
|
-V, --version Print version
|
|
32
34
|
|
|
33
35
|
(1) Issue types: files, dependencies, unlisted, unresolved, exports, nsExports, classMembers, types, nsTypes, enumMembers, duplicates
|
|
36
|
+
(2) Fixable issue types: dependencies, exports, types
|
|
34
37
|
|
|
35
38
|
Examples:
|
|
36
39
|
|
|
@@ -51,6 +54,8 @@ try {
|
|
|
51
54
|
directory: { type: 'string' },
|
|
52
55
|
exclude: { type: 'string', multiple: true },
|
|
53
56
|
exports: { type: 'boolean' },
|
|
57
|
+
fix: { type: 'boolean' },
|
|
58
|
+
'fix-type': { type: 'string', multiple: true },
|
|
54
59
|
help: { type: 'boolean', short: 'h' },
|
|
55
60
|
'ignore-internal': { type: 'boolean' },
|
|
56
61
|
include: { type: 'string', multiple: true },
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "3.
|
|
1
|
+
export declare const version = "3.7.1";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '3.
|
|
1
|
+
export const version = '3.7.1';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "knip",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.1",
|
|
4
4
|
"description": "Find unused files, dependencies and exports in your TypeScript and JavaScript projects",
|
|
5
5
|
"homepage": "https://knip.dev",
|
|
6
6
|
"repository": {
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@ericcornelissen/bash-parser": "0.5.2",
|
|
57
57
|
"@npmcli/map-workspaces": "3.0.4",
|
|
58
|
+
"@npmcli/package-json": "5.0.0",
|
|
58
59
|
"@pkgjs/parseargs": "0.11.0",
|
|
59
60
|
"@pnpm/logger": "5.0.0",
|
|
60
61
|
"@pnpm/workspace.pkgs-graph": "^2.0.11",
|
|
@@ -80,7 +81,6 @@
|
|
|
80
81
|
"devDependencies": {
|
|
81
82
|
"@jest/types": "29.6.3",
|
|
82
83
|
"@knip/eslint-config": "0.0.0",
|
|
83
|
-
"@npmcli/package-json": "5.0.0",
|
|
84
84
|
"@release-it/bumper": "^6.0.1",
|
|
85
85
|
"@swc/cli": "^0.1.63",
|
|
86
86
|
"@swc/core": "^1.3.100",
|