@noahnu/unused-files 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +6 -0
- package/README.md +8 -3
- package/lib/api/index.d.ts +9 -5
- package/lib/api/index.js +8 -3
- 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 +15 -8
- package/lib/command.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
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,3 +1,5 @@
|
|
1
|
+
import { type Resolver } from './types';
|
2
|
+
export type { Resolver, ResolverResult, ResolverParams } from './types';
|
1
3
|
export interface FindUnusedFilesOptions {
|
2
4
|
/**
|
3
5
|
* Entry files into the codebase. These files are known to be used and any files that
|
@@ -14,10 +16,11 @@ export interface 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
|
*/
|
@@ -25,6 +28,7 @@ export interface FindUnusedFilesOptions {
|
|
25
28
|
cwd?: string;
|
26
29
|
}
|
27
30
|
export interface UnusedFilesResult {
|
28
|
-
|
31
|
+
used: string[];
|
32
|
+
unused: string[];
|
29
33
|
}
|
30
|
-
export declare function findUnusedFiles({ entryFiles, ignorePatterns, sourceDirectories,
|
34
|
+
export declare function findUnusedFiles({ entryFiles, ignorePatterns, sourceDirectories, resolvers, depth, cwd, }: FindUnusedFilesOptions): Promise<UnusedFilesResult>;
|
package/lib/api/index.js
CHANGED
@@ -10,7 +10,7 @@ const debug_1 = __importDefault(require("debug"));
|
|
10
10
|
const fast_glob_1 = __importDefault(require("fast-glob"));
|
11
11
|
const walkDependencyTree_1 = require("./walkDependencyTree");
|
12
12
|
const debug = (0, debug_1.default)('unused-files');
|
13
|
-
async function findUnusedFiles({ entryFiles, ignorePatterns = ['**/node_modules'], sourceDirectories = [],
|
13
|
+
async function findUnusedFiles({ entryFiles, ignorePatterns = ['**/node_modules'], sourceDirectories = [], resolvers, depth, cwd = process.cwd(), }) {
|
14
14
|
cwd = await node_fs_1.default.promises.realpath(cwd);
|
15
15
|
const globFromSource = async (source) => {
|
16
16
|
const files = await fast_glob_1.default.glob(fast_glob_1.default.isDynamicPattern(source) ? source : node_path_1.default.join(source, '**'), {
|
@@ -24,12 +24,14 @@ async function findUnusedFiles({ entryFiles, ignorePatterns = ['**/node_modules'
|
|
24
24
|
const sourceDirs = sourceDirectories.length > 0 ? sourceDirectories : [cwd];
|
25
25
|
const files = new Set([].concat(...(await Promise.all(sourceDirs.map((source) => globFromSource(source))))));
|
26
26
|
const unvisitedFiles = new Set(files);
|
27
|
+
const visitedFiles = new Set();
|
27
28
|
for (const entryFile of entryFiles) {
|
28
29
|
const entry = await node_fs_1.default.promises.realpath(node_path_1.default.resolve(cwd, entryFile));
|
29
30
|
unvisitedFiles.delete(entry);
|
30
31
|
for await (const { source, dependency } of (0, walkDependencyTree_1.walkDependencyTree)(entry, {
|
31
|
-
|
32
|
+
resolvers,
|
32
33
|
depth,
|
34
|
+
visited: visitedFiles,
|
33
35
|
})) {
|
34
36
|
let resolvedDependency = dependency;
|
35
37
|
if (files.has(dependency)) {
|
@@ -48,7 +50,10 @@ async function findUnusedFiles({ entryFiles, ignorePatterns = ['**/node_modules'
|
|
48
50
|
}
|
49
51
|
}
|
50
52
|
return {
|
51
|
-
|
53
|
+
unused: Array.from(unvisitedFiles)
|
54
|
+
.map((abspath) => node_path_1.default.relative(cwd, abspath))
|
55
|
+
.sort(),
|
56
|
+
used: Array.from(visitedFiles)
|
52
57
|
.map((abspath) => node_path_1.default.relative(cwd, abspath))
|
53
58
|
.sort(),
|
54
59
|
};
|
@@ -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<{
|
@@ -10,8 +10,8 @@ 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
|
});
|
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;
|