@noahnu/unused-files 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +15 -1
- package/README.md +8 -3
- package/lib/api/index.d.ts +13 -9
- package/lib/api/index.js +30 -14
- package/lib/api/types.d.ts +15 -0
- package/lib/api/types.js +2 -0
- package/lib/api/walkDependencyTree.d.ts +3 -2
- package/lib/api/walkDependencyTree.js +16 -10
- package/lib/bin.js +1 -1
- package/lib/command.js +1 -1
- package/package.json +7 -6
package/CHANGELOG.md
CHANGED
@@ -1,6 +1,20 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
<!--
|
3
|
+
<!-- MONOWEAVE:BELOW -->
|
4
|
+
|
5
|
+
## @noahnu/unused-files (v0.1.0) <a name="0.1.0"></a>
|
6
|
+
|
7
|
+
Support custom resolvers.
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
## @noahnu/unused-files (v0.0.2) <a name="0.0.2"></a>
|
12
|
+
|
13
|
+
Update ESLint config and plugins to ESLint v9.
|
14
|
+
|
15
|
+
Update misc. dependencies.
|
16
|
+
|
17
|
+
|
4
18
|
|
5
19
|
## [0.0.1](https://github.com/noahnu/nodejs-tools/compare/@noahnu/unused-files@0.0.0...@noahnu/unused-files@0.0.1) "@noahnu/unused-files" (2024-01-15)<a name="0.0.1"></a>
|
6
20
|
|
package/README.md
CHANGED
@@ -17,9 +17,14 @@ const result = await findUnusedFiles({
|
|
17
17
|
// optional
|
18
18
|
sourceDirectories: [process.cwd()],
|
19
19
|
ignorePatterns: ['**/node_modules'],
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
resolvers: [
|
21
|
+
async ({ request, context }) => {
|
22
|
+
if (request === '@my/alias') {
|
23
|
+
return { result: 'path/to/file/index.ts' }
|
24
|
+
}
|
25
|
+
return null;
|
26
|
+
}
|
27
|
+
],
|
23
28
|
depth: 10,
|
24
29
|
})
|
25
30
|
|
package/lib/api/index.d.ts
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
import { type Resolver } from './types';
|
2
|
+
export type { Resolver, ResolverResult, ResolverParams } from './types';
|
3
|
+
export interface FindUnusedFilesOptions {
|
2
4
|
/**
|
3
5
|
* Entry files into the codebase. These files are known to be used and any files that
|
4
6
|
* are dependencies of these entry files are also considered used files.
|
@@ -14,17 +16,19 @@ export type FindUnusedFilesOptions = {
|
|
14
16
|
*/
|
15
17
|
ignorePatterns?: string[];
|
16
18
|
/**
|
17
|
-
* Custom
|
18
|
-
* It is recommended to rely on package.json aliases over these custom ones
|
19
|
+
* Custom resolver to use to resolve an import path relative to a source file.
|
20
|
+
* It is recommended to rely on package.json aliases over these custom ones
|
21
|
+
* when possible.
|
19
22
|
*/
|
20
|
-
|
23
|
+
resolvers?: Resolver[];
|
21
24
|
/**
|
22
25
|
* Maximum depth to traverse. -1 can be used to disable the depth limit (the default).
|
23
26
|
*/
|
24
27
|
depth?: number;
|
25
28
|
cwd?: string;
|
26
|
-
}
|
27
|
-
export
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
}
|
30
|
+
export interface UnusedFilesResult {
|
31
|
+
used: string[];
|
32
|
+
unused: string[];
|
33
|
+
}
|
34
|
+
export declare function findUnusedFiles({ entryFiles, ignorePatterns, sourceDirectories, resolvers, depth, cwd, }: FindUnusedFilesOptions): Promise<UnusedFilesResult>;
|
package/lib/api/index.js
CHANGED
@@ -3,42 +3,58 @@ 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.findUnusedFiles =
|
6
|
+
exports.findUnusedFiles = findUnusedFiles;
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
7
8
|
const node_path_1 = __importDefault(require("node:path"));
|
8
9
|
const debug_1 = __importDefault(require("debug"));
|
9
10
|
const fast_glob_1 = __importDefault(require("fast-glob"));
|
10
11
|
const walkDependencyTree_1 = require("./walkDependencyTree");
|
11
12
|
const debug = (0, debug_1.default)('unused-files');
|
12
|
-
async function findUnusedFiles({ entryFiles, ignorePatterns = ['**/node_modules'], sourceDirectories = [],
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
async function findUnusedFiles({ entryFiles, ignorePatterns = ['**/node_modules'], sourceDirectories = [], resolvers, depth, cwd = process.cwd(), }) {
|
14
|
+
cwd = await node_fs_1.default.promises.realpath(cwd);
|
15
|
+
const globFromSource = async (source) => {
|
16
|
+
const files = await fast_glob_1.default.glob(fast_glob_1.default.isDynamicPattern(source) ? source : node_path_1.default.join(source, '**'), {
|
17
|
+
dot: false,
|
18
|
+
ignore: ignorePatterns,
|
19
|
+
absolute: true,
|
20
|
+
cwd,
|
21
|
+
});
|
22
|
+
return Promise.all(files.map(async (file) => await node_fs_1.default.promises.realpath(file).catch(() => file)));
|
23
|
+
};
|
19
24
|
const sourceDirs = sourceDirectories.length > 0 ? sourceDirectories : [cwd];
|
20
25
|
const files = new Set([].concat(...(await Promise.all(sourceDirs.map((source) => globFromSource(source))))));
|
21
26
|
const unvisitedFiles = new Set(files);
|
27
|
+
const visitedFiles = new Set();
|
22
28
|
for (const entryFile of entryFiles) {
|
23
|
-
const entry = node_path_1.default.resolve(cwd, entryFile);
|
29
|
+
const entry = await node_fs_1.default.promises.realpath(node_path_1.default.resolve(cwd, entryFile));
|
24
30
|
unvisitedFiles.delete(entry);
|
25
31
|
for await (const { source, dependency } of (0, walkDependencyTree_1.walkDependencyTree)(entry, {
|
26
|
-
|
32
|
+
resolvers,
|
27
33
|
depth,
|
34
|
+
visited: visitedFiles,
|
28
35
|
})) {
|
36
|
+
let resolvedDependency = dependency;
|
29
37
|
if (files.has(dependency)) {
|
30
38
|
debug(`${source}: ${dependency} [dependency]`);
|
31
39
|
}
|
32
40
|
else {
|
33
|
-
|
41
|
+
const realpath = await node_fs_1.default.promises.realpath(dependency);
|
42
|
+
if (files.has(realpath)) {
|
43
|
+
resolvedDependency = realpath;
|
44
|
+
}
|
45
|
+
else {
|
46
|
+
debug(`${source}: ${dependency} [unknown dependency]`);
|
47
|
+
}
|
34
48
|
}
|
35
|
-
unvisitedFiles.delete(
|
49
|
+
unvisitedFiles.delete(resolvedDependency);
|
36
50
|
}
|
37
51
|
}
|
38
52
|
return {
|
39
|
-
|
53
|
+
unused: Array.from(unvisitedFiles)
|
54
|
+
.map((abspath) => node_path_1.default.relative(cwd, abspath))
|
55
|
+
.sort(),
|
56
|
+
used: Array.from(visitedFiles)
|
40
57
|
.map((abspath) => node_path_1.default.relative(cwd, abspath))
|
41
58
|
.sort(),
|
42
59
|
};
|
43
60
|
}
|
44
|
-
exports.findUnusedFiles = findUnusedFiles;
|
@@ -0,0 +1,15 @@
|
|
1
|
+
export interface ResolverResult {
|
2
|
+
/** Resolved path. */
|
3
|
+
result: string;
|
4
|
+
}
|
5
|
+
export interface ResolverParams {
|
6
|
+
/** The module/path being requested. */
|
7
|
+
request: string;
|
8
|
+
/** The file or directory to resolve the request from. */
|
9
|
+
context: string;
|
10
|
+
}
|
11
|
+
/**
|
12
|
+
* Used to resolve imports to the absolute path of the file.
|
13
|
+
* Return 'null' if unable to resolve and we will attempt the next resolver in the chain.
|
14
|
+
*/
|
15
|
+
export type Resolver = (params: ResolverParams) => Promise<ResolverResult | null>;
|
package/lib/api/types.js
ADDED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
import { type Resolver } from './types';
|
2
|
+
export declare function walkDependencyTree(source: string, { resolvers, visited, depth, }?: {
|
3
|
+
resolvers?: Resolver[];
|
3
4
|
visited?: Set<string>;
|
4
5
|
depth?: number;
|
5
6
|
}): AsyncGenerator<{
|
@@ -3,15 +3,15 @@ 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.walkDependencyTree =
|
6
|
+
exports.walkDependencyTree = walkDependencyTree;
|
7
7
|
const node_fs_1 = __importDefault(require("node:fs"));
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
9
9
|
const typescript_estree_1 = require("@typescript-eslint/typescript-estree");
|
10
10
|
const debug_1 = __importDefault(require("debug"));
|
11
11
|
const debug = (0, debug_1.default)('unused-files:parse');
|
12
12
|
const DEFAULT_DEPTH_LIMIT = -1; // no depth limit
|
13
|
-
const VALID_EXTENSIONS = new Set(['ts', 'tsx', 'mts', 'cts', 'js', 'mjs', 'cjs']);
|
14
|
-
async function* walkDependencyTree(source, {
|
13
|
+
const VALID_EXTENSIONS = new Set(['ts', 'tsx', 'mts', 'cts', 'js', 'jsx', 'mjs', 'cjs']);
|
14
|
+
async function* walkDependencyTree(source, { resolvers, visited, depth = DEFAULT_DEPTH_LIMIT, } = {}) {
|
15
15
|
const ext = node_path_1.default.extname(source).substring(1);
|
16
16
|
if (!VALID_EXTENSIONS.has(ext)) {
|
17
17
|
debug(`${source}: Unknown file extension '${ext}' [skipping]`);
|
@@ -59,6 +59,11 @@ async function* walkDependencyTree(source, { aliases, visited, depth = DEFAULT_D
|
|
59
59
|
importFroms.add(node.source.value);
|
60
60
|
}
|
61
61
|
}
|
62
|
+
else if (node.source.type === typescript_estree_1.TSESTree.AST_NODE_TYPES.TemplateLiteral &&
|
63
|
+
!node.source.expressions.length &&
|
64
|
+
node.source.quasis.length === 1) {
|
65
|
+
importFroms.add(node.source.quasis[0].value.cooked);
|
66
|
+
}
|
62
67
|
else {
|
63
68
|
debug(`${source}: Dynamic import expression found at ${node.loc.start}:${node.loc.end}`);
|
64
69
|
}
|
@@ -68,10 +73,12 @@ async function* walkDependencyTree(source, { aliases, visited, depth = DEFAULT_D
|
|
68
73
|
for (const body of ast.body) {
|
69
74
|
(0, typescript_estree_1.simpleTraverse)(body, { visitors });
|
70
75
|
}
|
71
|
-
const resolveToAbsPath = (request) => {
|
72
|
-
const
|
73
|
-
|
74
|
-
|
76
|
+
const resolveToAbsPath = async (request) => {
|
77
|
+
for (const resolver of resolvers ?? []) {
|
78
|
+
const resolution = await resolver({ context: source, request });
|
79
|
+
if (resolution) {
|
80
|
+
return node_path_1.default.resolve(resolution.result);
|
81
|
+
}
|
75
82
|
}
|
76
83
|
try {
|
77
84
|
return require.resolve(request, { paths: [node_path_1.default.dirname(source)] });
|
@@ -80,12 +87,12 @@ async function* walkDependencyTree(source, { aliases, visited, depth = DEFAULT_D
|
|
80
87
|
return undefined;
|
81
88
|
};
|
82
89
|
for (const importFrom of Array.from(importFroms)) {
|
83
|
-
const absPath = resolveToAbsPath(importFrom);
|
90
|
+
const absPath = await resolveToAbsPath(importFrom);
|
84
91
|
if (absPath) {
|
85
92
|
yield { dependency: absPath, source };
|
86
93
|
if (depth === -1 || depth > 0) {
|
87
94
|
yield* walkDependencyTree(absPath, {
|
88
|
-
|
95
|
+
resolvers,
|
89
96
|
visited: visitedSet,
|
90
97
|
depth: depth === -1 ? depth : depth - 1,
|
91
98
|
});
|
@@ -96,4 +103,3 @@ async function* walkDependencyTree(source, { aliases, visited, depth = DEFAULT_D
|
|
96
103
|
}
|
97
104
|
}
|
98
105
|
}
|
99
|
-
exports.walkDependencyTree = walkDependencyTree;
|
package/lib/bin.js
CHANGED
@@ -5,7 +5,7 @@ const command_1 = require("./command");
|
|
5
5
|
const cli = new clipanion_1.Cli({
|
6
6
|
binaryLabel: '@noahnu/unused-files',
|
7
7
|
binaryName: 'yarn @noahnu/unused-files',
|
8
|
-
// eslint-disable-next-line @typescript-eslint/no-
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
9
9
|
binaryVersion: require('../package.json').version,
|
10
10
|
enableCapture: true,
|
11
11
|
});
|
package/lib/command.js
CHANGED
@@ -57,7 +57,7 @@ class BaseCommand extends clipanion_1.Command {
|
|
57
57
|
? -1
|
58
58
|
: parseInt(Math.max(this.depth, -1).toFixed(0), 10),
|
59
59
|
});
|
60
|
-
this.context.stdout.write(result.
|
60
|
+
this.context.stdout.write(result.unused.join('\n'));
|
61
61
|
}
|
62
62
|
}
|
63
63
|
exports.BaseCommand = BaseCommand;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@noahnu/unused-files",
|
3
|
-
"version": "0.0
|
3
|
+
"version": "0.1.0",
|
4
4
|
"repository": {
|
5
5
|
"type": "git",
|
6
6
|
"url": "https://github.com/noahnu/nodejs-tools.git",
|
@@ -15,7 +15,7 @@
|
|
15
15
|
"scripts": {
|
16
16
|
"clean": "run workspace:clean \"$(pwd)\"",
|
17
17
|
"prepack": "run workspace:build \"$(pwd)\"",
|
18
|
-
"run-local": "run -T
|
18
|
+
"run-local": "run -T tsx ./src/bin.ts"
|
19
19
|
},
|
20
20
|
"bin": "./lib/bin.js",
|
21
21
|
"main": "./lib/api/index.js",
|
@@ -31,16 +31,17 @@
|
|
31
31
|
],
|
32
32
|
"dependencies": {
|
33
33
|
"@types/debug": "^4.1.12",
|
34
|
-
"@typescript-eslint/typescript-estree": "^
|
35
|
-
"clipanion": "4.0.0-rc.
|
36
|
-
"debug": "^4.3.
|
34
|
+
"@typescript-eslint/typescript-estree": "^8.13.0",
|
35
|
+
"clipanion": "^4.0.0-rc.4",
|
36
|
+
"debug": "^4.3.7",
|
37
37
|
"fast-glob": "^3.3.2",
|
38
38
|
"typanion": "^3.14.0"
|
39
39
|
},
|
40
40
|
"devDependencies": {
|
41
41
|
"@jest/globals": "^29.7.0",
|
42
42
|
"@noahnu/internal-test-utils": "0.0.0",
|
43
|
-
"@types/node": "^
|
43
|
+
"@types/node": "^22.9.0",
|
44
|
+
"typescript": "^5.6.3"
|
44
45
|
},
|
45
46
|
"types": "./lib/api/index.d.ts"
|
46
47
|
}
|