@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 CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  <!-- MONOWEAVE:BELOW -->
4
4
 
5
+ ## @noahnu/unused-files (v0.1.0) <a name="0.1.0"></a>
6
+
7
+ Support custom resolvers.
8
+
9
+
10
+
5
11
  ## @noahnu/unused-files (v0.0.2) <a name="0.0.2"></a>
6
12
 
7
13
  Update ESLint config and plugins to ESLint v9.
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
- aliases: {
21
- '@my/alias': 'path/to/file/index.ts',
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
 
@@ -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 aliases that are consulted first before attempting to resolve the import path.
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
- aliases?: Partial<Record<string, string>>;
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
- unusedFiles: string[];
31
+ used: string[];
32
+ unused: string[];
29
33
  }
30
- export declare function findUnusedFiles({ entryFiles, ignorePatterns, sourceDirectories, aliases, depth, cwd, }: FindUnusedFilesOptions): Promise<UnusedFilesResult>;
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 = [], aliases, depth, cwd = process.cwd(), }) {
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
- aliases,
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
- unusedFiles: Array.from(unvisitedFiles)
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>;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,5 +1,6 @@
1
- export declare function walkDependencyTree(source: string, { aliases, visited, depth, }?: {
2
- aliases?: Partial<Record<string, string>>;
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, { aliases, visited, depth = DEFAULT_DEPTH_LIMIT, } = {}) {
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 aliasedPath = aliases?.[request];
73
- if (aliasedPath) {
74
- return node_path_1.default.resolve(aliasedPath);
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
- aliases,
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.unusedFiles.join('\n'));
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.2",
3
+ "version": "0.1.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/noahnu/nodejs-tools.git",