knip 0.4.0 → 0.4.2
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 +59 -26
- package/dist/cli.js +33 -68
- package/dist/index.d.ts +4 -3
- package/dist/index.js +72 -186
- package/dist/reporters/compact.d.ts +4 -3
- package/dist/reporters/compact.js +1 -2
- package/dist/reporters/index.d.ts +6 -4
- package/dist/reporters/symbols.d.ts +4 -3
- package/dist/reporters/symbols.js +1 -2
- package/dist/runner.d.ts +16 -0
- package/dist/runner.js +193 -0
- package/dist/types.d.ts +22 -8
- package/dist/util/config.d.ts +1 -10
- package/dist/util/config.js +1 -2
- package/dist/util/errors.d.ts +2 -0
- package/dist/util/errors.js +6 -0
- package/dist/util/path.d.ts +8 -3
- package/dist/util/path.js +17 -7
- package/dist/util/project.d.ts +5 -3
- package/dist/util/project.js +4 -9
- package/package.json +2 -1
- package/dist/util/ignore.d.ts +0 -2
- package/dist/util/ignore.js +0 -38
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# ✂️ Knip
|
|
2
2
|
|
|
3
|
-
Knip scans your JavaScript and TypeScript projects for **unused files, dependencies and exports
|
|
4
|
-
|
|
3
|
+
Knip scans your JavaScript and TypeScript projects for **unused files, dependencies and exports**: things that can be
|
|
4
|
+
removed! Less code means better performance and less to maintain, important for both UX and DX!
|
|
5
5
|
|
|
6
6
|
For comparison, ESLint finds unused variables inside files in isolation, but this will not be flagged:
|
|
7
7
|
|
|
@@ -22,12 +22,14 @@ This is where Knip comes in:
|
|
|
22
22
|
- [x] Supports JavaScript-only projects using ESM (without a `tsconfig.json`)
|
|
23
23
|
|
|
24
24
|
Knip really shines in larger projects where you have non-production files (such as `/docs`, `/tools` and `/scripts`).
|
|
25
|
-
The `includes` setting in `tsconfig.json` is often too broad, resulting in too many false negatives.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
these separately.
|
|
25
|
+
The `includes` setting in `tsconfig.json` is often too broad, resulting in too many false negatives. To produce good
|
|
26
|
+
results it's essential to configure entry files. A comparison with similar toos answers the question
|
|
27
|
+
[Why yet another unused file/dependency/export finder?](#why-yet-another-unused-filedependencyexport-finder)
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
Knip is a fresh take on keeping your projects clean & tidy!
|
|
30
|
+
|
|
31
|
+
[](https://labs.openai.com/s/xZQACaLepaKya0PRUPtIN5dC)
|
|
32
|
+
<sup>_“An orange cow with scissors, Van Gogh style” - generated with OpenAI_</sup>
|
|
31
33
|
|
|
32
34
|
## Installation
|
|
33
35
|
|
|
@@ -142,7 +144,7 @@ As always, make sure to backup files or use Git before deleting files or making
|
|
|
142
144
|
|
|
143
145
|
The default configuration for Knip is very strict and targets production code. For best results, it is recommended to
|
|
144
146
|
exclude files such as tests from the project files. Here's why: when including tests and other non-production files,
|
|
145
|
-
they may
|
|
147
|
+
they may import production files, which will prevent them from being reported as unused.
|
|
146
148
|
|
|
147
149
|
Excluding non-production files from the `projectFiles` allows Knip to understand what production code can be removed
|
|
148
150
|
(including dependent files!).
|
|
@@ -162,8 +164,7 @@ and add `dev: true` to a file named such as `knip.dev.json`:
|
|
|
162
164
|
}
|
|
163
165
|
```
|
|
164
166
|
|
|
165
|
-
|
|
166
|
-
reported.
|
|
167
|
+
Now use `-c knip.dev.json` to find unused files and exports for the combined set of files as configured in `entryFiles`.
|
|
167
168
|
|
|
168
169
|
An alternative way to store `dev` configuration is in this example `package.json`:
|
|
169
170
|
|
|
@@ -192,7 +193,7 @@ This way, the `--dev` flag will use the `dev` options (and also add `devDependen
|
|
|
192
193
|
|
|
193
194
|
#### Separate packages
|
|
194
195
|
|
|
195
|
-
In repos with multiple (published) packages, the `--
|
|
196
|
+
In repos with multiple (published) packages, the `--dir` option comes in handy. With similar package structures, the
|
|
196
197
|
packages can be configured using globs:
|
|
197
198
|
|
|
198
199
|
```json
|
|
@@ -209,8 +210,8 @@ Packages can also be explicitly configured per package directory.
|
|
|
209
210
|
To scan the packages separately, using the first match from the configuration file:
|
|
210
211
|
|
|
211
212
|
```
|
|
212
|
-
knip --
|
|
213
|
-
knip --
|
|
213
|
+
knip --dir packages/client
|
|
214
|
+
knip --dir packages/services
|
|
214
215
|
```
|
|
215
216
|
|
|
216
217
|
#### Connected projects
|
|
@@ -296,16 +297,48 @@ src/components/Registration.tsx: Registration, default
|
|
|
296
297
|
src/components/Products.tsx: ProductsList, default
|
|
297
298
|
```
|
|
298
299
|
|
|
299
|
-
## Why Yet Another unused file/export finder?
|
|
300
|
-
|
|
301
|
-
There are some
|
|
302
|
-
|
|
303
|
-
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
300
|
+
## Why Yet Another unused file/dependency/export finder?
|
|
301
|
+
|
|
302
|
+
There are already some great packages available. Getting good results when finding unused files, dependencies and
|
|
303
|
+
exports is not trivial. Repositories don't seem to get any smaller and with the rise of monorepos even more so. Tools
|
|
304
|
+
like this need to analyze potentially many and/or large files, which is memory and time-consuming. Although I normally
|
|
305
|
+
try to stick to the Unix philosophy, here I believe it's efficient to merge these issue reports into a single tool. When
|
|
306
|
+
building a dependency graph of the project, an abstract syntax tree for each file, and traversing all of this, why not
|
|
307
|
+
collect the various issues in one go?
|
|
308
|
+
|
|
309
|
+
## Comparison
|
|
310
|
+
|
|
311
|
+
This table is a work in progress, but here's a first impression. Based on their docs (please report any mistakes):
|
|
312
|
+
|
|
313
|
+
| Feature | **knip** | [depcheck][1] | [unimported][2] | [ts-unused-exports][3] | [ts-prune][4] | [find-unused-exports][5] |
|
|
314
|
+
| --------------------------- | :--------: | :-----------: | :-------------: | :--------------------: | :-----------: | :----------------------: |
|
|
315
|
+
| Unused files | ✅ | - | ✅ | - | - | - |
|
|
316
|
+
| Unused dependencies | ✅ | ✅ | ✅ | - | - | - |
|
|
317
|
+
| Unlisted dependencies | ✅ | ✅ | ✅ | - | - | - |
|
|
318
|
+
| Custom dependency resolvers | ❌ [#7][6] | ✅ | ❌ (1) | - | - | - |
|
|
319
|
+
| Unused exports | ✅ | - | - | ✅ | ✅ | ✅ |
|
|
320
|
+
| Duplicate exports | ✅ | - | - | ❌ | ❌ | ❌ |
|
|
321
|
+
| Search namespaces | ✅ | - | - | ✅ | ❌ | ❌ |
|
|
322
|
+
| Custom reporters | ✅ | - | - | - | - | - |
|
|
323
|
+
| Pure JavaScript/ESM | ✅ | ✅ | ✅ | - | - | ✅ |
|
|
324
|
+
| Configure entry files | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
325
|
+
| Support monorepo | 🟠 (2) | - | - | - | - | - |
|
|
326
|
+
| ESLint plugin available | - | - | - | ✅ | - | - |
|
|
327
|
+
|
|
328
|
+
✅ = Supported, ❌ = Not supported, - = Out of scope
|
|
329
|
+
|
|
330
|
+
1. unimported is strict and works based on production files and `dependencies`, so does not have custom dependency
|
|
331
|
+
resolvers which are usually only needed for `devDependencies`.
|
|
332
|
+
2. knip wants to [support monorepos](#monorepos) properly, the first steps in this direction are implemented.
|
|
333
|
+
|
|
334
|
+
## Knip?!
|
|
335
|
+
|
|
336
|
+
Knip is Dutch for a "cut". A Dutch expression is "to be ge**knip**t for something", which means to be perfectly suited
|
|
337
|
+
for the job. I'm motivated to make knip perfectly suited for the job of cutting projects to perfection! ✂️
|
|
338
|
+
|
|
339
|
+
[1]: https://github.com/depcheck/depcheck
|
|
340
|
+
[2]: https://github.com/smeijer/unimported
|
|
341
|
+
[3]: https://github.com/pzavolinsky/ts-unused-exports
|
|
342
|
+
[4]: https://github.com/nadeesha/ts-prune
|
|
343
|
+
[5]: https://github.com/jaydenseric/find-unused-exports
|
|
344
|
+
[6]: https://github.com/webpro/knip/issues/7
|
package/dist/cli.js
CHANGED
|
@@ -6,14 +6,11 @@ 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
|
|
9
|
+
const _1 = require(".");
|
|
10
10
|
const help_1 = require("./help");
|
|
11
|
-
const config_1 = require("./util/config");
|
|
12
|
-
const fs_1 = require("./util/fs");
|
|
13
|
-
const ignore_1 = require("./util/ignore");
|
|
14
11
|
const reporters_1 = __importDefault(require("./reporters"));
|
|
15
|
-
const
|
|
16
|
-
const { values: { help, dir, config: configFilePath = 'knip.json', tsConfig: tsConfigFilePath, include = [], exclude = [],
|
|
12
|
+
const errors_1 = require("./util/errors");
|
|
13
|
+
const { values: { help, dir, config: configFilePath = 'knip.json', tsConfig: tsConfigFilePath, include = [], exclude = [], ignore = [], 'no-gitignore': isNoGitIgnore = false, dev: isDev = false, 'no-progress': noProgress = false, reporter = 'symbols', 'max-issues': maxIssues = '0', jsdoc: jsDoc = [], }, } = (0, node_util_1.parseArgs)({
|
|
17
14
|
options: {
|
|
18
15
|
help: { type: 'boolean' },
|
|
19
16
|
config: { type: 'string', short: 'c' },
|
|
@@ -21,11 +18,11 @@ const { values: { help, dir, config: configFilePath = 'knip.json', tsConfig: tsC
|
|
|
21
18
|
dir: { type: 'string' },
|
|
22
19
|
include: { type: 'string', multiple: true },
|
|
23
20
|
exclude: { type: 'string', multiple: true },
|
|
24
|
-
dev: { type: 'boolean' },
|
|
25
|
-
'max-issues': { type: 'string' },
|
|
26
21
|
ignore: { type: 'string', multiple: true },
|
|
27
22
|
'no-gitignore': { type: 'boolean' },
|
|
23
|
+
dev: { type: 'boolean' },
|
|
28
24
|
'no-progress': { type: 'boolean' },
|
|
25
|
+
'max-issues': { type: 'string' },
|
|
29
26
|
reporter: { type: 'string' },
|
|
30
27
|
jsdoc: { type: 'string', multiple: true },
|
|
31
28
|
},
|
|
@@ -38,69 +35,37 @@ const cwd = process.cwd();
|
|
|
38
35
|
const workingDir = dir ? node_path_1.default.resolve(dir) : cwd;
|
|
39
36
|
const isShowProgress = noProgress === false ? process.stdout.isTTY && typeof process.stdout.cursorTo === 'function' : !noProgress;
|
|
40
37
|
const printReport = reporter in reporters_1.default ? reporters_1.default[reporter] : require(node_path_1.default.join(workingDir, reporter));
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
38
|
+
const run = async () => {
|
|
39
|
+
try {
|
|
40
|
+
const { report, issues, counters } = await (0, _1.main)({
|
|
41
|
+
cwd,
|
|
42
|
+
workingDir,
|
|
43
|
+
configFilePath,
|
|
44
|
+
tsConfigFilePath,
|
|
45
|
+
include,
|
|
46
|
+
exclude,
|
|
47
|
+
ignore,
|
|
48
|
+
gitignore: !isNoGitIgnore,
|
|
49
|
+
isDev,
|
|
50
|
+
isShowProgress,
|
|
51
|
+
jsDoc,
|
|
52
|
+
});
|
|
53
|
+
printReport({ report, issues, workingDir, isDev });
|
|
54
|
+
const reportGroup = report.files ? 'files' : Object.keys(report).find(key => report[key]);
|
|
55
|
+
const counterGroup = reportGroup === 'unlisted' ? 'unresolved' : reportGroup;
|
|
56
|
+
if (counterGroup) {
|
|
57
|
+
const count = counters[counterGroup];
|
|
58
|
+
if (count > Number(maxIssues))
|
|
59
|
+
process.exit(count);
|
|
60
|
+
}
|
|
64
61
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
? Object.keys(tsConfig.config.compilerOptions.paths).map(p => p.replace(/\*/g, '**'))
|
|
69
|
-
: [];
|
|
70
|
-
if (tsConfig.error) {
|
|
71
|
-
console.error(`An error occured when reading ${node_path_1.default.relative(cwd, tsConfigPath)}.\n`);
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (error instanceof errors_1.ConfigurationError) {
|
|
64
|
+
console.error(error.message + '\n');
|
|
72
65
|
(0, help_1.printHelp)();
|
|
73
66
|
process.exit(1);
|
|
74
67
|
}
|
|
75
|
-
|
|
76
|
-
const ignorePatterns = ignore.map(ignore_1.convertPattern);
|
|
77
|
-
if (!isNoGitIgnore) {
|
|
78
|
-
const patterns = await (0, ignore_1.readIgnorePatterns)(cwd, workingDir);
|
|
79
|
-
patterns.forEach(pattern => ignorePatterns.push(pattern));
|
|
80
|
-
}
|
|
81
|
-
const config = {
|
|
82
|
-
workingDir,
|
|
83
|
-
report,
|
|
84
|
-
dependencies: Object.keys(manifest.dependencies ?? {}),
|
|
85
|
-
devDependencies: Object.keys(manifest.devDependencies ?? {}),
|
|
86
|
-
isDev: typeof resolvedConfig.dev === 'boolean' ? resolvedConfig.dev : isDev,
|
|
87
|
-
tsConfigFilePath,
|
|
88
|
-
tsConfigPaths,
|
|
89
|
-
ignorePatterns,
|
|
90
|
-
isShowProgress,
|
|
91
|
-
jsDocOptions: {
|
|
92
|
-
isReadPublicTag: jsdoc.includes('public'),
|
|
93
|
-
},
|
|
94
|
-
...resolvedConfig,
|
|
95
|
-
};
|
|
96
|
-
const { issues, counters } = await (0, _1.run)(config);
|
|
97
|
-
printReport({ issues, workingDir, config });
|
|
98
|
-
const reportGroup = report.files ? 'files' : Object.keys(report).find(key => report[key]);
|
|
99
|
-
const counterGroup = reportGroup === 'unlisted' ? 'unresolved' : reportGroup;
|
|
100
|
-
if (counterGroup) {
|
|
101
|
-
const count = counters[counterGroup];
|
|
102
|
-
if (count > Number(maxIssues))
|
|
103
|
-
process.exit(count);
|
|
68
|
+
throw error;
|
|
104
69
|
}
|
|
105
70
|
};
|
|
106
|
-
|
|
71
|
+
run();
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare
|
|
3
|
-
|
|
1
|
+
import type { UnresolvedConfiguration } from './types';
|
|
2
|
+
export declare const main: (options: UnresolvedConfiguration) => Promise<{
|
|
3
|
+
report: import("./types").Report;
|
|
4
|
+
issues: import("./types").Issues;
|
|
4
5
|
counters: {
|
|
5
6
|
files: number;
|
|
6
7
|
dependencies: number;
|
package/dist/index.js
CHANGED
|
@@ -3,196 +3,82 @@ 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.
|
|
6
|
+
exports.main = void 0;
|
|
7
7
|
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
-
const
|
|
9
|
-
const
|
|
8
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
9
|
+
const config_1 = require("./util/config");
|
|
10
|
+
const fs_1 = require("./util/fs");
|
|
11
|
+
const path_1 = require("./util/path");
|
|
10
12
|
const project_1 = require("./util/project");
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
13
|
+
const runner_1 = require("./runner");
|
|
14
|
+
const errors_1 = require("./util/errors");
|
|
15
|
+
const main = async (options) => {
|
|
16
|
+
const { cwd, workingDir, configFilePath = 'knip.json', tsConfigFilePath, include, exclude, ignore, gitignore, isDev, isShowProgress, jsDoc, } = options;
|
|
17
|
+
const localConfigurationPath = configFilePath && (await (0, fs_1.findFile)(workingDir, configFilePath));
|
|
18
|
+
const manifestPath = await (0, fs_1.findFile)(workingDir, 'package.json');
|
|
19
|
+
const localConfiguration = localConfigurationPath && require(localConfigurationPath);
|
|
20
|
+
const manifest = manifestPath && require(manifestPath);
|
|
21
|
+
if (!localConfigurationPath && !manifest.knip) {
|
|
22
|
+
const location = workingDir === cwd ? 'current directory' : `${node_path_1.default.relative(cwd, workingDir)} or up.`;
|
|
23
|
+
throw new errors_1.ConfigurationError(`Unable to find ${configFilePath} or package.json#knip in ${location}`);
|
|
24
|
+
}
|
|
25
|
+
const dir = node_path_1.default.relative(cwd, workingDir);
|
|
26
|
+
const resolvedConfig = (0, config_1.resolveConfig)(manifest.knip ?? localConfiguration, { workingDir: dir, isDev });
|
|
27
|
+
if (!resolvedConfig) {
|
|
28
|
+
throw new errors_1.ConfigurationError('Unable to find `entryFiles` and/or `projectFiles` in configuration.');
|
|
29
|
+
}
|
|
30
|
+
const report = (0, config_1.resolveIncludedIssueGroups)(include, exclude, resolvedConfig);
|
|
31
|
+
let tsConfigPaths = [];
|
|
32
|
+
const tsConfigPath = await (0, fs_1.findFile)(workingDir, tsConfigFilePath ?? 'tsconfig.json');
|
|
33
|
+
if (tsConfigFilePath && !tsConfigPath) {
|
|
34
|
+
throw new errors_1.ConfigurationError(`Unable to find ${tsConfigFilePath}`);
|
|
35
|
+
}
|
|
36
|
+
if (tsConfigPath) {
|
|
37
|
+
const tsConfig = typescript_1.default.readConfigFile(tsConfigPath, typescript_1.default.sys.readFile);
|
|
38
|
+
tsConfigPaths = tsConfig.config.compilerOptions?.paths
|
|
39
|
+
? Object.keys(tsConfig.config.compilerOptions.paths).map(p => p.replace(/\*/g, '**'))
|
|
40
|
+
: [];
|
|
41
|
+
if (tsConfig.error) {
|
|
42
|
+
throw new errors_1.ConfigurationError(`An error occured when reading ${node_path_1.default.relative(cwd, tsConfigPath)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const projectOptions = tsConfigPath ? { tsConfigFilePath: tsConfigPath } : { compilerOptions: { allowJs: true } };
|
|
46
|
+
const entryPaths = await (0, path_1.resolvePaths)({
|
|
47
|
+
cwd,
|
|
48
|
+
workingDir,
|
|
49
|
+
patterns: resolvedConfig.entryFiles,
|
|
50
|
+
ignore,
|
|
51
|
+
gitignore,
|
|
52
|
+
});
|
|
53
|
+
const production = (0, project_1.createProject)({ projectOptions, paths: entryPaths });
|
|
19
54
|
const entryFiles = production.getSourceFiles();
|
|
20
55
|
production.resolveSourceFileDependencies();
|
|
21
56
|
const productionFiles = production.getSourceFiles();
|
|
22
|
-
const
|
|
57
|
+
const projectPaths = await (0, path_1.resolvePaths)({
|
|
58
|
+
cwd,
|
|
59
|
+
workingDir,
|
|
60
|
+
patterns: resolvedConfig.projectFiles,
|
|
61
|
+
ignore,
|
|
62
|
+
gitignore,
|
|
63
|
+
});
|
|
64
|
+
const project = (0, project_1.createProject)({ projectOptions, paths: projectPaths });
|
|
23
65
|
const projectFiles = project.getSourceFiles();
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
files: issues.files.size,
|
|
39
|
-
dependencies: issues.dependencies.size,
|
|
40
|
-
devDependencies: issues.dependencies.size,
|
|
41
|
-
unresolved: 0,
|
|
42
|
-
exports: 0,
|
|
43
|
-
types: 0,
|
|
44
|
-
nsExports: 0,
|
|
45
|
-
nsTypes: 0,
|
|
46
|
-
duplicates: 0,
|
|
47
|
-
processed: issues.files.size,
|
|
48
|
-
};
|
|
49
|
-
const updateProcessingOutput = (item) => {
|
|
50
|
-
if (!isShowProgress)
|
|
51
|
-
return;
|
|
52
|
-
const counter = unreferencedProductionFiles.length + counters.processed;
|
|
53
|
-
const total = unreferencedProductionFiles.length + usedNonEntryFiles.length;
|
|
54
|
-
const percentage = Math.floor((counter / total) * 100);
|
|
55
|
-
const messages = [(0, log_1.getLine)(`${percentage}%`, `of files processed (${counter} of ${total})`)];
|
|
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'));
|
|
63
|
-
if (counter < total) {
|
|
64
|
-
messages.push('');
|
|
65
|
-
messages.push(`Processing: ${node_path_1.default.relative(workingDir, item.filePath)}`);
|
|
66
|
-
}
|
|
67
|
-
lineRewriter.update(messages);
|
|
66
|
+
const config = {
|
|
67
|
+
workingDir,
|
|
68
|
+
report,
|
|
69
|
+
entryFiles,
|
|
70
|
+
productionFiles,
|
|
71
|
+
projectFiles,
|
|
72
|
+
dependencies: Object.keys(manifest.dependencies ?? {}),
|
|
73
|
+
devDependencies: Object.keys(manifest.devDependencies ?? {}),
|
|
74
|
+
isDev: typeof resolvedConfig.dev === 'boolean' ? resolvedConfig.dev : isDev,
|
|
75
|
+
tsConfigPaths,
|
|
76
|
+
isShowProgress,
|
|
77
|
+
jsDocOptions: {
|
|
78
|
+
isReadPublicTag: jsDoc.includes('public'),
|
|
79
|
+
},
|
|
68
80
|
};
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
issues[issueType][key][symbol] = issue;
|
|
74
|
-
counters[issueType]++;
|
|
75
|
-
updateProcessingOutput(issue);
|
|
76
|
-
};
|
|
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) {
|
|
97
|
-
usedNonEntryFiles.forEach(sourceFile => {
|
|
98
|
-
const filePath = sourceFile.getFilePath();
|
|
99
|
-
if (report.dependencies || report.unlisted) {
|
|
100
|
-
const unresolvedDependencies = getUnresolvedDependencies(sourceFile);
|
|
101
|
-
unresolvedDependencies.forEach(issue => addSymbolIssue('unresolved', issue));
|
|
102
|
-
}
|
|
103
|
-
const exportDeclarations = sourceFile.getExportedDeclarations();
|
|
104
|
-
if (report.duplicates) {
|
|
105
|
-
const duplicateExports = (0, ts_morph_helpers_1.findDuplicateExportedNames)(sourceFile);
|
|
106
|
-
duplicateExports.forEach(symbols => {
|
|
107
|
-
const symbol = symbols.join('|');
|
|
108
|
-
addSymbolIssue('duplicates', { filePath, symbol, symbols });
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
if (report.exports || report.types || report.nsExports || report.nsTypes) {
|
|
112
|
-
const uniqueExportedSymbols = new Set([...exportDeclarations.values()].flat());
|
|
113
|
-
if (uniqueExportedSymbols.size === 1)
|
|
114
|
-
return;
|
|
115
|
-
exportDeclarations.forEach(declarations => {
|
|
116
|
-
declarations.forEach(declaration => {
|
|
117
|
-
const type = (0, type_1.getType)(declaration);
|
|
118
|
-
if (!report.nsExports && !report.nsTypes) {
|
|
119
|
-
if (!report.types && type)
|
|
120
|
-
return;
|
|
121
|
-
if (!report.exports && !type)
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
if (jsDocOptions.isReadPublicTag && ts_morph_1.ts.getJSDocPublicTag(declaration.compilerNode))
|
|
125
|
-
return;
|
|
126
|
-
let identifier;
|
|
127
|
-
if (declaration.isKind(ts_morph_1.ts.SyntaxKind.Identifier)) {
|
|
128
|
-
identifier = declaration;
|
|
129
|
-
}
|
|
130
|
-
else if (declaration.isKind(ts_morph_1.ts.SyntaxKind.ArrowFunction)) {
|
|
131
|
-
}
|
|
132
|
-
else if (declaration.isKind(ts_morph_1.ts.SyntaxKind.FunctionDeclaration) ||
|
|
133
|
-
declaration.isKind(ts_morph_1.ts.SyntaxKind.ClassDeclaration) ||
|
|
134
|
-
declaration.isKind(ts_morph_1.ts.SyntaxKind.TypeAliasDeclaration) ||
|
|
135
|
-
declaration.isKind(ts_morph_1.ts.SyntaxKind.InterfaceDeclaration) ||
|
|
136
|
-
declaration.isKind(ts_morph_1.ts.SyntaxKind.EnumDeclaration)) {
|
|
137
|
-
identifier = declaration.getFirstChildByKindOrThrow(ts_morph_1.ts.SyntaxKind.Identifier);
|
|
138
|
-
}
|
|
139
|
-
else if (declaration.isKind(ts_morph_1.ts.SyntaxKind.PropertyAccessExpression)) {
|
|
140
|
-
identifier = declaration.getLastChildByKindOrThrow(ts_morph_1.ts.SyntaxKind.Identifier);
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
identifier = declaration.getFirstDescendantByKind(ts_morph_1.ts.SyntaxKind.Identifier);
|
|
144
|
-
}
|
|
145
|
-
if (identifier) {
|
|
146
|
-
const identifierText = identifier.getText();
|
|
147
|
-
if (report.exports && issues.exports[filePath]?.[identifierText])
|
|
148
|
-
return;
|
|
149
|
-
if (report.types && issues.types[filePath]?.[identifierText])
|
|
150
|
-
return;
|
|
151
|
-
if (report.nsExports && issues.nsExports[filePath]?.[identifierText])
|
|
152
|
-
return;
|
|
153
|
-
if (report.nsTypes && issues.nsTypes[filePath]?.[identifierText])
|
|
154
|
-
return;
|
|
155
|
-
const refs = identifier.findReferences();
|
|
156
|
-
if (refs.length === 0) {
|
|
157
|
-
addSymbolIssue('exports', { filePath, symbol: identifierText });
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
const refFiles = new Set(refs.map(r => r.compilerObject.references.map(r => r.fileName)).flat());
|
|
161
|
-
const isReferencedOnlyBySelf = refFiles.size === 1 && [...refFiles][0] === filePath;
|
|
162
|
-
if (!isReferencedOnlyBySelf)
|
|
163
|
-
return;
|
|
164
|
-
if ((0, ts_morph_helpers_1.findReferencingNamespaceNodes)(sourceFile).length > 0) {
|
|
165
|
-
if (type) {
|
|
166
|
-
addSymbolIssue('nsTypes', { filePath, symbol: identifierText, symbolType: type });
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
addSymbolIssue('nsExports', { filePath, symbol: identifierText });
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
else if (type) {
|
|
173
|
-
addSymbolIssue('types', { filePath, symbol: identifierText, symbolType: type });
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
addSymbolIssue('exports', { filePath, symbol: identifierText });
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
counters.processed++;
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
if (report.dependencies) {
|
|
187
|
-
const unusedDependencies = getUnusedDependencies();
|
|
188
|
-
unusedDependencies.forEach(symbol => addProjectIssue('dependencies', { filePath: '', symbol }));
|
|
189
|
-
if (isDev) {
|
|
190
|
-
const unusedDevDependencies = getUnusedDevDependencies();
|
|
191
|
-
unusedDevDependencies.forEach(symbol => addProjectIssue('devDependencies', { filePath: '', symbol }));
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
if (isShowProgress)
|
|
195
|
-
lineRewriter.resetLines();
|
|
196
|
-
return { issues, counters };
|
|
197
|
-
}
|
|
198
|
-
exports.run = run;
|
|
81
|
+
const { issues, counters } = await (0, runner_1.findIssues)(config);
|
|
82
|
+
return { report, issues, counters };
|
|
83
|
+
};
|
|
84
|
+
exports.main = main;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { Issues,
|
|
2
|
-
declare const _default: ({
|
|
1
|
+
import type { Issues, Report } from '../types';
|
|
2
|
+
declare const _default: ({ report, issues, workingDir, isDev, }: {
|
|
3
|
+
report: Report;
|
|
3
4
|
issues: Issues;
|
|
4
|
-
config: Configuration;
|
|
5
5
|
workingDir: string;
|
|
6
|
+
isDev: boolean;
|
|
6
7
|
}) => void;
|
|
7
8
|
export default _default;
|
|
@@ -26,8 +26,7 @@ const logIssueGroupResults = (issues, workingDir, title) => {
|
|
|
26
26
|
console.log('Not found');
|
|
27
27
|
}
|
|
28
28
|
};
|
|
29
|
-
exports.default = ({
|
|
30
|
-
const { report, isDev } = config;
|
|
29
|
+
exports.default = ({ report, issues, workingDir, isDev, }) => {
|
|
31
30
|
const reportMultipleGroups = Object.values(report).filter(Boolean).length > 1;
|
|
32
31
|
if (report.files) {
|
|
33
32
|
const unreferencedFiles = Array.from(issues.files);
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
declare const _default: {
|
|
2
|
-
symbols: ({
|
|
2
|
+
symbols: ({ report, issues, workingDir, isDev, }: {
|
|
3
|
+
report: import("../types").Report;
|
|
3
4
|
issues: import("../types").Issues;
|
|
4
|
-
config: import("../types").Configuration;
|
|
5
5
|
workingDir: string;
|
|
6
|
+
isDev: boolean;
|
|
6
7
|
}) => void;
|
|
7
|
-
compact: ({
|
|
8
|
+
compact: ({ report, issues, workingDir, isDev, }: {
|
|
9
|
+
report: import("../types").Report;
|
|
8
10
|
issues: import("../types").Issues;
|
|
9
|
-
config: import("../types").Configuration;
|
|
10
11
|
workingDir: string;
|
|
12
|
+
isDev: boolean;
|
|
11
13
|
}) => void;
|
|
12
14
|
};
|
|
13
15
|
export default _default;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { Issues,
|
|
2
|
-
declare const _default: ({
|
|
1
|
+
import type { Issues, Report } from '../types';
|
|
2
|
+
declare const _default: ({ report, issues, workingDir, isDev, }: {
|
|
3
|
+
report: Report;
|
|
3
4
|
issues: Issues;
|
|
4
|
-
config: Configuration;
|
|
5
5
|
workingDir: string;
|
|
6
|
+
isDev: boolean;
|
|
6
7
|
}) => void;
|
|
7
8
|
export default _default;
|
|
@@ -28,8 +28,7 @@ const logIssueGroupResults = (issues, workingDir, title) => {
|
|
|
28
28
|
console.log('Not found');
|
|
29
29
|
}
|
|
30
30
|
};
|
|
31
|
-
exports.default = ({
|
|
32
|
-
const { report, isDev } = config;
|
|
31
|
+
exports.default = ({ report, issues, workingDir, isDev, }) => {
|
|
33
32
|
const reportMultipleGroups = Object.values(report).filter(Boolean).length > 1;
|
|
34
33
|
if (report.files) {
|
|
35
34
|
const unreferencedFiles = Array.from(issues.files);
|
package/dist/runner.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Configuration, Issues } from './types';
|
|
2
|
+
export declare function findIssues(configuration: Configuration): Promise<{
|
|
3
|
+
issues: Issues;
|
|
4
|
+
counters: {
|
|
5
|
+
files: number;
|
|
6
|
+
dependencies: number;
|
|
7
|
+
devDependencies: number;
|
|
8
|
+
unresolved: number;
|
|
9
|
+
exports: number;
|
|
10
|
+
types: number;
|
|
11
|
+
nsExports: number;
|
|
12
|
+
nsTypes: number;
|
|
13
|
+
duplicates: number;
|
|
14
|
+
processed: number;
|
|
15
|
+
};
|
|
16
|
+
}>;
|
package/dist/runner.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
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.findIssues = void 0;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const ts_morph_1 = require("ts-morph");
|
|
9
|
+
const ts_morph_helpers_1 = require("ts-morph-helpers");
|
|
10
|
+
const project_1 = require("./util/project");
|
|
11
|
+
const type_1 = require("./util/type");
|
|
12
|
+
const dependencies_1 = require("./util/dependencies");
|
|
13
|
+
const log_1 = require("./log");
|
|
14
|
+
const lineRewriter = new log_1.LineRewriter();
|
|
15
|
+
async function findIssues(configuration) {
|
|
16
|
+
const { workingDir, isShowProgress, report, isDev, jsDocOptions } = configuration;
|
|
17
|
+
const { entryFiles, productionFiles, projectFiles } = configuration;
|
|
18
|
+
const { getUnresolvedDependencies, getUnusedDependencies, getUnusedDevDependencies } = (0, dependencies_1.getDependencyAnalyzer)(configuration);
|
|
19
|
+
const [usedProductionFiles, unreferencedProductionFiles] = (0, project_1.partitionSourceFiles)(projectFiles, productionFiles);
|
|
20
|
+
const [usedEntryFiles, usedNonEntryFiles] = (0, project_1.partitionSourceFiles)(usedProductionFiles, entryFiles);
|
|
21
|
+
const issues = {
|
|
22
|
+
files: new Set(unreferencedProductionFiles.map(file => file.getFilePath())),
|
|
23
|
+
dependencies: new Set(),
|
|
24
|
+
devDependencies: new Set(),
|
|
25
|
+
unresolved: {},
|
|
26
|
+
exports: {},
|
|
27
|
+
types: {},
|
|
28
|
+
nsExports: {},
|
|
29
|
+
nsTypes: {},
|
|
30
|
+
duplicates: {},
|
|
31
|
+
};
|
|
32
|
+
const counters = {
|
|
33
|
+
files: issues.files.size,
|
|
34
|
+
dependencies: issues.dependencies.size,
|
|
35
|
+
devDependencies: issues.dependencies.size,
|
|
36
|
+
unresolved: 0,
|
|
37
|
+
exports: 0,
|
|
38
|
+
types: 0,
|
|
39
|
+
nsExports: 0,
|
|
40
|
+
nsTypes: 0,
|
|
41
|
+
duplicates: 0,
|
|
42
|
+
processed: issues.files.size,
|
|
43
|
+
};
|
|
44
|
+
const updateProcessingOutput = (item) => {
|
|
45
|
+
if (!isShowProgress)
|
|
46
|
+
return;
|
|
47
|
+
const counter = unreferencedProductionFiles.length + counters.processed;
|
|
48
|
+
const total = unreferencedProductionFiles.length + usedNonEntryFiles.length;
|
|
49
|
+
const percentage = Math.floor((counter / total) * 100);
|
|
50
|
+
const messages = [(0, log_1.getLine)(`${percentage}%`, `of files processed (${counter} of ${total})`)];
|
|
51
|
+
report.files && messages.push((0, log_1.getLine)(unreferencedProductionFiles.length, 'unused files'));
|
|
52
|
+
report.unlisted && messages.push((0, log_1.getLine)(counters.unresolved, 'unlisted dependencies'));
|
|
53
|
+
report.exports && messages.push((0, log_1.getLine)(counters.exports, 'unused exports'));
|
|
54
|
+
report.nsExports && messages.push((0, log_1.getLine)(counters.nsExports, 'unused exports in namespace'));
|
|
55
|
+
report.types && messages.push((0, log_1.getLine)(counters.types, 'unused types'));
|
|
56
|
+
report.nsTypes && messages.push((0, log_1.getLine)(counters.nsTypes, 'unused types in namespace'));
|
|
57
|
+
report.duplicates && messages.push((0, log_1.getLine)(counters.duplicates, 'duplicate exports'));
|
|
58
|
+
if (counter < total) {
|
|
59
|
+
messages.push('');
|
|
60
|
+
messages.push(`Processing: ${node_path_1.default.relative(workingDir, item.filePath)}`);
|
|
61
|
+
}
|
|
62
|
+
lineRewriter.update(messages);
|
|
63
|
+
};
|
|
64
|
+
const addSymbolIssue = (issueType, issue) => {
|
|
65
|
+
const { filePath, symbol } = issue;
|
|
66
|
+
const key = node_path_1.default.relative(workingDir, filePath);
|
|
67
|
+
issues[issueType][key] = issues[issueType][key] ?? {};
|
|
68
|
+
issues[issueType][key][symbol] = issue;
|
|
69
|
+
counters[issueType]++;
|
|
70
|
+
updateProcessingOutput(issue);
|
|
71
|
+
};
|
|
72
|
+
const addProjectIssue = (issueType, issue) => {
|
|
73
|
+
if (!issues[issueType].has(issue.symbol)) {
|
|
74
|
+
issues[issueType].add(issue.symbol);
|
|
75
|
+
counters[issueType]++;
|
|
76
|
+
}
|
|
77
|
+
updateProcessingOutput(issue);
|
|
78
|
+
};
|
|
79
|
+
if (report.dependencies || report.unlisted) {
|
|
80
|
+
usedEntryFiles.forEach(sourceFile => {
|
|
81
|
+
const unresolvedDependencies = getUnresolvedDependencies(sourceFile);
|
|
82
|
+
unresolvedDependencies.forEach(issue => addSymbolIssue('unresolved', issue));
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (report.dependencies ||
|
|
86
|
+
report.unlisted ||
|
|
87
|
+
report.exports ||
|
|
88
|
+
report.types ||
|
|
89
|
+
report.nsExports ||
|
|
90
|
+
report.nsTypes ||
|
|
91
|
+
report.duplicates) {
|
|
92
|
+
usedNonEntryFiles.forEach(sourceFile => {
|
|
93
|
+
const filePath = sourceFile.getFilePath();
|
|
94
|
+
if (report.dependencies || report.unlisted) {
|
|
95
|
+
const unresolvedDependencies = getUnresolvedDependencies(sourceFile);
|
|
96
|
+
unresolvedDependencies.forEach(issue => addSymbolIssue('unresolved', issue));
|
|
97
|
+
}
|
|
98
|
+
const exportDeclarations = sourceFile.getExportedDeclarations();
|
|
99
|
+
if (report.duplicates) {
|
|
100
|
+
const duplicateExports = (0, ts_morph_helpers_1.findDuplicateExportedNames)(sourceFile);
|
|
101
|
+
duplicateExports.forEach(symbols => {
|
|
102
|
+
const symbol = symbols.join('|');
|
|
103
|
+
addSymbolIssue('duplicates', { filePath, symbol, symbols });
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (report.exports || report.types || report.nsExports || report.nsTypes) {
|
|
107
|
+
const uniqueExportedSymbols = new Set([...exportDeclarations.values()].flat());
|
|
108
|
+
if (uniqueExportedSymbols.size === 1)
|
|
109
|
+
return;
|
|
110
|
+
exportDeclarations.forEach(declarations => {
|
|
111
|
+
declarations.forEach(declaration => {
|
|
112
|
+
const type = (0, type_1.getType)(declaration);
|
|
113
|
+
if (!report.nsExports && !report.nsTypes) {
|
|
114
|
+
if (!report.types && type)
|
|
115
|
+
return;
|
|
116
|
+
if (!report.exports && !type)
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (jsDocOptions.isReadPublicTag && ts_morph_1.ts.getJSDocPublicTag(declaration.compilerNode))
|
|
120
|
+
return;
|
|
121
|
+
let identifier;
|
|
122
|
+
if (declaration.isKind(ts_morph_1.ts.SyntaxKind.Identifier)) {
|
|
123
|
+
identifier = declaration;
|
|
124
|
+
}
|
|
125
|
+
else if (declaration.isKind(ts_morph_1.ts.SyntaxKind.ArrowFunction)) {
|
|
126
|
+
}
|
|
127
|
+
else if (declaration.isKind(ts_morph_1.ts.SyntaxKind.FunctionDeclaration) ||
|
|
128
|
+
declaration.isKind(ts_morph_1.ts.SyntaxKind.ClassDeclaration) ||
|
|
129
|
+
declaration.isKind(ts_morph_1.ts.SyntaxKind.TypeAliasDeclaration) ||
|
|
130
|
+
declaration.isKind(ts_morph_1.ts.SyntaxKind.InterfaceDeclaration) ||
|
|
131
|
+
declaration.isKind(ts_morph_1.ts.SyntaxKind.EnumDeclaration)) {
|
|
132
|
+
identifier = declaration.getFirstChildByKindOrThrow(ts_morph_1.ts.SyntaxKind.Identifier);
|
|
133
|
+
}
|
|
134
|
+
else if (declaration.isKind(ts_morph_1.ts.SyntaxKind.PropertyAccessExpression)) {
|
|
135
|
+
identifier = declaration.getLastChildByKindOrThrow(ts_morph_1.ts.SyntaxKind.Identifier);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
identifier = declaration.getFirstDescendantByKind(ts_morph_1.ts.SyntaxKind.Identifier);
|
|
139
|
+
}
|
|
140
|
+
if (identifier) {
|
|
141
|
+
const identifierText = identifier.getText();
|
|
142
|
+
if (report.exports && issues.exports[filePath]?.[identifierText])
|
|
143
|
+
return;
|
|
144
|
+
if (report.types && issues.types[filePath]?.[identifierText])
|
|
145
|
+
return;
|
|
146
|
+
if (report.nsExports && issues.nsExports[filePath]?.[identifierText])
|
|
147
|
+
return;
|
|
148
|
+
if (report.nsTypes && issues.nsTypes[filePath]?.[identifierText])
|
|
149
|
+
return;
|
|
150
|
+
const refs = identifier.findReferences();
|
|
151
|
+
if (refs.length === 0) {
|
|
152
|
+
addSymbolIssue('exports', { filePath, symbol: identifierText });
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
const refFiles = new Set(refs.map(r => r.compilerObject.references.map(r => r.fileName)).flat());
|
|
156
|
+
const isReferencedOnlyBySelf = refFiles.size === 1 && [...refFiles][0] === filePath;
|
|
157
|
+
if (!isReferencedOnlyBySelf)
|
|
158
|
+
return;
|
|
159
|
+
if ((0, ts_morph_helpers_1.findReferencingNamespaceNodes)(sourceFile).length > 0) {
|
|
160
|
+
if (type) {
|
|
161
|
+
addSymbolIssue('nsTypes', { filePath, symbol: identifierText, symbolType: type });
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
addSymbolIssue('nsExports', { filePath, symbol: identifierText });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else if (type) {
|
|
168
|
+
addSymbolIssue('types', { filePath, symbol: identifierText, symbolType: type });
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
addSymbolIssue('exports', { filePath, symbol: identifierText });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
counters.processed++;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (report.dependencies) {
|
|
182
|
+
const unusedDependencies = getUnusedDependencies();
|
|
183
|
+
unusedDependencies.forEach(symbol => addProjectIssue('dependencies', { filePath: '', symbol }));
|
|
184
|
+
if (isDev) {
|
|
185
|
+
const unusedDevDependencies = getUnusedDevDependencies();
|
|
186
|
+
unusedDevDependencies.forEach(symbol => addProjectIssue('devDependencies', { filePath: '', symbol }));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (isShowProgress)
|
|
190
|
+
lineRewriter.resetLines();
|
|
191
|
+
return { issues, counters };
|
|
192
|
+
}
|
|
193
|
+
exports.findIssues = findIssues;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SourceFile } from 'ts-morph';
|
|
1
2
|
declare type SymbolType = 'type' | 'interface' | 'enum';
|
|
2
3
|
declare type UnusedFileIssues = Set<string>;
|
|
3
4
|
declare type UnusedExportIssues = Record<string, Record<string, Issue>>;
|
|
@@ -30,23 +31,36 @@ declare type BaseLocalConfiguration = {
|
|
|
30
31
|
};
|
|
31
32
|
export declare type LocalConfiguration = BaseLocalConfiguration & {
|
|
32
33
|
dev?: boolean | BaseLocalConfiguration;
|
|
33
|
-
entryFiles: string[];
|
|
34
|
-
projectFiles: string[];
|
|
35
34
|
include?: string[];
|
|
36
35
|
exclude?: string[];
|
|
37
36
|
};
|
|
38
37
|
export declare type ImportedConfiguration = LocalConfiguration | Record<string, LocalConfiguration>;
|
|
39
|
-
export declare type
|
|
38
|
+
export declare type UnresolvedConfiguration = {
|
|
39
|
+
cwd: string;
|
|
40
40
|
workingDir: string;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
configFilePath?: string;
|
|
42
|
+
tsConfigFilePath?: string;
|
|
43
|
+
include: string[];
|
|
44
|
+
exclude: string[];
|
|
45
|
+
ignore: string[];
|
|
46
|
+
gitignore: boolean;
|
|
47
|
+
isDev: boolean;
|
|
48
|
+
isShowProgress: boolean;
|
|
49
|
+
jsDoc: string[];
|
|
50
|
+
};
|
|
51
|
+
export declare type Report = {
|
|
52
|
+
[key in IssueGroup]: boolean;
|
|
53
|
+
};
|
|
54
|
+
export declare type Configuration = {
|
|
55
|
+
workingDir: string;
|
|
56
|
+
report: Report;
|
|
57
|
+
projectFiles: SourceFile[];
|
|
58
|
+
productionFiles: SourceFile[];
|
|
59
|
+
entryFiles: SourceFile[];
|
|
44
60
|
dependencies: string[];
|
|
45
61
|
devDependencies: string[];
|
|
46
62
|
isDev: boolean;
|
|
47
|
-
tsConfigFilePath: undefined | string;
|
|
48
63
|
tsConfigPaths: string[];
|
|
49
|
-
ignorePatterns: string[];
|
|
50
64
|
isShowProgress: boolean;
|
|
51
65
|
jsDocOptions: {
|
|
52
66
|
isReadPublicTag: boolean;
|
package/dist/util/config.d.ts
CHANGED
|
@@ -3,13 +3,4 @@ export declare const resolveConfig: (importedConfiguration: ImportedConfiguratio
|
|
|
3
3
|
workingDir?: string;
|
|
4
4
|
isDev?: boolean;
|
|
5
5
|
}) => LocalConfiguration | undefined;
|
|
6
|
-
export declare const resolveIncludedIssueGroups: (includeArg: string[], excludeArg: string[], resolvedConfig?: LocalConfiguration) =>
|
|
7
|
-
files: boolean;
|
|
8
|
-
dependencies: boolean;
|
|
9
|
-
exports: boolean;
|
|
10
|
-
types: boolean;
|
|
11
|
-
nsExports: boolean;
|
|
12
|
-
nsTypes: boolean;
|
|
13
|
-
duplicates: boolean;
|
|
14
|
-
unlisted: boolean;
|
|
15
|
-
};
|
|
6
|
+
export declare const resolveIncludedIssueGroups: (includeArg: string[], excludeArg: string[], resolvedConfig?: LocalConfiguration) => import("../types").Report;
|
package/dist/util/config.js
CHANGED
|
@@ -19,8 +19,7 @@ const resolveConfig = (importedConfiguration, options) => {
|
|
|
19
19
|
resolvedConfig = resolvedConfig.dev;
|
|
20
20
|
}
|
|
21
21
|
if (!resolvedConfig.entryFiles || !resolvedConfig.projectFiles) {
|
|
22
|
-
console.
|
|
23
|
-
console.info(`Add these properties at root level, or use --cwd and match one of: ${configKeys.join(', ')}\n`);
|
|
22
|
+
console.info(`Add these properties at root level, or use --dir and match one of: ${configKeys.join(', ')}\n`);
|
|
24
23
|
return;
|
|
25
24
|
}
|
|
26
25
|
return resolvedConfig;
|
package/dist/util/path.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const
|
|
3
|
-
|
|
1
|
+
export declare const prependDirToPattern: (workingDir: string, pattern: string) => string;
|
|
2
|
+
export declare const resolvePaths: ({ cwd, workingDir, patterns, ignore, gitignore, }: {
|
|
3
|
+
cwd: string;
|
|
4
|
+
workingDir: string;
|
|
5
|
+
patterns: string[];
|
|
6
|
+
ignore: string[];
|
|
7
|
+
gitignore: boolean;
|
|
8
|
+
}) => Promise<string[]>;
|
package/dist/util/path.js
CHANGED
|
@@ -3,16 +3,26 @@ 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.resolvePaths = exports.
|
|
6
|
+
exports.resolvePaths = exports.prependDirToPattern = void 0;
|
|
7
7
|
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
-
|
|
8
|
+
let _globby;
|
|
9
|
+
const glob = async function (patterns, options) {
|
|
10
|
+
if (!_globby) {
|
|
11
|
+
const { globby } = await eval('import("globby")');
|
|
12
|
+
_globby = globby;
|
|
13
|
+
}
|
|
14
|
+
return _globby(patterns, options);
|
|
15
|
+
};
|
|
16
|
+
const prependDirToPattern = (workingDir, pattern) => {
|
|
9
17
|
if (pattern.startsWith('!'))
|
|
10
18
|
return '!' + node_path_1.default.join(workingDir, pattern.slice(1));
|
|
11
19
|
return node_path_1.default.join(workingDir, pattern);
|
|
12
20
|
};
|
|
13
|
-
exports.
|
|
14
|
-
const resolvePaths = (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
exports.prependDirToPattern = prependDirToPattern;
|
|
22
|
+
const resolvePaths = async ({ cwd, workingDir, patterns, ignore, gitignore, }) => glob(patterns.map(pattern => (0, exports.prependDirToPattern)(node_path_1.default.relative(cwd, workingDir), pattern)), {
|
|
23
|
+
cwd,
|
|
24
|
+
ignore,
|
|
25
|
+
gitignore,
|
|
26
|
+
absolute: true,
|
|
27
|
+
});
|
|
18
28
|
exports.resolvePaths = resolvePaths;
|
package/dist/util/project.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Project } from 'ts-morph';
|
|
2
|
-
import type { SourceFile } from 'ts-morph';
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import type { ProjectOptions, SourceFile } from 'ts-morph';
|
|
3
|
+
export declare const createProject: ({ projectOptions, paths }: {
|
|
4
|
+
projectOptions: ProjectOptions;
|
|
5
|
+
paths?: string[] | undefined;
|
|
6
|
+
}) => Project;
|
|
5
7
|
export declare const partitionSourceFiles: (projectFiles: SourceFile[], productionFiles: SourceFile[]) => SourceFile[][];
|
package/dist/util/project.js
CHANGED
|
@@ -2,19 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.partitionSourceFiles = exports.createProject = void 0;
|
|
4
4
|
const ts_morph_1 = require("ts-morph");
|
|
5
|
-
const
|
|
6
|
-
const createProject = async (configuration, paths) => {
|
|
7
|
-
const { tsConfigFilePath, ignorePatterns } = configuration;
|
|
8
|
-
const tsConfig = tsConfigFilePath ? { tsConfigFilePath } : { compilerOptions: { allowJs: true } };
|
|
5
|
+
const createProject = ({ projectOptions, paths }) => {
|
|
9
6
|
const workspace = new ts_morph_1.Project({
|
|
10
|
-
...
|
|
7
|
+
...projectOptions,
|
|
11
8
|
skipAddingFilesFromTsConfig: true,
|
|
12
9
|
skipFileDependencyResolution: true,
|
|
13
10
|
});
|
|
14
|
-
if (paths)
|
|
15
|
-
|
|
16
|
-
workspace.addSourceFilesAtPaths([...resolvedPaths, ...ignorePatterns]);
|
|
17
|
-
}
|
|
11
|
+
if (paths)
|
|
12
|
+
workspace.addSourceFilesAtPaths(paths);
|
|
18
13
|
return workspace;
|
|
19
14
|
};
|
|
20
15
|
exports.createProject = createProject;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "knip",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
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
|
+
"globby": "13.1.2",
|
|
41
42
|
"is-builtin-module": "3.2.0",
|
|
42
43
|
"micromatch": "4.0.5",
|
|
43
44
|
"ts-morph": "16.0.0",
|
package/dist/util/ignore.d.ts
DELETED
package/dist/util/ignore.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
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.readIgnorePatterns = exports.convertPattern = void 0;
|
|
7
|
-
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
8
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
-
const fs_1 = require("./fs");
|
|
10
|
-
const path_1 = require("./path");
|
|
11
|
-
const convertPattern = (pattern) => (pattern.startsWith('!') ? pattern.substring(1) : `!${pattern}`);
|
|
12
|
-
exports.convertPattern = convertPattern;
|
|
13
|
-
const readIgnoreFile = async (filePath) => {
|
|
14
|
-
let contents = '';
|
|
15
|
-
try {
|
|
16
|
-
contents = (await promises_1.default.readFile(filePath)).toString();
|
|
17
|
-
}
|
|
18
|
-
catch (error) {
|
|
19
|
-
}
|
|
20
|
-
return contents.split(/\r?\n/).filter(line => line && !line.startsWith('#'));
|
|
21
|
-
};
|
|
22
|
-
const traverseDirs = async (rootDir, currentDir, patterns = []) => {
|
|
23
|
-
const gitIgnorePath = node_path_1.default.join(currentDir, '.gitignore');
|
|
24
|
-
const parentDir = node_path_1.default.resolve(currentDir, '..');
|
|
25
|
-
if (await (0, fs_1.isFile)(gitIgnorePath)) {
|
|
26
|
-
(await readIgnoreFile(gitIgnorePath))
|
|
27
|
-
.map(pattern => (0, path_1.addWorkingDirToPattern)(currentDir, pattern))
|
|
28
|
-
.forEach(pattern => patterns.push(pattern));
|
|
29
|
-
}
|
|
30
|
-
if (rootDir === currentDir || parentDir === '/')
|
|
31
|
-
return patterns;
|
|
32
|
-
return traverseDirs(rootDir, parentDir, patterns);
|
|
33
|
-
};
|
|
34
|
-
const readIgnorePatterns = async (cwd, workingDir) => {
|
|
35
|
-
const patterns = await traverseDirs(cwd, workingDir);
|
|
36
|
-
return patterns.map(exports.convertPattern);
|
|
37
|
-
};
|
|
38
|
-
exports.readIgnorePatterns = readIgnorePatterns;
|