knip 0.1.2 → 0.2.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/README.md +52 -39
- package/dist/cli.js +56 -20
- package/dist/help.js +7 -4
- package/dist/index.d.ts +3 -0
- package/dist/index.js +73 -32
- package/dist/reporters/compact.d.ts +2 -2
- package/dist/reporters/compact.js +38 -23
- package/dist/reporters/index.d.ts +4 -4
- package/dist/reporters/symbols.d.ts +2 -2
- package/dist/reporters/symbols.js +35 -23
- package/dist/types.d.ts +22 -8
- package/dist/util/config.d.ts +5 -7
- package/dist/util/config.js +25 -28
- package/dist/util/dependencies.d.ts +7 -0
- package/dist/util/dependencies.js +45 -0
- package/dist/util/path.d.ts +1 -0
- package/dist/util/path.js +28 -0
- package/dist/{util.d.ts → util/project.d.ts} +1 -2
- package/dist/{util.js → util/project.js} +3 -27
- package/dist/util/type.d.ts +2 -0
- package/dist/util/type.js +13 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -12,8 +12,9 @@ requires an analysis of all the right files in the project.
|
|
|
12
12
|
|
|
13
13
|
This is where Knip comes in:
|
|
14
14
|
|
|
15
|
-
- [x] Resolves all (unused) files in your project and reports **unused files and exports**.
|
|
15
|
+
- [x] Resolves all (unused) files in your project and reports **unused files, dependencies and exports**.
|
|
16
16
|
- [x] Verifies that exported symbols are actually used in other files, even when part of an imported namespace.
|
|
17
|
+
- [x] Finds dependencies not listed in `package.json`.
|
|
17
18
|
- [x] Finds duplicate exports of the same symbol.
|
|
18
19
|
- [x] Supports JavaScript inside TypeScript projects (`"allowJs": true`)
|
|
19
20
|
- [ ] Supports JavaScript-only projects with CommonJS and ESM (no `tsconfig.json`) - TODO
|
|
@@ -54,21 +55,20 @@ npx knip
|
|
|
54
55
|
|
|
55
56
|
This will analyze the project and output unused files, exports, types and duplicate exports.
|
|
56
57
|
|
|
57
|
-
Use `--
|
|
58
|
+
Use `--include files` when configuring knip the first time for faster initial results.
|
|
58
59
|
|
|
59
60
|
## How It Works
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
Knip works by creating two sets of files:
|
|
62
63
|
|
|
63
64
|
1. Production code is the set of files resolved from the `entryFiles`.
|
|
64
65
|
2. They are matched against the set of `projectFiles`.
|
|
65
|
-
3. The subset of project files that
|
|
66
|
+
3. The subset of project files that is not production code will be reported as unused files (in red).
|
|
66
67
|
4. Then the production code (in blue) will be scanned for unused exports.
|
|
67
68
|
|
|
68
69
|

|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
(using negation patterns such as `!**/*.test.ts`).
|
|
71
|
+
Please read on if you think you have too many results: [too many false positives?](#too-many-false-positives)
|
|
72
72
|
|
|
73
73
|
## Options
|
|
74
74
|
|
|
@@ -79,78 +79,91 @@ knip [options]
|
|
|
79
79
|
Options:
|
|
80
80
|
-c/--config [file] Configuration file path (default: ./knip.json or package.json#knip)
|
|
81
81
|
--cwd Working directory (default: current working directory)
|
|
82
|
-
--
|
|
83
|
-
--
|
|
84
|
-
--
|
|
82
|
+
--include Report only listed issue group(s) (see below)
|
|
83
|
+
--exclude Exclude issue group(s) from report (see below)
|
|
84
|
+
--dev Include `devDependencies` in report(s) (default: false)
|
|
85
85
|
--no-progress Don't show dynamic progress updates
|
|
86
|
+
--max-issues Maximum number of issues before non-zero exit code (default: 0)
|
|
86
87
|
--reporter Select reporter: symbols, compact (default: symbols)
|
|
87
88
|
--jsdoc Enable JSDoc parsing, with options: public (default: disabled)
|
|
88
89
|
|
|
90
|
+
Issue groups: files, dependencies, unlisted, exports, nsExports, types, nsTypes, duplicates
|
|
91
|
+
|
|
89
92
|
Examples:
|
|
90
93
|
|
|
91
94
|
$ knip
|
|
92
|
-
$ knip --cwd packages/client --
|
|
95
|
+
$ knip --cwd packages/client --include files
|
|
93
96
|
$ knip -c ./knip.js --reporter compact --jsdoc public
|
|
94
97
|
|
|
95
98
|
More info: https://github.com/webpro/knip
|
|
96
99
|
```
|
|
97
100
|
|
|
101
|
+
🚀 Knip is considerably faster when only the `files` and/or `duplicates` groups are included.
|
|
102
|
+
|
|
98
103
|
## Reading the report
|
|
99
104
|
|
|
100
105
|
After analyzing all the files resolved from the `entryFiles` against the `projectFiles`, the report contains the
|
|
101
106
|
following groups of issues:
|
|
102
107
|
|
|
103
|
-
- Unused
|
|
104
|
-
- Unused
|
|
105
|
-
-
|
|
106
|
-
|
|
107
|
-
- Unused
|
|
108
|
-
-
|
|
109
|
-
|
|
110
|
-
- Duplicate exports
|
|
108
|
+
- `files` - Unused files: did not find references to this file
|
|
109
|
+
- `dependencies` - Unused dependencies: did not find references to this dependency
|
|
110
|
+
- `unlisted` - Unlisted dependencies: this dependency is used, but not listed in package.json (1)
|
|
111
|
+
- `exports` - Unused exports: did not find references to this exported variable
|
|
112
|
+
- `nsExports` - Unused exports in namespaces: did not find direct references to this exported variable (2)
|
|
113
|
+
- `types` - Unused types: did not find references to this exported type
|
|
114
|
+
- `nsTypes` - Unused types in namespaces: did not find direct references to this exported variable (2)
|
|
115
|
+
- `duplicates` - Duplicate exports: the same thing is exported more than once with different names
|
|
111
116
|
|
|
112
|
-
Each group type
|
|
113
|
-
needs.
|
|
117
|
+
Each group type can be an `--include` or `--exclude` to slice & dice the report to your needs.
|
|
114
118
|
|
|
115
|
-
|
|
119
|
+
1. This may also include dependencies that could not be resolved properly, such as `local/dir/file.ts`.
|
|
120
|
+
2. The variable or type is not referenced directly, and has become a member of a namespace. That's why Knip is not sure
|
|
121
|
+
whether this export can be removed, so please look into it:
|
|
116
122
|
|
|
117
123
|
## Now what?
|
|
118
124
|
|
|
119
|
-
|
|
125
|
+
As always, make sure to backup files or use Git before deleting files or making changes. Run tests to verify results.
|
|
120
126
|
|
|
121
|
-
|
|
122
|
-
|
|
127
|
+
- Unused files can be deleted.
|
|
128
|
+
- Unused dependencies can be removed from `package.json`.
|
|
129
|
+
- Unlisted dependencies should be added to `package.json`.
|
|
130
|
+
- Unused exports and types: remove the `export` keyword in front of unused exports. Then you (or tools such as ESLint)
|
|
131
|
+
can see whether the variable or type is used within its own file. If this is not the case, it can be removed.
|
|
123
132
|
|
|
124
133
|
🔁 Repeat the process to reveal new unused files and exports. Sometimes it's so liberating to delete things.
|
|
125
134
|
|
|
126
|
-
##
|
|
135
|
+
## Too many false positives?
|
|
127
136
|
|
|
128
|
-
|
|
137
|
+
The default configuration for Knip is very strict and targets production code. For best results, it is recommended to
|
|
138
|
+
exclude files such as tests from the project files. Here's why: when including tests and other non-production files,
|
|
139
|
+
they may prevent production files from being reported as unused.
|
|
129
140
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
make it clear what production files can be removed (including dependent files!).
|
|
141
|
+
Excluding non-production files from the `projectFiles` allows Knip to understand what production code can be removed
|
|
142
|
+
(including dependent files!).
|
|
133
143
|
|
|
134
|
-
|
|
144
|
+
Non-production code includes files such as end-to-end tests, tooling, scripts, Storybook stories, etc.
|
|
135
145
|
|
|
136
|
-
To
|
|
137
|
-
|
|
146
|
+
To include both production and test files to analyze the project as a whole, include both sets of files to `entryFiles`,
|
|
147
|
+
and add `dev: true`:
|
|
138
148
|
|
|
139
149
|
```json
|
|
140
150
|
{
|
|
141
|
-
"
|
|
142
|
-
"
|
|
151
|
+
"dev": true,
|
|
152
|
+
"entryFiles": ["src/index.ts", "src/**/*.spec.ts", "src/**/*.e2e.ts"],
|
|
153
|
+
"projectFiles": ["src/**/*.ts"]
|
|
143
154
|
}
|
|
144
155
|
```
|
|
145
156
|
|
|
146
|
-
|
|
157
|
+
Knip will now report unused files and exports for the combined set of files as configured in `entryFiles`.
|
|
158
|
+
|
|
159
|
+
## More configuration examples
|
|
147
160
|
|
|
148
161
|
### Monorepos
|
|
149
162
|
|
|
150
163
|
#### Separate packages
|
|
151
164
|
|
|
152
|
-
In repos with multiple packages, the `--cwd` option comes in handy. With similar package structures, the
|
|
153
|
-
configured using globs:
|
|
165
|
+
In repos with multiple (published) packages, the `--cwd` option comes in handy. With similar package structures, the
|
|
166
|
+
packages can be configured using globs:
|
|
154
167
|
|
|
155
168
|
```json
|
|
156
169
|
{
|
|
@@ -173,8 +186,8 @@ knip --cwd packages/services --config knip.json
|
|
|
173
186
|
#### Connected projects
|
|
174
187
|
|
|
175
188
|
A good example of a large project setup is a monorepo, such as created with Nx. Let's take an example project
|
|
176
|
-
configuration for an Nx project using Next.js, Jest and Storybook. This can also be a JavaScript
|
|
177
|
-
add logic and/or comments:
|
|
189
|
+
configuration for an Nx project using Next.js, Jest and Storybook. This configuration file can also be a JavaScript
|
|
190
|
+
file, which allows to add logic and/or comments (e.g. `knip.js`):
|
|
178
191
|
|
|
179
192
|
```js
|
|
180
193
|
const entryFiles = ['apps/**/pages/**/*.{js,ts,tsx}'];
|
package/dist/cli.js
CHANGED
|
@@ -6,52 +6,88 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
const node_path_1 = __importDefault(require("node:path"));
|
|
8
8
|
const node_util_1 = require("node:util");
|
|
9
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
9
10
|
const help_1 = require("./help");
|
|
10
11
|
const config_1 = require("./util/config");
|
|
12
|
+
const path_1 = require("./util/path");
|
|
11
13
|
const reporters_1 = __importDefault(require("./reporters"));
|
|
12
14
|
const _1 = require(".");
|
|
13
|
-
const { values: { help, cwd: cwdArg, config = 'knip.json',
|
|
15
|
+
const { values: { help, cwd: cwdArg, config: configFilePath = 'knip.json', include = [], exclude = [], dev: isDev = false, 'no-progress': noProgress = false, reporter = 'symbols', jsdoc = [], 'max-issues': maxIssues = '0', }, } = (0, node_util_1.parseArgs)({
|
|
14
16
|
options: {
|
|
15
17
|
help: { type: 'boolean' },
|
|
16
|
-
cwd: { type: 'string' },
|
|
17
18
|
config: { type: 'string', short: 'c' },
|
|
18
|
-
|
|
19
|
+
cwd: { type: 'string' },
|
|
20
|
+
include: { type: 'string', multiple: true },
|
|
19
21
|
exclude: { type: 'string', multiple: true },
|
|
22
|
+
dev: { type: 'boolean' },
|
|
23
|
+
'max-issues': { type: 'string' },
|
|
20
24
|
'no-progress': { type: 'boolean' },
|
|
21
25
|
reporter: { type: 'string' },
|
|
22
26
|
jsdoc: { type: 'string', multiple: true },
|
|
23
|
-
'max-issues': { type: 'string' },
|
|
24
27
|
},
|
|
25
28
|
});
|
|
26
29
|
if (help) {
|
|
27
30
|
(0, help_1.printHelp)();
|
|
28
31
|
process.exit(0);
|
|
29
32
|
}
|
|
30
|
-
const cwd =
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
const isShowProgress = noProgress !== false || (process.stdout.isTTY && typeof process.stdout.cursorTo === 'function');
|
|
37
|
-
const report = reporter in reporters_1.default ? reporters_1.default[reporter] : require(node_path_1.default.join(cwd, reporter));
|
|
33
|
+
const cwd = process.cwd();
|
|
34
|
+
const workingDir = cwdArg ? node_path_1.default.resolve(cwdArg) : cwd;
|
|
35
|
+
const isShowProgress = noProgress === false ? process.stdout.isTTY && typeof process.stdout.cursorTo === 'function' : !noProgress;
|
|
36
|
+
const printReport = reporter in reporters_1.default ? reporters_1.default[reporter] : require(node_path_1.default.join(workingDir, reporter));
|
|
38
37
|
const main = async () => {
|
|
39
|
-
const
|
|
38
|
+
const localConfigurationPath = await (0, path_1.findFile)(workingDir, configFilePath);
|
|
39
|
+
const manifestPath = await (0, path_1.findFile)(workingDir, 'package.json');
|
|
40
|
+
if (!manifestPath || !localConfigurationPath) {
|
|
41
|
+
const location = workingDir === cwd ? 'current directory' : `${node_path_1.default.relative(cwd, workingDir)} or up.`;
|
|
42
|
+
console.error(`Unable to find ${configFilePath} (or package.json#knip) in ${location}\n`);
|
|
43
|
+
(0, help_1.printHelp)();
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
const localConfiguration = require(localConfigurationPath);
|
|
47
|
+
const manifest = require(manifestPath);
|
|
48
|
+
const resolvedConfig = (0, config_1.resolveConfig)(manifest.knip ?? localConfiguration, cwdArg);
|
|
40
49
|
if (!resolvedConfig) {
|
|
41
50
|
(0, help_1.printHelp)();
|
|
42
51
|
process.exit(1);
|
|
43
52
|
}
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
const report = (0, config_1.resolveIncludedIssueGroups)(include, exclude, resolvedConfig);
|
|
54
|
+
const tsConfigFile = await (0, path_1.findFile)(workingDir, 'tsconfig.json');
|
|
55
|
+
if (!tsConfigFile) {
|
|
56
|
+
const location = workingDir === cwd ? 'current directory' : `${node_path_1.default.relative(cwd, workingDir)} or up.`;
|
|
57
|
+
console.error(`Unable to find ${tsConfigFile} or package.json in ${location}\n`);
|
|
58
|
+
(0, help_1.printHelp)();
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
const tsConfig = typescript_1.default.readConfigFile(tsConfigFile, typescript_1.default.sys.readFile);
|
|
62
|
+
const tsConfigPaths = tsConfig.config.compilerOptions?.paths
|
|
63
|
+
? Object.keys(tsConfig.config.compilerOptions.paths).map(p => p.replace(/\*/g, '**'))
|
|
64
|
+
: [];
|
|
65
|
+
if (tsConfig.error) {
|
|
66
|
+
console.error(`An error occured when reading ${node_path_1.default.relative(cwd, tsConfigFile)}.\n`);
|
|
67
|
+
(0, help_1.printHelp)();
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
const config = {
|
|
71
|
+
workingDir,
|
|
72
|
+
report,
|
|
73
|
+
dependencies: Object.keys(manifest.dependencies ?? {}),
|
|
74
|
+
devDependencies: Object.keys(manifest.devDependencies ?? {}),
|
|
75
|
+
isDev: resolvedConfig.dev ?? isDev,
|
|
76
|
+
tsConfigPaths,
|
|
47
77
|
isShowProgress,
|
|
48
78
|
jsDocOptions: {
|
|
49
79
|
isReadPublicTag: jsdoc.includes('public'),
|
|
50
80
|
},
|
|
51
|
-
|
|
81
|
+
...resolvedConfig,
|
|
82
|
+
};
|
|
52
83
|
const { issues, counters } = await (0, _1.run)(config);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
84
|
+
printReport({ issues, workingDir, config });
|
|
85
|
+
const reportGroup = report.files ? 'files' : Object.keys(report).find(key => report[key]);
|
|
86
|
+
const counterGroup = reportGroup === 'unlisted' ? 'unresolved' : reportGroup;
|
|
87
|
+
if (counterGroup) {
|
|
88
|
+
const count = counters[counterGroup];
|
|
89
|
+
if (count > Number(maxIssues))
|
|
90
|
+
process.exit(count);
|
|
91
|
+
}
|
|
56
92
|
};
|
|
57
93
|
main();
|
package/dist/help.js
CHANGED
|
@@ -7,17 +7,20 @@ const printHelp = () => {
|
|
|
7
7
|
Options:
|
|
8
8
|
-c/--config [file] Configuration file path (default: ./knip.json or package.json#knip)
|
|
9
9
|
--cwd Working directory (default: current working directory)
|
|
10
|
-
--
|
|
11
|
-
--
|
|
12
|
-
--
|
|
10
|
+
--include Report only listed issue group(s) (see below)
|
|
11
|
+
--exclude Exclude issue group(s) from report (see below)
|
|
12
|
+
--dev Include \`devDependencies\` in report(s) (default: false)
|
|
13
13
|
--no-progress Don't show dynamic progress updates
|
|
14
|
+
--max-issues Maximum number of issues before non-zero exit code (default: 0)
|
|
14
15
|
--reporter Select reporter: symbols, compact (default: symbols)
|
|
15
16
|
--jsdoc Enable JSDoc parsing, with options: public (default: disabled)
|
|
16
17
|
|
|
18
|
+
Issue groups: files, dependencies, unlisted, exports, nsExports, types, nsTypes, duplicates
|
|
19
|
+
|
|
17
20
|
Examples:
|
|
18
21
|
|
|
19
22
|
$ knip
|
|
20
|
-
$ knip --cwd packages/client --
|
|
23
|
+
$ knip --cwd packages/client --include files
|
|
21
24
|
$ knip -c ./knip.js --reporter compact --jsdoc public
|
|
22
25
|
|
|
23
26
|
More info: https://github.com/webpro/knip`);
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -7,21 +7,27 @@ exports.run = void 0;
|
|
|
7
7
|
const node_path_1 = __importDefault(require("node:path"));
|
|
8
8
|
const ts_morph_1 = require("ts-morph");
|
|
9
9
|
const ts_morph_helpers_1 = require("ts-morph-helpers");
|
|
10
|
-
const
|
|
10
|
+
const project_1 = require("./util/project");
|
|
11
|
+
const type_1 = require("./util/type");
|
|
12
|
+
const dependencies_1 = require("./util/dependencies");
|
|
11
13
|
const log_1 = require("./log");
|
|
12
14
|
const lineRewriter = new log_1.LineRewriter();
|
|
13
15
|
async function run(configuration) {
|
|
14
|
-
const {
|
|
15
|
-
const
|
|
16
|
+
const { workingDir, isShowProgress, report, isDev, jsDocOptions } = configuration;
|
|
17
|
+
const { getUnresolvedDependencies, getUnusedDependencies, getUnusedDevDependencies } = (0, dependencies_1.getDependencyAnalyzer)(configuration);
|
|
18
|
+
const production = await (0, project_1.createProject)(workingDir, configuration.entryFiles);
|
|
16
19
|
const entryFiles = production.getSourceFiles();
|
|
17
20
|
production.resolveSourceFileDependencies();
|
|
18
21
|
const productionFiles = production.getSourceFiles();
|
|
19
|
-
const project = await (0,
|
|
22
|
+
const project = await (0, project_1.createProject)(workingDir, configuration.projectFiles);
|
|
20
23
|
const projectFiles = project.getSourceFiles();
|
|
21
|
-
const [usedProductionFiles, unreferencedProductionFiles] = (0,
|
|
22
|
-
const [, usedNonEntryFiles] = (0,
|
|
24
|
+
const [usedProductionFiles, unreferencedProductionFiles] = (0, project_1.partitionSourceFiles)(projectFiles, productionFiles);
|
|
25
|
+
const [usedEntryFiles, usedNonEntryFiles] = (0, project_1.partitionSourceFiles)(usedProductionFiles, entryFiles);
|
|
23
26
|
const issues = {
|
|
24
27
|
files: new Set(unreferencedProductionFiles.map(file => file.getFilePath())),
|
|
28
|
+
dependencies: new Set(),
|
|
29
|
+
devDependencies: new Set(),
|
|
30
|
+
unresolved: {},
|
|
25
31
|
exports: {},
|
|
26
32
|
types: {},
|
|
27
33
|
nsExports: {},
|
|
@@ -30,6 +36,9 @@ async function run(configuration) {
|
|
|
30
36
|
};
|
|
31
37
|
const counters = {
|
|
32
38
|
files: issues.files.size,
|
|
39
|
+
dependencies: issues.dependencies.size,
|
|
40
|
+
devDependencies: issues.dependencies.size,
|
|
41
|
+
unresolved: 0,
|
|
33
42
|
exports: 0,
|
|
34
43
|
types: 0,
|
|
35
44
|
nsExports: 0,
|
|
@@ -44,48 +53,72 @@ async function run(configuration) {
|
|
|
44
53
|
const total = unreferencedProductionFiles.length + usedNonEntryFiles.length;
|
|
45
54
|
const percentage = Math.floor((counter / total) * 100);
|
|
46
55
|
const messages = [(0, log_1.getLine)(`${percentage}%`, `of files processed (${counter} of ${total})`)];
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
report.files && messages.push((0, log_1.getLine)(unreferencedProductionFiles.length, 'unused files'));
|
|
57
|
+
report.unlisted && messages.push((0, log_1.getLine)(counters.unresolved, 'unlisted dependencies'));
|
|
58
|
+
report.exports && messages.push((0, log_1.getLine)(counters.exports, 'unused exports'));
|
|
59
|
+
report.nsExports && messages.push((0, log_1.getLine)(counters.nsExports, 'unused exports in namespace'));
|
|
60
|
+
report.types && messages.push((0, log_1.getLine)(counters.types, 'unused types'));
|
|
61
|
+
report.nsTypes && messages.push((0, log_1.getLine)(counters.nsTypes, 'unused types in namespace'));
|
|
62
|
+
report.duplicates && messages.push((0, log_1.getLine)(counters.duplicates, 'duplicate exports'));
|
|
53
63
|
if (counter < total) {
|
|
54
64
|
messages.push('');
|
|
55
|
-
messages.push(`Processing: ${node_path_1.default.relative(
|
|
65
|
+
messages.push(`Processing: ${node_path_1.default.relative(workingDir, item.filePath)}`);
|
|
56
66
|
}
|
|
57
67
|
lineRewriter.update(messages);
|
|
58
68
|
};
|
|
59
|
-
const
|
|
69
|
+
const addSymbolIssue = (issueType, issue) => {
|
|
60
70
|
const { filePath, symbol } = issue;
|
|
61
|
-
const key = node_path_1.default.relative(
|
|
71
|
+
const key = node_path_1.default.relative(workingDir, filePath);
|
|
62
72
|
issues[issueType][key] = issues[issueType][key] ?? {};
|
|
63
73
|
issues[issueType][key][symbol] = issue;
|
|
64
74
|
counters[issueType]++;
|
|
65
75
|
updateProcessingOutput(issue);
|
|
66
76
|
};
|
|
67
|
-
|
|
77
|
+
const addProjectIssue = (issueType, issue) => {
|
|
78
|
+
if (!issues[issueType].has(issue.symbol)) {
|
|
79
|
+
issues[issueType].add(issue.symbol);
|
|
80
|
+
counters[issueType]++;
|
|
81
|
+
}
|
|
82
|
+
updateProcessingOutput(issue);
|
|
83
|
+
};
|
|
84
|
+
if (report.dependencies || report.unlisted) {
|
|
85
|
+
usedEntryFiles.forEach(sourceFile => {
|
|
86
|
+
const unresolvedDependencies = getUnresolvedDependencies(sourceFile);
|
|
87
|
+
unresolvedDependencies.forEach(issue => addSymbolIssue('unresolved', issue));
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (report.dependencies ||
|
|
91
|
+
report.unlisted ||
|
|
92
|
+
report.exports ||
|
|
93
|
+
report.types ||
|
|
94
|
+
report.nsExports ||
|
|
95
|
+
report.nsTypes ||
|
|
96
|
+
report.duplicates) {
|
|
68
97
|
usedNonEntryFiles.forEach(sourceFile => {
|
|
69
98
|
const filePath = sourceFile.getFilePath();
|
|
99
|
+
if (report.dependencies || report.unlisted) {
|
|
100
|
+
const unresolvedDependencies = getUnresolvedDependencies(sourceFile);
|
|
101
|
+
unresolvedDependencies.forEach(issue => addSymbolIssue('unresolved', issue));
|
|
102
|
+
}
|
|
70
103
|
const exportDeclarations = sourceFile.getExportedDeclarations();
|
|
71
|
-
if (
|
|
104
|
+
if (report.duplicates) {
|
|
72
105
|
const duplicateExports = (0, ts_morph_helpers_1.findDuplicateExportedNames)(sourceFile);
|
|
73
106
|
duplicateExports.forEach(symbols => {
|
|
74
107
|
const symbol = symbols.join('|');
|
|
75
|
-
|
|
108
|
+
addSymbolIssue('duplicates', { filePath, symbol, symbols });
|
|
76
109
|
});
|
|
77
110
|
}
|
|
78
|
-
if (
|
|
111
|
+
if (report.exports || report.types || report.nsExports || report.nsTypes) {
|
|
79
112
|
const uniqueExportedSymbols = new Set([...exportDeclarations.values()].flat());
|
|
80
113
|
if (uniqueExportedSymbols.size === 1)
|
|
81
114
|
return;
|
|
82
115
|
exportDeclarations.forEach(declarations => {
|
|
83
116
|
declarations.forEach(declaration => {
|
|
84
|
-
const type = (0,
|
|
85
|
-
if (!
|
|
86
|
-
if (!
|
|
117
|
+
const type = (0, type_1.getType)(declaration);
|
|
118
|
+
if (!report.nsExports && !report.nsTypes) {
|
|
119
|
+
if (!report.types && type)
|
|
87
120
|
return;
|
|
88
|
-
if (!
|
|
121
|
+
if (!report.exports && !type)
|
|
89
122
|
return;
|
|
90
123
|
}
|
|
91
124
|
if (jsDocOptions.isReadPublicTag && ts_morph_1.ts.getJSDocPublicTag(declaration.compilerNode))
|
|
@@ -109,17 +142,17 @@ async function run(configuration) {
|
|
|
109
142
|
}
|
|
110
143
|
if (identifier) {
|
|
111
144
|
const identifierText = identifier.getText();
|
|
112
|
-
if (
|
|
145
|
+
if (report.exports && issues.exports[filePath]?.[identifierText])
|
|
113
146
|
return;
|
|
114
|
-
if (
|
|
147
|
+
if (report.types && issues.types[filePath]?.[identifierText])
|
|
115
148
|
return;
|
|
116
|
-
if (
|
|
149
|
+
if (report.nsExports && issues.nsExports[filePath]?.[identifierText])
|
|
117
150
|
return;
|
|
118
|
-
if (
|
|
151
|
+
if (report.nsTypes && issues.nsTypes[filePath]?.[identifierText])
|
|
119
152
|
return;
|
|
120
153
|
const refs = identifier.findReferences();
|
|
121
154
|
if (refs.length === 0) {
|
|
122
|
-
|
|
155
|
+
addSymbolIssue('exports', { filePath, symbol: identifierText });
|
|
123
156
|
}
|
|
124
157
|
else {
|
|
125
158
|
const refFiles = new Set(refs.map(r => r.compilerObject.references.map(r => r.fileName)).flat());
|
|
@@ -128,17 +161,17 @@ async function run(configuration) {
|
|
|
128
161
|
return;
|
|
129
162
|
if ((0, ts_morph_helpers_1.findReferencingNamespaceNodes)(sourceFile).length > 0) {
|
|
130
163
|
if (type) {
|
|
131
|
-
|
|
164
|
+
addSymbolIssue('nsTypes', { filePath, symbol: identifierText, symbolType: type });
|
|
132
165
|
}
|
|
133
166
|
else {
|
|
134
|
-
|
|
167
|
+
addSymbolIssue('nsExports', { filePath, symbol: identifierText });
|
|
135
168
|
}
|
|
136
169
|
}
|
|
137
170
|
else if (type) {
|
|
138
|
-
|
|
171
|
+
addSymbolIssue('types', { filePath, symbol: identifierText, symbolType: type });
|
|
139
172
|
}
|
|
140
173
|
else {
|
|
141
|
-
|
|
174
|
+
addSymbolIssue('exports', { filePath, symbol: identifierText });
|
|
142
175
|
}
|
|
143
176
|
}
|
|
144
177
|
}
|
|
@@ -148,6 +181,14 @@ async function run(configuration) {
|
|
|
148
181
|
counters.processed++;
|
|
149
182
|
});
|
|
150
183
|
}
|
|
184
|
+
if (report.dependencies) {
|
|
185
|
+
const unusedDependencies = getUnusedDependencies();
|
|
186
|
+
unusedDependencies.forEach(symbol => addProjectIssue('dependencies', { filePath: '', symbol }));
|
|
187
|
+
if (isDev) {
|
|
188
|
+
const unusedDevDependencies = getUnusedDevDependencies();
|
|
189
|
+
unusedDevDependencies.forEach(symbol => addProjectIssue('devDependencies', { filePath: '', symbol }));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
151
192
|
if (isShowProgress)
|
|
152
193
|
lineRewriter.resetLines();
|
|
153
194
|
return { issues, counters };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Issues, Configuration } from '../types';
|
|
2
|
-
declare const _default: ({ issues, config,
|
|
2
|
+
declare const _default: ({ issues, config, workingDir }: {
|
|
3
3
|
issues: Issues;
|
|
4
4
|
config: Configuration;
|
|
5
|
-
|
|
5
|
+
workingDir: string;
|
|
6
6
|
}) => void;
|
|
7
7
|
export default _default;
|
|
@@ -4,67 +4,82 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const node_path_1 = __importDefault(require("node:path"));
|
|
7
|
-
const logIssueLine = (
|
|
8
|
-
console.log(`${node_path_1.default.relative(
|
|
7
|
+
const logIssueLine = (workingDir, filePath, symbols) => {
|
|
8
|
+
console.log(`${node_path_1.default.relative(workingDir, filePath)}${symbols ? `: ${symbols.join(', ')}` : ''}`);
|
|
9
9
|
};
|
|
10
|
-
const logIssueGroupResult = (issues,
|
|
10
|
+
const logIssueGroupResult = (issues, workingDir, title) => {
|
|
11
11
|
title && console.log(`--- ${title} (${issues.length})`);
|
|
12
12
|
if (issues.length) {
|
|
13
|
-
issues.sort().forEach(
|
|
13
|
+
issues.sort().forEach(value => console.log(value.startsWith('/') ? node_path_1.default.relative(workingDir, value) : value));
|
|
14
14
|
}
|
|
15
15
|
else {
|
|
16
|
-
console.log('
|
|
16
|
+
console.log('Not found');
|
|
17
17
|
}
|
|
18
18
|
};
|
|
19
|
-
const logIssueGroupResults = (issues,
|
|
19
|
+
const logIssueGroupResults = (issues, workingDir, title) => {
|
|
20
20
|
title && console.log(`--- ${title} (${issues.length})`);
|
|
21
21
|
if (issues.length) {
|
|
22
22
|
const sortedByFilePath = issues.sort((a, b) => (a.filePath > b.filePath ? 1 : -1));
|
|
23
|
-
sortedByFilePath.forEach(({ filePath, symbols }) => logIssueLine(
|
|
23
|
+
sortedByFilePath.forEach(({ filePath, symbols }) => logIssueLine(workingDir, filePath, symbols));
|
|
24
24
|
}
|
|
25
25
|
else {
|
|
26
|
-
console.log('
|
|
26
|
+
console.log('Not found');
|
|
27
27
|
}
|
|
28
28
|
};
|
|
29
|
-
exports.default = ({ issues, config,
|
|
30
|
-
const {
|
|
31
|
-
const reportMultipleGroups = Object.values(
|
|
32
|
-
if (
|
|
29
|
+
exports.default = ({ issues, config, workingDir }) => {
|
|
30
|
+
const { report, isDev } = config;
|
|
31
|
+
const reportMultipleGroups = Object.values(report).filter(Boolean).length > 1;
|
|
32
|
+
if (report.files) {
|
|
33
33
|
const unreferencedFiles = Array.from(issues.files);
|
|
34
|
-
logIssueGroupResult(unreferencedFiles,
|
|
34
|
+
logIssueGroupResult(unreferencedFiles, workingDir, reportMultipleGroups && 'UNUSED FILES');
|
|
35
35
|
}
|
|
36
|
-
if (
|
|
36
|
+
if (report.dependencies) {
|
|
37
|
+
const unreferencedDependencies = Array.from(issues.dependencies);
|
|
38
|
+
logIssueGroupResult(unreferencedDependencies, workingDir, reportMultipleGroups && 'UNUSED DEPENDENCIES');
|
|
39
|
+
}
|
|
40
|
+
if (report.dependencies && isDev) {
|
|
41
|
+
const unreferencedDevDependencies = Array.from(issues.devDependencies);
|
|
42
|
+
logIssueGroupResult(unreferencedDevDependencies, workingDir, reportMultipleGroups && 'UNUSED DEV DEPENDENCIES');
|
|
43
|
+
}
|
|
44
|
+
if (report.unlisted) {
|
|
45
|
+
const unreferencedDependencies = Object.values(issues.unresolved).map(issues => {
|
|
46
|
+
const items = Object.values(issues);
|
|
47
|
+
return { ...items[0], symbols: items.map(i => i.symbol) };
|
|
48
|
+
});
|
|
49
|
+
logIssueGroupResults(unreferencedDependencies, workingDir, reportMultipleGroups && 'UNLISTED DEPENDENCIES');
|
|
50
|
+
}
|
|
51
|
+
if (report.exports) {
|
|
37
52
|
const unreferencedExports = Object.values(issues.exports).map(issues => {
|
|
38
53
|
const items = Object.values(issues);
|
|
39
54
|
return { ...items[0], symbols: items.map(i => i.symbol) };
|
|
40
55
|
});
|
|
41
|
-
logIssueGroupResults(unreferencedExports,
|
|
56
|
+
logIssueGroupResults(unreferencedExports, workingDir, reportMultipleGroups && 'UNUSED EXPORTS');
|
|
42
57
|
}
|
|
43
|
-
if (
|
|
58
|
+
if (report.nsExports) {
|
|
44
59
|
const unreferencedNsExports = Object.values(issues.nsExports).map(issues => {
|
|
45
60
|
const items = Object.values(issues);
|
|
46
61
|
return { ...items[0], symbols: items.map(i => i.symbol) };
|
|
47
62
|
});
|
|
48
|
-
logIssueGroupResults(unreferencedNsExports,
|
|
63
|
+
logIssueGroupResults(unreferencedNsExports, workingDir, reportMultipleGroups && 'UNUSED EXPORTS IN NAMESPACE');
|
|
49
64
|
}
|
|
50
|
-
if (
|
|
65
|
+
if (report.types) {
|
|
51
66
|
const unreferencedTypes = Object.values(issues.types).map(issues => {
|
|
52
67
|
const items = Object.values(issues);
|
|
53
68
|
return { ...items[0], symbols: items.map(i => i.symbol) };
|
|
54
69
|
});
|
|
55
|
-
logIssueGroupResults(unreferencedTypes,
|
|
70
|
+
logIssueGroupResults(unreferencedTypes, workingDir, reportMultipleGroups && 'UNUSED TYPES');
|
|
56
71
|
}
|
|
57
|
-
if (
|
|
72
|
+
if (report.nsTypes) {
|
|
58
73
|
const unreferencedNsTypes = Object.values(issues.nsTypes).map(issues => {
|
|
59
74
|
const items = Object.values(issues);
|
|
60
75
|
return { ...items[0], symbols: items.map(i => i.symbol) };
|
|
61
76
|
});
|
|
62
|
-
logIssueGroupResults(unreferencedNsTypes,
|
|
77
|
+
logIssueGroupResults(unreferencedNsTypes, workingDir, reportMultipleGroups && 'UNUSED TYPES IN NAMESPACE');
|
|
63
78
|
}
|
|
64
|
-
if (
|
|
79
|
+
if (report.duplicates) {
|
|
65
80
|
const unreferencedDuplicates = Object.values(issues.duplicates)
|
|
66
81
|
.map(issues => Object.values(issues))
|
|
67
82
|
.flat();
|
|
68
|
-
logIssueGroupResults(unreferencedDuplicates,
|
|
83
|
+
logIssueGroupResults(unreferencedDuplicates, workingDir, reportMultipleGroups && 'DUPLICATE EXPORTS');
|
|
69
84
|
}
|
|
70
85
|
};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
declare const _default: {
|
|
2
|
-
symbols: ({ issues, config,
|
|
2
|
+
symbols: ({ issues, config, workingDir }: {
|
|
3
3
|
issues: import("../types").Issues;
|
|
4
4
|
config: import("../types").Configuration;
|
|
5
|
-
|
|
5
|
+
workingDir: string;
|
|
6
6
|
}) => void;
|
|
7
|
-
compact: ({ issues, config,
|
|
7
|
+
compact: ({ issues, config, workingDir }: {
|
|
8
8
|
issues: import("../types").Issues;
|
|
9
9
|
config: import("../types").Configuration;
|
|
10
|
-
|
|
10
|
+
workingDir: string;
|
|
11
11
|
}) => void;
|
|
12
12
|
};
|
|
13
13
|
export default _default;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Issues, Configuration } from '../types';
|
|
2
|
-
declare const _default: ({ issues, config,
|
|
2
|
+
declare const _default: ({ issues, config, workingDir }: {
|
|
3
3
|
issues: Issues;
|
|
4
4
|
config: Configuration;
|
|
5
|
-
|
|
5
|
+
workingDir: string;
|
|
6
6
|
}) => void;
|
|
7
7
|
export default _default;
|
|
@@ -4,55 +4,67 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const node_path_1 = __importDefault(require("node:path"));
|
|
7
|
-
const logIssueLine = ({ issue,
|
|
7
|
+
const logIssueLine = ({ issue, workingDir, padding }) => {
|
|
8
8
|
const symbols = issue.symbols ? issue.symbols.join(', ') : issue.symbol;
|
|
9
|
-
console.log(`${symbols.padEnd(padding + 2)}${issue.symbolType?.padEnd(11) || ''}${node_path_1.default.relative(
|
|
9
|
+
console.log(`${symbols.padEnd(padding + 2)}${issue.symbolType?.padEnd(11) || ''}${node_path_1.default.relative(workingDir, issue.filePath)}`);
|
|
10
10
|
};
|
|
11
|
-
const logIssueGroupResult = (issues,
|
|
11
|
+
const logIssueGroupResult = (issues, workingDir, title) => {
|
|
12
12
|
title && console.log(`--- ${title} (${issues.length})`);
|
|
13
13
|
if (issues.length) {
|
|
14
|
-
issues.sort().forEach(
|
|
14
|
+
issues.sort().forEach(value => console.log(value.startsWith('/') ? node_path_1.default.relative(workingDir, value) : value));
|
|
15
15
|
}
|
|
16
16
|
else {
|
|
17
|
-
console.log('
|
|
17
|
+
console.log('Not found');
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
|
-
const logIssueGroupResults = (issues,
|
|
20
|
+
const logIssueGroupResults = (issues, workingDir, title) => {
|
|
21
21
|
title && console.log(`--- ${title} (${issues.length})`);
|
|
22
22
|
if (issues.length) {
|
|
23
23
|
const sortedByFilePath = issues.sort((a, b) => (a.filePath > b.filePath ? 1 : -1));
|
|
24
24
|
const padding = [...issues].sort((a, b) => b.symbol.length - a.symbol.length)[0].symbol.length;
|
|
25
|
-
sortedByFilePath.forEach(issue => logIssueLine({ issue,
|
|
25
|
+
sortedByFilePath.forEach(issue => logIssueLine({ issue, workingDir, padding }));
|
|
26
26
|
}
|
|
27
27
|
else {
|
|
28
|
-
console.log('
|
|
28
|
+
console.log('Not found');
|
|
29
29
|
}
|
|
30
30
|
};
|
|
31
|
-
exports.default = ({ issues, config,
|
|
32
|
-
const {
|
|
33
|
-
const reportMultipleGroups = Object.values(
|
|
34
|
-
if (
|
|
31
|
+
exports.default = ({ issues, config, workingDir }) => {
|
|
32
|
+
const { report, isDev } = config;
|
|
33
|
+
const reportMultipleGroups = Object.values(report).filter(Boolean).length > 1;
|
|
34
|
+
if (report.files) {
|
|
35
35
|
const unreferencedFiles = Array.from(issues.files);
|
|
36
|
-
logIssueGroupResult(unreferencedFiles,
|
|
36
|
+
logIssueGroupResult(unreferencedFiles, workingDir, reportMultipleGroups && 'UNUSED FILES');
|
|
37
37
|
}
|
|
38
|
-
if (
|
|
38
|
+
if (report.dependencies) {
|
|
39
|
+
const unreferencedDependencies = Array.from(issues.dependencies);
|
|
40
|
+
logIssueGroupResult(unreferencedDependencies, workingDir, reportMultipleGroups && 'UNUSED DEPENDENCIES');
|
|
41
|
+
}
|
|
42
|
+
if (report.dependencies && isDev) {
|
|
43
|
+
const unreferencedDevDependencies = Array.from(issues.devDependencies);
|
|
44
|
+
logIssueGroupResult(unreferencedDevDependencies, workingDir, reportMultipleGroups && 'UNUSED DEV DEPENDENCIES');
|
|
45
|
+
}
|
|
46
|
+
if (report.unlisted) {
|
|
47
|
+
const unresolvedDependencies = Object.values(issues.unresolved).map(Object.values).flat();
|
|
48
|
+
logIssueGroupResults(unresolvedDependencies, workingDir, reportMultipleGroups && 'UNLISTED DEPENDENCIES');
|
|
49
|
+
}
|
|
50
|
+
if (report.exports) {
|
|
39
51
|
const unreferencedExports = Object.values(issues.exports).map(Object.values).flat();
|
|
40
|
-
logIssueGroupResults(unreferencedExports,
|
|
52
|
+
logIssueGroupResults(unreferencedExports, workingDir, reportMultipleGroups && 'UNUSED EXPORTS');
|
|
41
53
|
}
|
|
42
|
-
if (
|
|
54
|
+
if (report.nsExports) {
|
|
43
55
|
const unreferencedNsExports = Object.values(issues.nsExports).map(Object.values).flat();
|
|
44
|
-
logIssueGroupResults(unreferencedNsExports,
|
|
56
|
+
logIssueGroupResults(unreferencedNsExports, workingDir, reportMultipleGroups && 'UNUSED EXPORTS IN NAMESPACE');
|
|
45
57
|
}
|
|
46
|
-
if (
|
|
58
|
+
if (report.types) {
|
|
47
59
|
const unreferencedTypes = Object.values(issues.types).map(Object.values).flat();
|
|
48
|
-
logIssueGroupResults(unreferencedTypes,
|
|
60
|
+
logIssueGroupResults(unreferencedTypes, workingDir, reportMultipleGroups && 'UNUSED TYPES');
|
|
49
61
|
}
|
|
50
|
-
if (
|
|
62
|
+
if (report.nsTypes) {
|
|
51
63
|
const unreferencedNsTypes = Object.values(issues.nsTypes).map(Object.values).flat();
|
|
52
|
-
logIssueGroupResults(unreferencedNsTypes,
|
|
64
|
+
logIssueGroupResults(unreferencedNsTypes, workingDir, reportMultipleGroups && 'UNUSED TYPES IN NAMESPACE');
|
|
53
65
|
}
|
|
54
|
-
if (
|
|
66
|
+
if (report.duplicates) {
|
|
55
67
|
const unreferencedDuplicates = Object.values(issues.duplicates).map(Object.values).flat();
|
|
56
|
-
logIssueGroupResults(unreferencedDuplicates,
|
|
68
|
+
logIssueGroupResults(unreferencedDuplicates, workingDir, reportMultipleGroups && 'DUPLICATE EXPORTS');
|
|
57
69
|
}
|
|
58
70
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
declare type FilePath = string;
|
|
2
1
|
declare type SymbolType = 'type' | 'interface' | 'enum';
|
|
3
|
-
declare type UnusedFileIssues = Set<
|
|
2
|
+
declare type UnusedFileIssues = Set<string>;
|
|
4
3
|
declare type UnusedExportIssues = Record<string, Record<string, Issue>>;
|
|
4
|
+
declare type UnresolvedDependencyIssues = Record<string, Record<string, Issue>>;
|
|
5
|
+
declare type UnusedDependencyIssues = Set<string>;
|
|
5
6
|
export declare type Issue = {
|
|
6
|
-
filePath:
|
|
7
|
+
filePath: string;
|
|
7
8
|
symbol: string;
|
|
8
9
|
symbols?: string[];
|
|
9
10
|
symbolType?: SymbolType;
|
|
10
11
|
};
|
|
11
12
|
export declare type Issues = {
|
|
12
13
|
files: UnusedFileIssues;
|
|
14
|
+
dependencies: UnusedDependencyIssues;
|
|
15
|
+
devDependencies: UnusedDependencyIssues;
|
|
16
|
+
unresolved: UnresolvedDependencyIssues;
|
|
13
17
|
exports: UnusedExportIssues;
|
|
14
18
|
types: UnusedExportIssues;
|
|
15
19
|
nsExports: UnusedExportIssues;
|
|
@@ -17,19 +21,29 @@ export declare type Issues = {
|
|
|
17
21
|
duplicates: UnusedExportIssues;
|
|
18
22
|
};
|
|
19
23
|
export declare type IssueType = keyof Issues;
|
|
20
|
-
declare type
|
|
24
|
+
export declare type ProjectIssueType = Extract<IssueType, 'files' | 'dependencies' | 'devDependencies'>;
|
|
25
|
+
export declare type SymbolIssueType = Exclude<IssueType, ProjectIssueType>;
|
|
26
|
+
export declare type IssueGroup = 'files' | 'dependencies' | 'unlisted' | 'exports' | 'nsExports' | 'types' | 'nsTypes' | 'duplicates';
|
|
27
|
+
export declare type LocalConfiguration = {
|
|
28
|
+
dev?: boolean;
|
|
21
29
|
entryFiles: string[];
|
|
22
30
|
projectFiles: string[];
|
|
31
|
+
include?: string[];
|
|
32
|
+
exclude?: string[];
|
|
23
33
|
};
|
|
34
|
+
export declare type ImportedConfiguration = LocalConfiguration | Record<string, LocalConfiguration>;
|
|
24
35
|
export declare type Configuration = LocalConfiguration & {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
[key in
|
|
36
|
+
workingDir: string;
|
|
37
|
+
report: {
|
|
38
|
+
[key in IssueGroup]: boolean;
|
|
28
39
|
};
|
|
40
|
+
dependencies: string[];
|
|
41
|
+
devDependencies: string[];
|
|
42
|
+
isDev: boolean;
|
|
43
|
+
tsConfigPaths: string[];
|
|
29
44
|
isShowProgress: boolean;
|
|
30
45
|
jsDocOptions: {
|
|
31
46
|
isReadPublicTag: boolean;
|
|
32
47
|
};
|
|
33
48
|
};
|
|
34
|
-
export declare type ImportedConfiguration = LocalConfiguration | Record<string, LocalConfiguration>;
|
|
35
49
|
export {};
|
package/dist/util/config.d.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import type { ImportedConfiguration } from '../types';
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
4
|
-
entryFiles: string[];
|
|
5
|
-
projectFiles: string[];
|
|
6
|
-
} | undefined;
|
|
7
|
-
export declare const resolveIncludedFromArgs: (onlyArg: string[], excludeArg: string[]) => {
|
|
1
|
+
import type { ImportedConfiguration, LocalConfiguration } from '../types';
|
|
2
|
+
export declare const resolveConfig: (importedConfiguration: ImportedConfiguration, cwdArg?: string) => LocalConfiguration | undefined;
|
|
3
|
+
export declare const resolveIncludedIssueGroups: (includeArg: string[], excludeArg: string[], resolvedConfig?: LocalConfiguration) => {
|
|
8
4
|
files: boolean;
|
|
5
|
+
dependencies: boolean;
|
|
9
6
|
exports: boolean;
|
|
10
7
|
types: boolean;
|
|
11
8
|
nsExports: boolean;
|
|
12
9
|
nsTypes: boolean;
|
|
13
10
|
duplicates: boolean;
|
|
11
|
+
unlisted: boolean;
|
|
14
12
|
};
|
package/dist/util/config.js
CHANGED
|
@@ -3,47 +3,44 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
7
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
6
|
+
exports.resolveIncludedIssueGroups = exports.resolveConfig = void 0;
|
|
8
7
|
const micromatch_1 = __importDefault(require("micromatch"));
|
|
9
|
-
const importConfig = (cwd, configArg) => {
|
|
10
|
-
try {
|
|
11
|
-
const manifest = require(node_path_1.default.join(cwd, 'package.json'));
|
|
12
|
-
if ('knip' in manifest)
|
|
13
|
-
return manifest.knip;
|
|
14
|
-
else
|
|
15
|
-
throw new Error('Unable to find `knip` key in package.json');
|
|
16
|
-
}
|
|
17
|
-
catch (error) {
|
|
18
|
-
try {
|
|
19
|
-
return require(node_path_1.default.resolve(configArg));
|
|
20
|
-
}
|
|
21
|
-
catch (error) {
|
|
22
|
-
console.error(`Unable to find configuration at ${node_path_1.default.join(cwd, configArg)}\n`);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
exports.importConfig = importConfig;
|
|
27
8
|
const resolveConfig = (importedConfiguration, cwdArg) => {
|
|
9
|
+
const configKeys = Object.keys(importedConfiguration);
|
|
28
10
|
if (cwdArg && !('projectFiles' in importedConfiguration)) {
|
|
29
|
-
const importedConfigKey =
|
|
11
|
+
const importedConfigKey = configKeys.find(pattern => micromatch_1.default.isMatch(cwdArg.replace(/\/$/, ''), pattern));
|
|
30
12
|
if (importedConfigKey) {
|
|
31
13
|
return importedConfiguration[importedConfigKey];
|
|
32
14
|
}
|
|
33
15
|
}
|
|
34
16
|
if (!cwdArg && (!importedConfiguration.entryFiles || !importedConfiguration.projectFiles)) {
|
|
35
17
|
console.error('Unable to find `entryFiles` and/or `projectFiles` in configuration.');
|
|
36
|
-
console.info(
|
|
18
|
+
console.info(`Add it at root level, or use --cwd and match one of: ${configKeys.join(', ')}\n`);
|
|
37
19
|
return;
|
|
38
20
|
}
|
|
39
21
|
return importedConfiguration;
|
|
40
22
|
};
|
|
41
23
|
exports.resolveConfig = resolveConfig;
|
|
42
|
-
const
|
|
43
|
-
const groups = [
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
24
|
+
const resolveIncludedIssueGroups = (includeArg, excludeArg, resolvedConfig) => {
|
|
25
|
+
const groups = [
|
|
26
|
+
'files',
|
|
27
|
+
'dependencies',
|
|
28
|
+
'unlisted',
|
|
29
|
+
'exports',
|
|
30
|
+
'types',
|
|
31
|
+
'nsExports',
|
|
32
|
+
'nsTypes',
|
|
33
|
+
'duplicates',
|
|
34
|
+
];
|
|
35
|
+
const include = [includeArg, resolvedConfig?.include ?? []]
|
|
36
|
+
.flat()
|
|
37
|
+
.map(value => value.split(','))
|
|
38
|
+
.flat();
|
|
39
|
+
const exclude = [excludeArg, resolvedConfig?.exclude ?? []]
|
|
40
|
+
.flat()
|
|
41
|
+
.map(value => value.split(','))
|
|
42
|
+
.flat();
|
|
43
|
+
const includes = (include.length > 0 ? include : groups).filter((group) => !exclude.includes(group));
|
|
47
44
|
return groups.reduce((r, group) => ((r[group] = includes.includes(group)), r), {});
|
|
48
45
|
};
|
|
49
|
-
exports.
|
|
46
|
+
exports.resolveIncludedIssueGroups = resolveIncludedIssueGroups;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SourceFile } from 'ts-morph';
|
|
2
|
+
import type { Configuration, Issue } from '../types';
|
|
3
|
+
export declare const getDependencyAnalyzer: (configuration: Configuration) => {
|
|
4
|
+
getUnresolvedDependencies: (sourceFile: SourceFile) => Set<Issue>;
|
|
5
|
+
getUnusedDependencies: () => string[];
|
|
6
|
+
getUnusedDevDependencies: () => string[];
|
|
7
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getDependencyAnalyzer = void 0;
|
|
7
|
+
const ts_morph_1 = require("ts-morph");
|
|
8
|
+
const is_builtin_module_1 = __importDefault(require("is-builtin-module"));
|
|
9
|
+
const micromatch_1 = __importDefault(require("micromatch"));
|
|
10
|
+
const compact = (collection) => Array.from(new Set(collection)).filter((value) => Boolean(value));
|
|
11
|
+
const getDependencyAnalyzer = (configuration) => {
|
|
12
|
+
const { dependencies, devDependencies, tsConfigPaths } = configuration;
|
|
13
|
+
const referencedDependencies = new Set();
|
|
14
|
+
const getUnresolvedDependencies = (sourceFile) => {
|
|
15
|
+
const unresolvedDependencies = new Set();
|
|
16
|
+
const importLiterals = sourceFile.getImportStringLiterals();
|
|
17
|
+
const requires = sourceFile
|
|
18
|
+
.getDescendantsOfKind(ts_morph_1.ts.SyntaxKind.CallExpression)
|
|
19
|
+
.filter(callExpression => callExpression.getExpression().getText() === 'require')
|
|
20
|
+
.map(expression => expression.getFirstDescendantByKind(ts_morph_1.ts.SyntaxKind.StringLiteral));
|
|
21
|
+
const literals = compact([importLiterals, requires].flat());
|
|
22
|
+
literals.forEach(importLiteral => {
|
|
23
|
+
const moduleSpecifier = importLiteral.getLiteralText();
|
|
24
|
+
if (moduleSpecifier.startsWith('.'))
|
|
25
|
+
return;
|
|
26
|
+
if ((0, is_builtin_module_1.default)(moduleSpecifier))
|
|
27
|
+
return;
|
|
28
|
+
if (tsConfigPaths.length > 0 && micromatch_1.default.isMatch(moduleSpecifier, tsConfigPaths))
|
|
29
|
+
return;
|
|
30
|
+
const parts = moduleSpecifier.split('/').slice(0, 2);
|
|
31
|
+
const packageName = moduleSpecifier.startsWith('@') ? parts.join('/') : parts[0];
|
|
32
|
+
if (!dependencies.includes(packageName) && !devDependencies.includes(packageName)) {
|
|
33
|
+
unresolvedDependencies.add({ filePath: sourceFile.getFilePath(), symbol: moduleSpecifier });
|
|
34
|
+
}
|
|
35
|
+
if (dependencies.includes(packageName) || devDependencies.includes(packageName)) {
|
|
36
|
+
referencedDependencies.add(packageName);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return unresolvedDependencies;
|
|
40
|
+
};
|
|
41
|
+
const getUnusedDependencies = () => dependencies.filter(dependency => !referencedDependencies.has(dependency));
|
|
42
|
+
const getUnusedDevDependencies = () => devDependencies.filter(dependency => !referencedDependencies.has(dependency));
|
|
43
|
+
return { getUnresolvedDependencies, getUnusedDependencies, getUnusedDevDependencies };
|
|
44
|
+
};
|
|
45
|
+
exports.getDependencyAnalyzer = getDependencyAnalyzer;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const findFile: (cwd: string, fileName: string) => Promise<string | undefined>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.findFile = void 0;
|
|
7
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const isFile = async (filePath) => {
|
|
10
|
+
try {
|
|
11
|
+
const stats = await promises_1.default.stat(filePath);
|
|
12
|
+
return stats.isFile();
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const findFile = async (cwd, fileName) => {
|
|
19
|
+
const filePath = node_path_1.default.join(cwd, fileName);
|
|
20
|
+
if (await isFile(filePath)) {
|
|
21
|
+
return filePath;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const parentDir = node_path_1.default.resolve(cwd, '..');
|
|
25
|
+
return parentDir === '/' ? undefined : (0, exports.findFile)(parentDir, fileName);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
exports.findFile = findFile;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Project } from 'ts-morph';
|
|
2
|
-
import type { SourceFile
|
|
2
|
+
import type { SourceFile } from 'ts-morph';
|
|
3
3
|
export declare const createProject: (cwd: string, paths?: string | string[]) => Promise<Project>;
|
|
4
4
|
export declare const partitionSourceFiles: (projectFiles: SourceFile[], productionFiles: SourceFile[]) => SourceFile[][];
|
|
5
|
-
export declare const getType: (declaration: ExportedDeclarations) => "type" | "interface" | "enum" | undefined;
|
|
@@ -3,25 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
7
|
-
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
6
|
+
exports.partitionSourceFiles = exports.createProject = void 0;
|
|
8
7
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
8
|
const ts_morph_1 = require("ts-morph");
|
|
10
|
-
const
|
|
11
|
-
try {
|
|
12
|
-
const stats = await promises_1.default.stat(filePath);
|
|
13
|
-
return stats.isFile();
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
const findFile = async (cwd, fileName) => {
|
|
20
|
-
const filePath = node_path_1.default.join(cwd, fileName);
|
|
21
|
-
if (await isFile(filePath))
|
|
22
|
-
return filePath;
|
|
23
|
-
return findFile(node_path_1.default.resolve(cwd, '..'), fileName);
|
|
24
|
-
};
|
|
9
|
+
const path_1 = require("./path");
|
|
25
10
|
const resolvePaths = (cwd, patterns) => {
|
|
26
11
|
return [patterns].flat().map(pattern => {
|
|
27
12
|
if (pattern.startsWith('!'))
|
|
@@ -30,7 +15,7 @@ const resolvePaths = (cwd, patterns) => {
|
|
|
30
15
|
});
|
|
31
16
|
};
|
|
32
17
|
const createProject = async (cwd, paths) => {
|
|
33
|
-
const tsConfigFilePath = await findFile(cwd, 'tsconfig.json');
|
|
18
|
+
const tsConfigFilePath = await (0, path_1.findFile)(cwd, 'tsconfig.json');
|
|
34
19
|
const workspace = new ts_morph_1.Project({
|
|
35
20
|
tsConfigFilePath,
|
|
36
21
|
skipAddingFilesFromTsConfig: true,
|
|
@@ -56,12 +41,3 @@ const partitionSourceFiles = (projectFiles, productionFiles) => {
|
|
|
56
41
|
return [usedFiles, unusedFiles];
|
|
57
42
|
};
|
|
58
43
|
exports.partitionSourceFiles = partitionSourceFiles;
|
|
59
|
-
const getType = (declaration) => {
|
|
60
|
-
if (declaration.isKind(ts_morph_1.ts.SyntaxKind.TypeAliasDeclaration))
|
|
61
|
-
return 'type';
|
|
62
|
-
if (declaration.isKind(ts_morph_1.ts.SyntaxKind.InterfaceDeclaration))
|
|
63
|
-
return 'interface';
|
|
64
|
-
if (declaration.isKind(ts_morph_1.ts.SyntaxKind.EnumDeclaration))
|
|
65
|
-
return 'enum';
|
|
66
|
-
};
|
|
67
|
-
exports.getType = getType;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getType = void 0;
|
|
4
|
+
const ts_morph_1 = require("ts-morph");
|
|
5
|
+
const getType = (declaration) => {
|
|
6
|
+
if (declaration.isKind(ts_morph_1.ts.SyntaxKind.TypeAliasDeclaration))
|
|
7
|
+
return 'type';
|
|
8
|
+
if (declaration.isKind(ts_morph_1.ts.SyntaxKind.InterfaceDeclaration))
|
|
9
|
+
return 'interface';
|
|
10
|
+
if (declaration.isKind(ts_morph_1.ts.SyntaxKind.EnumDeclaration))
|
|
11
|
+
return 'enum';
|
|
12
|
+
};
|
|
13
|
+
exports.getType = getType;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "knip",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Find unused files and exports in your TypeScript project",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"find",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"license": "ISC",
|
|
40
40
|
"dependencies": {
|
|
41
|
+
"is-builtin-module": "3.2.0",
|
|
41
42
|
"micromatch": "4.0.5",
|
|
42
43
|
"ts-morph": "16.0.0",
|
|
43
44
|
"ts-morph-helpers": "0.5.0"
|