@noahnu/unused-files 0.0.2 → 0.1.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/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;
|