foresthouse 1.0.0-dev.13 → 1.0.0-dev.15
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 +4 -1
- package/dist/cli.mjs +82 -6
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +4 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{react-a7ugf8JN.mjs → react-B0frvTuH.mjs} +122 -25
- package/dist/react-B0frvTuH.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/react-a7ugf8JN.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -65,9 +65,12 @@ src/main.ts
|
|
|
65
65
|
`react` command:
|
|
66
66
|
|
|
67
67
|
- `foresthouse react <path>`: print a React usage tree from an entry file
|
|
68
|
+
- `foresthouse react --nextjs`: infer Next.js page entries from `app/`, `pages/`, `src/app/`, and `src/pages/`
|
|
68
69
|
- `--cwd <path>`: working directory for resolving the entry file and config
|
|
69
70
|
- `--config <path>`: use a specific `tsconfig.json` or `jsconfig.json`
|
|
70
|
-
- `--
|
|
71
|
+
- `--nextjs`: allow omitting the explicit React entry file and discover Next.js page entries relative to `--cwd` or the current directory
|
|
72
|
+
- `--filter <component|hook|builtin>`: limit the output to a specific React symbol kind
|
|
73
|
+
- `--builtin`: include built-in HTML nodes such as `button` and `div`
|
|
71
74
|
- `--no-workspaces`: stop at sibling workspace package boundaries instead of expanding them
|
|
72
75
|
- `--project-only`: restrict traversal to the active `tsconfig.json` or `jsconfig.json` project
|
|
73
76
|
- `--json`: print a JSON tree instead of ASCII output
|
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { c as analyzeReactUsage, i as printDependencyTree, l as analyzeDependencies, n as graphToSerializableTree, r as printReactUsageTree, t as graphToSerializableReactTree } from "./react-
|
|
2
|
+
import { c as analyzeReactUsage, i as printDependencyTree, l as analyzeDependencies, n as graphToSerializableTree, r as printReactUsageTree, t as graphToSerializableReactTree, u as isSourceCodeFile } from "./react-B0frvTuH.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import fs from "node:fs";
|
|
4
6
|
import process$1 from "node:process";
|
|
5
7
|
import { cac } from "cac";
|
|
6
8
|
//#region src/commands/base.ts
|
|
@@ -46,10 +48,71 @@ var ImportCommand = class extends BaseCommand {
|
|
|
46
48
|
}
|
|
47
49
|
};
|
|
48
50
|
//#endregion
|
|
51
|
+
//#region src/app/react-entry-files.ts
|
|
52
|
+
const NEXT_JS_ENTRY_ROOTS = [
|
|
53
|
+
"pages",
|
|
54
|
+
"app",
|
|
55
|
+
path.join("src", "pages"),
|
|
56
|
+
path.join("src", "app")
|
|
57
|
+
];
|
|
58
|
+
function resolveReactEntryFiles(options) {
|
|
59
|
+
if (options.entryFile !== void 0) return [options.entryFile];
|
|
60
|
+
if (!options.nextjs) throw new Error("Missing React entry file. Use `foresthouse react <entry-file>` or `foresthouse react --nextjs`.");
|
|
61
|
+
return discoverNextJsPageEntries(options.cwd);
|
|
62
|
+
}
|
|
63
|
+
function discoverNextJsPageEntries(cwd) {
|
|
64
|
+
const effectiveCwd = path.resolve(cwd ?? process.cwd());
|
|
65
|
+
const entries = /* @__PURE__ */ new Set();
|
|
66
|
+
collectPagesRouterEntries(path.join(effectiveCwd, "pages"), effectiveCwd, entries);
|
|
67
|
+
collectAppRouterEntries(path.join(effectiveCwd, "app"), effectiveCwd, entries);
|
|
68
|
+
collectPagesRouterEntries(path.join(effectiveCwd, "src", "pages"), effectiveCwd, entries);
|
|
69
|
+
collectAppRouterEntries(path.join(effectiveCwd, "src", "app"), effectiveCwd, entries);
|
|
70
|
+
const resolvedEntries = [...entries].sort();
|
|
71
|
+
if (resolvedEntries.length > 0) return resolvedEntries;
|
|
72
|
+
const searchedRoots = NEXT_JS_ENTRY_ROOTS.map((root) => `\`${root}/\``).join(", ");
|
|
73
|
+
throw new Error(`No Next.js page entries found. Searched ${searchedRoots} relative to ${effectiveCwd}.`);
|
|
74
|
+
}
|
|
75
|
+
function collectPagesRouterEntries(rootDirectory, cwd, entries) {
|
|
76
|
+
walkDirectory(rootDirectory, (filePath, relativePath) => {
|
|
77
|
+
if (!isSourceCodeFile(filePath) || filePath.endsWith(".d.ts")) return;
|
|
78
|
+
if (relativePath.split(path.sep)[0] === "api") return;
|
|
79
|
+
if (path.basename(filePath).startsWith("_")) return;
|
|
80
|
+
entries.add(path.relative(cwd, filePath));
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function collectAppRouterEntries(rootDirectory, cwd, entries) {
|
|
84
|
+
walkDirectory(rootDirectory, (filePath) => {
|
|
85
|
+
if (!isSourceCodeFile(filePath) || filePath.endsWith(".d.ts")) return;
|
|
86
|
+
const extension = path.extname(filePath);
|
|
87
|
+
if (path.basename(filePath, extension) !== "page") return;
|
|
88
|
+
entries.add(path.relative(cwd, filePath));
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function walkDirectory(rootDirectory, visitor) {
|
|
92
|
+
if (!fs.existsSync(rootDirectory)) return;
|
|
93
|
+
const stack = [rootDirectory];
|
|
94
|
+
while (stack.length > 0) {
|
|
95
|
+
const directory = stack.pop();
|
|
96
|
+
if (directory === void 0) continue;
|
|
97
|
+
const directoryEntries = fs.readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
|
|
98
|
+
for (let index = directoryEntries.length - 1; index >= 0; index -= 1) {
|
|
99
|
+
const entry = directoryEntries[index];
|
|
100
|
+
if (entry === void 0) continue;
|
|
101
|
+
const entryPath = path.join(directory, entry.name);
|
|
102
|
+
if (entry.isDirectory()) {
|
|
103
|
+
stack.push(entryPath);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (!entry.isFile()) continue;
|
|
107
|
+
visitor(entryPath, path.relative(rootDirectory, entryPath));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
49
112
|
//#region src/commands/react.ts
|
|
50
113
|
var ReactCommand = class extends BaseCommand {
|
|
51
114
|
analyze() {
|
|
52
|
-
return analyzeReactUsage(this.options
|
|
115
|
+
return analyzeReactUsage(resolveReactEntryFiles(this.options), this.getAnalyzeOptions());
|
|
53
116
|
}
|
|
54
117
|
serialize(graph) {
|
|
55
118
|
return graphToSerializableReactTree(graph, { filter: this.getFilter() });
|
|
@@ -60,6 +123,12 @@ var ReactCommand = class extends BaseCommand {
|
|
|
60
123
|
filter: this.getFilter()
|
|
61
124
|
});
|
|
62
125
|
}
|
|
126
|
+
getAnalyzeOptions() {
|
|
127
|
+
return {
|
|
128
|
+
...super.getAnalyzeOptions(),
|
|
129
|
+
includeBuiltins: this.options.includeBuiltins
|
|
130
|
+
};
|
|
131
|
+
}
|
|
63
132
|
getFilter() {
|
|
64
133
|
return this.options.filter;
|
|
65
134
|
}
|
|
@@ -111,7 +180,7 @@ var CliMain = class {
|
|
|
111
180
|
cli.command("import [entry-file]", "Analyze an entry file and print its dependency tree.").usage("import <entry-file> [options]").option("--entry <path>", "Entry file to analyze.").option("--cwd <path>", "Working directory used for relative paths.").option("--config <path>", "Explicit tsconfig.json or jsconfig.json path.").option("--include-externals", "Include packages and Node built-ins in the tree.").option("--no-workspaces", "Do not expand sibling workspace packages into source subtrees.").option("--project-only", "Restrict traversal to the active tsconfig.json or jsconfig.json project.").option("--no-unused", "Omit imports that are never referenced.").option("--json", "Print the dependency tree as JSON.").action((entryFile, rawOptions) => {
|
|
112
181
|
runCli(normalizeImportCliOptions(entryFile, rawOptions));
|
|
113
182
|
});
|
|
114
|
-
cli.command("react
|
|
183
|
+
cli.command("react [entry-file]", "Analyze React usage from an entry file.").usage("react [entry-file] [options]").option("--cwd <path>", "Working directory used for relative paths.").option("--config <path>", "Explicit tsconfig.json or jsconfig.json path.").option("--nextjs", "Infer Next.js page entries from app/ and pages/ when no entry is provided.").option("--filter <mode>", "Limit output to `component`, `hook`, or `builtin` usages.").option("--builtin", "Include built-in HTML nodes in the React tree.").option("--no-workspaces", "Do not expand sibling workspace packages into source subtrees.").option("--project-only", "Restrict traversal to the active tsconfig.json or jsconfig.json project.").option("--json", "Print the React usage tree as JSON.").action((entryFile, rawOptions) => {
|
|
115
184
|
runCli(normalizeReactCliOptions(entryFile, rawOptions));
|
|
116
185
|
});
|
|
117
186
|
cli.help();
|
|
@@ -151,13 +220,15 @@ function normalizeImportCliOptions(entryFile, options) {
|
|
|
151
220
|
function normalizeReactCliOptions(entryFile, options) {
|
|
152
221
|
return {
|
|
153
222
|
command: "react",
|
|
154
|
-
entryFile,
|
|
223
|
+
entryFile: resolveReactEntryFile(entryFile, options.nextjs),
|
|
155
224
|
cwd: options.cwd,
|
|
156
225
|
configPath: options.config,
|
|
157
226
|
expandWorkspaces: options.workspaces !== false,
|
|
158
227
|
projectOnly: options.projectOnly === true,
|
|
159
228
|
json: options.json === true,
|
|
160
|
-
filter: normalizeReactFilter(options.filter)
|
|
229
|
+
filter: normalizeReactFilter(options.filter),
|
|
230
|
+
nextjs: options.nextjs === true,
|
|
231
|
+
includeBuiltins: options.builtin === true
|
|
161
232
|
};
|
|
162
233
|
}
|
|
163
234
|
function resolveImportEntryFile(positionalEntryFile, optionEntryFile) {
|
|
@@ -168,9 +239,14 @@ function resolveImportEntryFile(positionalEntryFile, optionEntryFile) {
|
|
|
168
239
|
}
|
|
169
240
|
function normalizeReactFilter(filter) {
|
|
170
241
|
if (filter === void 0) return "all";
|
|
171
|
-
if (filter === "component" || filter === "hook") return filter;
|
|
242
|
+
if (filter === "component" || filter === "hook" || filter === "builtin") return filter;
|
|
172
243
|
throw new Error(`Unknown React filter: ${filter}`);
|
|
173
244
|
}
|
|
245
|
+
function resolveReactEntryFile(entryFile, nextjs) {
|
|
246
|
+
if (entryFile !== void 0) return entryFile;
|
|
247
|
+
if (nextjs === true) return;
|
|
248
|
+
throw new Error("Missing React entry file. Use `foresthouse react <entry-file>` or `foresthouse react --nextjs`.");
|
|
249
|
+
}
|
|
174
250
|
//#endregion
|
|
175
251
|
//#region src/cli.ts
|
|
176
252
|
const { version } = createRequire(import.meta.url)("../package.json");
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":["process"],"sources":["../src/commands/base.ts","../src/commands/import.ts","../src/commands/react.ts","../src/app/run.ts","../src/app/cli.ts","../src/cli.ts"],"sourcesContent":["import type { CliOptions } from '../app/args.js'\nimport type { AnalyzeOptions } from '../types/analyze-options.js'\n\nexport abstract class BaseCommand<\n TGraph,\n TOptions extends CliOptions = CliOptions,\n> {\n constructor(protected readonly options: TOptions) {}\n\n run(): void {\n const graph = this.analyze()\n\n if (this.isJsonMode()) {\n process.stdout.write(\n `${JSON.stringify(this.serialize(graph), null, 2)}\\n`,\n )\n return\n }\n\n process.stdout.write(`${this.render(graph)}\\n`)\n }\n\n protected isJsonMode(): boolean {\n return this.options.json\n }\n\n protected getAnalyzeOptions(): AnalyzeOptions {\n return {\n ...(this.options.cwd === undefined ? {} : { cwd: this.options.cwd }),\n ...(this.options.configPath === undefined\n ? {}\n : { configPath: this.options.configPath }),\n expandWorkspaces: this.options.expandWorkspaces,\n projectOnly: this.options.projectOnly,\n }\n }\n\n protected abstract analyze(): TGraph\n protected abstract serialize(graph: TGraph): object\n protected abstract render(graph: TGraph): string\n}\n","import { analyzeDependencies } from '../analyzers/import/index.js'\nimport type { ImportCliOptions } from '../app/args.js'\nimport { printDependencyTree } from '../output/ascii/import.js'\nimport { graphToSerializableTree } from '../output/json/import.js'\nimport type { DependencyGraph } from '../types/dependency-graph.js'\nimport { BaseCommand } from './base.js'\n\nexport class ImportCommand extends BaseCommand<\n DependencyGraph,\n ImportCliOptions\n> {\n protected analyze(): DependencyGraph {\n return analyzeDependencies(this.options.entryFile, this.getAnalyzeOptions())\n }\n\n protected serialize(graph: DependencyGraph): object {\n return graphToSerializableTree(graph, {\n omitUnused: this.options.omitUnused,\n })\n }\n\n protected render(graph: DependencyGraph): string {\n return printDependencyTree(graph, {\n cwd: this.options.cwd ?? graph.cwd,\n includeExternals: this.options.includeExternals,\n omitUnused: this.options.omitUnused,\n })\n }\n}\n","import { analyzeReactUsage } from '../analyzers/react/index.js'\nimport type { ReactCliOptions } from '../app/args.js'\nimport { printReactUsageTree } from '../output/ascii/react.js'\nimport { graphToSerializableReactTree } from '../output/json/react.js'\nimport type { ReactUsageFilter } from '../types/react-usage-filter.js'\nimport type { ReactUsageGraph } from '../types/react-usage-graph.js'\nimport { BaseCommand } from './base.js'\n\nexport class ReactCommand extends BaseCommand<\n ReactUsageGraph,\n ReactCliOptions\n> {\n protected analyze(): ReactUsageGraph {\n return analyzeReactUsage(this.options.entryFile, this.getAnalyzeOptions())\n }\n\n protected serialize(graph: ReactUsageGraph): object {\n return graphToSerializableReactTree(graph, {\n filter: this.getFilter(),\n })\n }\n\n protected render(graph: ReactUsageGraph): string {\n return printReactUsageTree(graph, {\n cwd: this.options.cwd ?? graph.cwd,\n filter: this.getFilter(),\n })\n }\n\n private getFilter(): ReactUsageFilter {\n return this.options.filter\n }\n}\n","import { ImportCommand } from '../commands/import.js'\nimport { ReactCommand } from '../commands/react.js'\nimport type { CliOptions } from './args.js'\n\nexport function runCli(options: CliOptions): void {\n new CliApplication(options).run()\n}\n\nclass CliApplication {\n constructor(private readonly options: CliOptions) {}\n\n run(): void {\n this.createCommand().run()\n }\n\n private createCommand(): {\n run(): void\n } {\n if (this.options.command === 'react') {\n return new ReactCommand(this.options)\n }\n\n return new ImportCommand(this.options)\n }\n}\n","import process from 'node:process'\n\nimport { cac } from 'cac'\n\nimport type { ReactUsageFilter } from '../types/react-usage-filter.js'\nimport type { ImportCliOptions, ReactCliOptions } from './args.js'\nimport { runCli } from './run.js'\n\nexport function main(version: string, argv = process.argv.slice(2)): void {\n new CliMain(version, argv).run()\n}\n\nclass CliMain {\n constructor(\n private readonly version: string,\n private readonly argv: string[],\n ) {}\n\n run(): void {\n try {\n const cli = this.createCli()\n\n if (this.argv.length === 0) {\n cli.outputHelp()\n return\n }\n\n this.validateCommandSyntax()\n cli.parse(this.toProcessArgv())\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Unknown error occurred.'\n process.stderr.write(`foresthouse: ${message}\\n`)\n process.exitCode = 1\n }\n }\n\n private createCli() {\n const cli = cac('foresthouse')\n\n cli\n .command(\n 'import [entry-file]',\n 'Analyze an entry file and print its dependency tree.',\n )\n .usage('import <entry-file> [options]')\n .option('--entry <path>', 'Entry file to analyze.')\n .option('--cwd <path>', 'Working directory used for relative paths.')\n .option(\n '--config <path>',\n 'Explicit tsconfig.json or jsconfig.json path.',\n )\n .option(\n '--include-externals',\n 'Include packages and Node built-ins in the tree.',\n )\n .option(\n '--no-workspaces',\n 'Do not expand sibling workspace packages into source subtrees.',\n )\n .option(\n '--project-only',\n 'Restrict traversal to the active tsconfig.json or jsconfig.json project.',\n )\n .option('--no-unused', 'Omit imports that are never referenced.')\n .option('--json', 'Print the dependency tree as JSON.')\n .action(\n (entryFile: string | undefined, rawOptions: ParsedImportCliOptions) => {\n runCli(normalizeImportCliOptions(entryFile, rawOptions))\n },\n )\n\n cli\n .command('react <entry-file>', 'Analyze React usage from an entry file.')\n .usage('react <entry-file> [options]')\n .option('--cwd <path>', 'Working directory used for relative paths.')\n .option(\n '--config <path>',\n 'Explicit tsconfig.json or jsconfig.json path.',\n )\n .option(\n '--filter <mode>',\n 'Limit output to `component` or `hook` usages.',\n )\n .option(\n '--no-workspaces',\n 'Do not expand sibling workspace packages into source subtrees.',\n )\n .option(\n '--project-only',\n 'Restrict traversal to the active tsconfig.json or jsconfig.json project.',\n )\n .option('--json', 'Print the React usage tree as JSON.')\n .action((entryFile: string, rawOptions: ParsedReactCliOptions) => {\n runCli(normalizeReactCliOptions(entryFile, rawOptions))\n })\n\n cli.help()\n cli.version(this.version)\n\n return cli\n }\n\n private validateCommandSyntax(): void {\n if (this.argv.length === 0) {\n return\n }\n\n const firstArgument = this.argv[0]\n\n if (firstArgument === undefined) {\n return\n }\n\n if (firstArgument === '--react' || firstArgument.startsWith('--react=')) {\n throw new Error('Unknown option `--react`')\n }\n\n if (firstArgument.startsWith('-')) {\n return\n }\n\n if (firstArgument === 'import' || firstArgument === 'react') {\n return\n }\n\n throw new Error(`Unknown command \\`${firstArgument}\\``)\n }\n\n private toProcessArgv(): string[] {\n return ['node', 'foresthouse', ...this.argv]\n }\n}\n\ninterface ParsedBaseCliOptions {\n readonly cwd?: string\n readonly config?: string\n readonly workspaces?: boolean\n readonly projectOnly?: boolean\n readonly json?: boolean\n}\n\ninterface ParsedImportCliOptions extends ParsedBaseCliOptions {\n readonly entry?: string\n readonly includeExternals?: boolean\n readonly unused?: boolean\n}\n\ninterface ParsedReactCliOptions extends ParsedBaseCliOptions {\n readonly filter?: string\n}\n\nfunction normalizeImportCliOptions(\n entryFile: string | undefined,\n options: ParsedImportCliOptions,\n): ImportCliOptions {\n return {\n command: 'import',\n entryFile: resolveImportEntryFile(entryFile, options.entry),\n cwd: options.cwd,\n configPath: options.config,\n expandWorkspaces: options.workspaces !== false,\n projectOnly: options.projectOnly === true,\n includeExternals: options.includeExternals === true,\n omitUnused: options.unused === false,\n json: options.json === true,\n }\n}\n\nfunction normalizeReactCliOptions(\n entryFile: string,\n options: ParsedReactCliOptions,\n): ReactCliOptions {\n return {\n command: 'react',\n entryFile,\n cwd: options.cwd,\n configPath: options.config,\n expandWorkspaces: options.workspaces !== false,\n projectOnly: options.projectOnly === true,\n json: options.json === true,\n filter: normalizeReactFilter(options.filter),\n }\n}\n\nfunction resolveImportEntryFile(\n positionalEntryFile: string | undefined,\n optionEntryFile: string | undefined,\n): string {\n if (\n positionalEntryFile !== undefined &&\n optionEntryFile !== undefined &&\n positionalEntryFile !== optionEntryFile\n ) {\n throw new Error(\n 'Provide the import entry only once, either as `foresthouse import <entry-file>` or `foresthouse import --entry <path>`.',\n )\n }\n\n const resolvedEntryFile = positionalEntryFile ?? optionEntryFile\n\n if (resolvedEntryFile === undefined) {\n throw new Error(\n 'Missing import entry file. Use `foresthouse import <entry-file>` or `foresthouse import --entry <path>`.',\n )\n }\n\n return resolvedEntryFile\n}\n\nfunction normalizeReactFilter(\n filter: ParsedReactCliOptions['filter'],\n): ReactUsageFilter {\n if (filter === undefined) {\n return 'all'\n }\n\n if (filter === 'component' || filter === 'hook') {\n return filter\n }\n\n throw new Error(`Unknown React filter: ${filter}`)\n}\n","#!/usr/bin/env node\n\nimport { createRequire } from 'node:module'\n\nimport { main } from './app/cli.js'\n\nconst require = createRequire(import.meta.url)\nconst { version } = require('../package.json') as { version: string }\n\nmain(version)\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":["process"],"sources":["../src/commands/base.ts","../src/commands/import.ts","../src/app/react-entry-files.ts","../src/commands/react.ts","../src/app/run.ts","../src/app/cli.ts","../src/cli.ts"],"sourcesContent":["import type { CliOptions } from '../app/args.js'\nimport type { AnalyzeOptions } from '../types/analyze-options.js'\n\nexport abstract class BaseCommand<\n TGraph,\n TOptions extends CliOptions = CliOptions,\n> {\n constructor(protected readonly options: TOptions) {}\n\n run(): void {\n const graph = this.analyze()\n\n if (this.isJsonMode()) {\n process.stdout.write(\n `${JSON.stringify(this.serialize(graph), null, 2)}\\n`,\n )\n return\n }\n\n process.stdout.write(`${this.render(graph)}\\n`)\n }\n\n protected isJsonMode(): boolean {\n return this.options.json\n }\n\n protected getAnalyzeOptions(): AnalyzeOptions {\n return {\n ...(this.options.cwd === undefined ? {} : { cwd: this.options.cwd }),\n ...(this.options.configPath === undefined\n ? {}\n : { configPath: this.options.configPath }),\n expandWorkspaces: this.options.expandWorkspaces,\n projectOnly: this.options.projectOnly,\n }\n }\n\n protected abstract analyze(): TGraph\n protected abstract serialize(graph: TGraph): object\n protected abstract render(graph: TGraph): string\n}\n","import { analyzeDependencies } from '../analyzers/import/index.js'\nimport type { ImportCliOptions } from '../app/args.js'\nimport { printDependencyTree } from '../output/ascii/import.js'\nimport { graphToSerializableTree } from '../output/json/import.js'\nimport type { DependencyGraph } from '../types/dependency-graph.js'\nimport { BaseCommand } from './base.js'\n\nexport class ImportCommand extends BaseCommand<\n DependencyGraph,\n ImportCliOptions\n> {\n protected analyze(): DependencyGraph {\n return analyzeDependencies(this.options.entryFile, this.getAnalyzeOptions())\n }\n\n protected serialize(graph: DependencyGraph): object {\n return graphToSerializableTree(graph, {\n omitUnused: this.options.omitUnused,\n })\n }\n\n protected render(graph: DependencyGraph): string {\n return printDependencyTree(graph, {\n cwd: this.options.cwd ?? graph.cwd,\n includeExternals: this.options.includeExternals,\n omitUnused: this.options.omitUnused,\n })\n }\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport { isSourceCodeFile } from '../utils/is-source-code-file.js'\nimport type { ReactCliOptions } from './args.js'\n\nconst NEXT_JS_ENTRY_ROOTS = [\n 'pages',\n 'app',\n path.join('src', 'pages'),\n path.join('src', 'app'),\n]\n\nexport function resolveReactEntryFiles(options: ReactCliOptions): string[] {\n if (options.entryFile !== undefined) {\n return [options.entryFile]\n }\n\n if (!options.nextjs) {\n throw new Error(\n 'Missing React entry file. Use `foresthouse react <entry-file>` or `foresthouse react --nextjs`.',\n )\n }\n\n return discoverNextJsPageEntries(options.cwd)\n}\n\nexport function discoverNextJsPageEntries(cwd?: string): string[] {\n const effectiveCwd = path.resolve(cwd ?? process.cwd())\n const entries = new Set<string>()\n\n collectPagesRouterEntries(\n path.join(effectiveCwd, 'pages'),\n effectiveCwd,\n entries,\n )\n collectAppRouterEntries(path.join(effectiveCwd, 'app'), effectiveCwd, entries)\n collectPagesRouterEntries(\n path.join(effectiveCwd, 'src', 'pages'),\n effectiveCwd,\n entries,\n )\n collectAppRouterEntries(\n path.join(effectiveCwd, 'src', 'app'),\n effectiveCwd,\n entries,\n )\n\n const resolvedEntries = [...entries].sort()\n if (resolvedEntries.length > 0) {\n return resolvedEntries\n }\n\n const searchedRoots = NEXT_JS_ENTRY_ROOTS.map((root) => `\\`${root}/\\``).join(\n ', ',\n )\n throw new Error(\n `No Next.js page entries found. Searched ${searchedRoots} relative to ${effectiveCwd}.`,\n )\n}\n\nfunction collectPagesRouterEntries(\n rootDirectory: string,\n cwd: string,\n entries: Set<string>,\n): void {\n walkDirectory(rootDirectory, (filePath, relativePath) => {\n if (!isSourceCodeFile(filePath) || filePath.endsWith('.d.ts')) {\n return\n }\n\n const segments = relativePath.split(path.sep)\n if (segments[0] === 'api') {\n return\n }\n\n const baseName = path.basename(filePath)\n if (baseName.startsWith('_')) {\n return\n }\n\n entries.add(path.relative(cwd, filePath))\n })\n}\n\nfunction collectAppRouterEntries(\n rootDirectory: string,\n cwd: string,\n entries: Set<string>,\n): void {\n walkDirectory(rootDirectory, (filePath) => {\n if (!isSourceCodeFile(filePath) || filePath.endsWith('.d.ts')) {\n return\n }\n\n const extension = path.extname(filePath)\n const baseName = path.basename(filePath, extension)\n if (baseName !== 'page') {\n return\n }\n\n entries.add(path.relative(cwd, filePath))\n })\n}\n\nfunction walkDirectory(\n rootDirectory: string,\n visitor: (filePath: string, relativePath: string) => void,\n): void {\n if (!fs.existsSync(rootDirectory)) {\n return\n }\n\n const stack = [rootDirectory]\n\n while (stack.length > 0) {\n const directory = stack.pop()\n if (directory === undefined) {\n continue\n }\n\n const directoryEntries = fs\n .readdirSync(directory, { withFileTypes: true })\n .sort((left, right) => left.name.localeCompare(right.name))\n\n for (let index = directoryEntries.length - 1; index >= 0; index -= 1) {\n const entry = directoryEntries[index]\n if (entry === undefined) {\n continue\n }\n\n const entryPath = path.join(directory, entry.name)\n if (entry.isDirectory()) {\n stack.push(entryPath)\n continue\n }\n\n if (!entry.isFile()) {\n continue\n }\n\n visitor(entryPath, path.relative(rootDirectory, entryPath))\n }\n }\n}\n","import { analyzeReactUsage } from '../analyzers/react/index.js'\nimport type { ReactCliOptions } from '../app/args.js'\nimport { resolveReactEntryFiles } from '../app/react-entry-files.js'\nimport { printReactUsageTree } from '../output/ascii/react.js'\nimport { graphToSerializableReactTree } from '../output/json/react.js'\nimport type { AnalyzeOptions } from '../types/analyze-options.js'\nimport type { ReactUsageFilter } from '../types/react-usage-filter.js'\nimport type { ReactUsageGraph } from '../types/react-usage-graph.js'\nimport { BaseCommand } from './base.js'\n\nexport class ReactCommand extends BaseCommand<\n ReactUsageGraph,\n ReactCliOptions\n> {\n protected analyze(): ReactUsageGraph {\n return analyzeReactUsage(\n resolveReactEntryFiles(this.options),\n this.getAnalyzeOptions(),\n )\n }\n\n protected serialize(graph: ReactUsageGraph): object {\n return graphToSerializableReactTree(graph, {\n filter: this.getFilter(),\n })\n }\n\n protected render(graph: ReactUsageGraph): string {\n return printReactUsageTree(graph, {\n cwd: this.options.cwd ?? graph.cwd,\n filter: this.getFilter(),\n })\n }\n\n protected getAnalyzeOptions(): AnalyzeOptions {\n return {\n ...super.getAnalyzeOptions(),\n includeBuiltins: this.options.includeBuiltins,\n }\n }\n\n private getFilter(): ReactUsageFilter {\n return this.options.filter\n }\n}\n","import { ImportCommand } from '../commands/import.js'\nimport { ReactCommand } from '../commands/react.js'\nimport type { CliOptions } from './args.js'\n\nexport function runCli(options: CliOptions): void {\n new CliApplication(options).run()\n}\n\nclass CliApplication {\n constructor(private readonly options: CliOptions) {}\n\n run(): void {\n this.createCommand().run()\n }\n\n private createCommand(): {\n run(): void\n } {\n if (this.options.command === 'react') {\n return new ReactCommand(this.options)\n }\n\n return new ImportCommand(this.options)\n }\n}\n","import process from 'node:process'\n\nimport { cac } from 'cac'\n\nimport type { ReactUsageFilter } from '../types/react-usage-filter.js'\nimport type { ImportCliOptions, ReactCliOptions } from './args.js'\nimport { runCli } from './run.js'\n\nexport function main(version: string, argv = process.argv.slice(2)): void {\n new CliMain(version, argv).run()\n}\n\nclass CliMain {\n constructor(\n private readonly version: string,\n private readonly argv: string[],\n ) {}\n\n run(): void {\n try {\n const cli = this.createCli()\n\n if (this.argv.length === 0) {\n cli.outputHelp()\n return\n }\n\n this.validateCommandSyntax()\n cli.parse(this.toProcessArgv())\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Unknown error occurred.'\n process.stderr.write(`foresthouse: ${message}\\n`)\n process.exitCode = 1\n }\n }\n\n private createCli() {\n const cli = cac('foresthouse')\n\n cli\n .command(\n 'import [entry-file]',\n 'Analyze an entry file and print its dependency tree.',\n )\n .usage('import <entry-file> [options]')\n .option('--entry <path>', 'Entry file to analyze.')\n .option('--cwd <path>', 'Working directory used for relative paths.')\n .option(\n '--config <path>',\n 'Explicit tsconfig.json or jsconfig.json path.',\n )\n .option(\n '--include-externals',\n 'Include packages and Node built-ins in the tree.',\n )\n .option(\n '--no-workspaces',\n 'Do not expand sibling workspace packages into source subtrees.',\n )\n .option(\n '--project-only',\n 'Restrict traversal to the active tsconfig.json or jsconfig.json project.',\n )\n .option('--no-unused', 'Omit imports that are never referenced.')\n .option('--json', 'Print the dependency tree as JSON.')\n .action(\n (entryFile: string | undefined, rawOptions: ParsedImportCliOptions) => {\n runCli(normalizeImportCliOptions(entryFile, rawOptions))\n },\n )\n\n cli\n .command('react [entry-file]', 'Analyze React usage from an entry file.')\n .usage('react [entry-file] [options]')\n .option('--cwd <path>', 'Working directory used for relative paths.')\n .option(\n '--config <path>',\n 'Explicit tsconfig.json or jsconfig.json path.',\n )\n .option(\n '--nextjs',\n 'Infer Next.js page entries from app/ and pages/ when no entry is provided.',\n )\n .option(\n '--filter <mode>',\n 'Limit output to `component`, `hook`, or `builtin` usages.',\n )\n .option('--builtin', 'Include built-in HTML nodes in the React tree.')\n .option(\n '--no-workspaces',\n 'Do not expand sibling workspace packages into source subtrees.',\n )\n .option(\n '--project-only',\n 'Restrict traversal to the active tsconfig.json or jsconfig.json project.',\n )\n .option('--json', 'Print the React usage tree as JSON.')\n .action(\n (entryFile: string | undefined, rawOptions: ParsedReactCliOptions) => {\n runCli(normalizeReactCliOptions(entryFile, rawOptions))\n },\n )\n\n cli.help()\n cli.version(this.version)\n\n return cli\n }\n\n private validateCommandSyntax(): void {\n if (this.argv.length === 0) {\n return\n }\n\n const firstArgument = this.argv[0]\n\n if (firstArgument === undefined) {\n return\n }\n\n if (firstArgument === '--react' || firstArgument.startsWith('--react=')) {\n throw new Error('Unknown option `--react`')\n }\n\n if (firstArgument.startsWith('-')) {\n return\n }\n\n if (firstArgument === 'import' || firstArgument === 'react') {\n return\n }\n\n throw new Error(`Unknown command \\`${firstArgument}\\``)\n }\n\n private toProcessArgv(): string[] {\n return ['node', 'foresthouse', ...this.argv]\n }\n}\n\ninterface ParsedBaseCliOptions {\n readonly cwd?: string\n readonly config?: string\n readonly workspaces?: boolean\n readonly projectOnly?: boolean\n readonly json?: boolean\n}\n\ninterface ParsedImportCliOptions extends ParsedBaseCliOptions {\n readonly entry?: string\n readonly includeExternals?: boolean\n readonly unused?: boolean\n}\n\ninterface ParsedReactCliOptions extends ParsedBaseCliOptions {\n readonly filter?: string\n readonly nextjs?: boolean\n readonly builtin?: boolean\n}\n\nfunction normalizeImportCliOptions(\n entryFile: string | undefined,\n options: ParsedImportCliOptions,\n): ImportCliOptions {\n return {\n command: 'import',\n entryFile: resolveImportEntryFile(entryFile, options.entry),\n cwd: options.cwd,\n configPath: options.config,\n expandWorkspaces: options.workspaces !== false,\n projectOnly: options.projectOnly === true,\n includeExternals: options.includeExternals === true,\n omitUnused: options.unused === false,\n json: options.json === true,\n }\n}\n\nfunction normalizeReactCliOptions(\n entryFile: string | undefined,\n options: ParsedReactCliOptions,\n): ReactCliOptions {\n return {\n command: 'react',\n entryFile: resolveReactEntryFile(entryFile, options.nextjs),\n cwd: options.cwd,\n configPath: options.config,\n expandWorkspaces: options.workspaces !== false,\n projectOnly: options.projectOnly === true,\n json: options.json === true,\n filter: normalizeReactFilter(options.filter),\n nextjs: options.nextjs === true,\n includeBuiltins: options.builtin === true,\n }\n}\n\nfunction resolveImportEntryFile(\n positionalEntryFile: string | undefined,\n optionEntryFile: string | undefined,\n): string {\n if (\n positionalEntryFile !== undefined &&\n optionEntryFile !== undefined &&\n positionalEntryFile !== optionEntryFile\n ) {\n throw new Error(\n 'Provide the import entry only once, either as `foresthouse import <entry-file>` or `foresthouse import --entry <path>`.',\n )\n }\n\n const resolvedEntryFile = positionalEntryFile ?? optionEntryFile\n\n if (resolvedEntryFile === undefined) {\n throw new Error(\n 'Missing import entry file. Use `foresthouse import <entry-file>` or `foresthouse import --entry <path>`.',\n )\n }\n\n return resolvedEntryFile\n}\n\nfunction normalizeReactFilter(\n filter: ParsedReactCliOptions['filter'],\n): ReactUsageFilter {\n if (filter === undefined) {\n return 'all'\n }\n\n if (filter === 'component' || filter === 'hook' || filter === 'builtin') {\n return filter\n }\n\n throw new Error(`Unknown React filter: ${filter}`)\n}\n\nfunction resolveReactEntryFile(\n entryFile: string | undefined,\n nextjs: boolean | undefined,\n): string | undefined {\n if (entryFile !== undefined) {\n return entryFile\n }\n\n if (nextjs === true) {\n return undefined\n }\n\n throw new Error(\n 'Missing React entry file. Use `foresthouse react <entry-file>` or `foresthouse react --nextjs`.',\n )\n}\n","#!/usr/bin/env node\n\nimport { createRequire } from 'node:module'\n\nimport { main } from './app/cli.js'\n\nconst require = createRequire(import.meta.url)\nconst { version } = require('../package.json') as { version: string }\n\nmain(version)\n"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -4,6 +4,7 @@ interface AnalyzeOptions {
|
|
|
4
4
|
readonly configPath?: string;
|
|
5
5
|
readonly expandWorkspaces?: boolean;
|
|
6
6
|
readonly projectOnly?: boolean;
|
|
7
|
+
readonly includeBuiltins?: boolean;
|
|
7
8
|
}
|
|
8
9
|
//#endregion
|
|
9
10
|
//#region src/types/dependency-kind.d.ts
|
|
@@ -55,7 +56,7 @@ interface ReactUsageEntry {
|
|
|
55
56
|
}
|
|
56
57
|
//#endregion
|
|
57
58
|
//#region src/types/react-symbol-kind.d.ts
|
|
58
|
-
type ReactSymbolKind = 'component' | 'hook';
|
|
59
|
+
type ReactSymbolKind = 'component' | 'hook' | 'builtin';
|
|
59
60
|
//#endregion
|
|
60
61
|
//#region src/types/react-usage-edge-kind.d.ts
|
|
61
62
|
type ReactUsageEdgeKind = 'render' | 'hook-call';
|
|
@@ -81,12 +82,13 @@ interface ReactUsageNode {
|
|
|
81
82
|
interface ReactUsageGraph {
|
|
82
83
|
readonly cwd: string;
|
|
83
84
|
readonly entryId: string;
|
|
85
|
+
readonly entryIds: readonly string[];
|
|
84
86
|
readonly nodes: ReadonlyMap<string, ReactUsageNode>;
|
|
85
87
|
readonly entries: readonly ReactUsageEntry[];
|
|
86
88
|
}
|
|
87
89
|
//#endregion
|
|
88
90
|
//#region src/analyzers/react/index.d.ts
|
|
89
|
-
declare function analyzeReactUsage(entryFile: string, options?: AnalyzeOptions): ReactUsageGraph;
|
|
91
|
+
declare function analyzeReactUsage(entryFile: string | readonly string[], options?: AnalyzeOptions): ReactUsageGraph;
|
|
90
92
|
//#endregion
|
|
91
93
|
//#region src/types/react-usage-filter.d.ts
|
|
92
94
|
type ReactUsageFilter = 'all' | ReactSymbolKind;
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/analyze-options.ts","../src/types/dependency-kind.ts","../src/types/reference-kind.ts","../src/types/dependency-edge.ts","../src/types/source-module-node.ts","../src/types/dependency-graph.ts","../src/analyzers/import/index.ts","../src/types/react-usage-location.ts","../src/types/react-usage-entry.ts","../src/types/react-symbol-kind.ts","../src/types/react-usage-edge-kind.ts","../src/types/react-usage-edge.ts","../src/types/react-usage-node.ts","../src/types/react-usage-graph.ts","../src/analyzers/react/index.ts","../src/types/react-usage-filter.ts","../src/analyzers/react/queries.ts","../src/types/color-mode.ts","../src/types/print-tree-options.ts","../src/output/ascii/import.ts","../src/types/print-react-tree-options.ts","../src/output/ascii/react.ts","../src/output/json/import.ts","../src/output/json/react.ts"],"mappings":";UAAiB,cAAA;EAAA,SACN,GAAA;EAAA,SACA,UAAA;EAAA,SACA,gBAAA;EAAA,SACA,WAAA;AAAA;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/analyze-options.ts","../src/types/dependency-kind.ts","../src/types/reference-kind.ts","../src/types/dependency-edge.ts","../src/types/source-module-node.ts","../src/types/dependency-graph.ts","../src/analyzers/import/index.ts","../src/types/react-usage-location.ts","../src/types/react-usage-entry.ts","../src/types/react-symbol-kind.ts","../src/types/react-usage-edge-kind.ts","../src/types/react-usage-edge.ts","../src/types/react-usage-node.ts","../src/types/react-usage-graph.ts","../src/analyzers/react/index.ts","../src/types/react-usage-filter.ts","../src/analyzers/react/queries.ts","../src/types/color-mode.ts","../src/types/print-tree-options.ts","../src/output/ascii/import.ts","../src/types/print-react-tree-options.ts","../src/output/ascii/react.ts","../src/output/json/import.ts","../src/output/json/react.ts"],"mappings":";UAAiB,cAAA;EAAA,SACN,GAAA;EAAA,SACA,UAAA;EAAA,SACA,gBAAA;EAAA,SACA,WAAA;EAAA,SACA,eAAA;AAAA;;;KCLC,cAAA;;;KCAA,aAAA;;;UCGK,cAAA;EAAA,SACN,SAAA;EAAA,SACA,aAAA,EAAe,aAAA;EAAA,SACf,UAAA;EAAA,SACA,MAAA;EAAA,SACA,IAAA,EAAM,cAAA;EAAA,SACN,MAAA;EAAA,SACA,QAAA;AAAA;;;UCRM,gBAAA;EAAA,SACN,EAAA;EAAA,SACA,YAAA,WAAuB,cAAA;AAAA;;;UCFjB,eAAA;EAAA,SACN,GAAA;EAAA,SACA,OAAA;EAAA,SACA,KAAA,EAAO,WAAA,SAAoB,gBAAA;EAAA,SAC3B,UAAA;AAAA;;;iBCGK,mBAAA,CACd,SAAA,UACA,OAAA,GAAS,cAAA,GACR,eAAA;;;UCZc,kBAAA;EAAA,SACN,QAAA;EAAA,SACA,IAAA;EAAA,SACA,MAAA;AAAA;;;UCDM,eAAA;EAAA,SACN,MAAA;EAAA,SACA,aAAA;EAAA,SACA,QAAA,EAAU,kBAAA;AAAA;;;KCLT,eAAA;;;KCAA,kBAAA;;;UCEK,cAAA;EAAA,SACN,IAAA,EAAM,kBAAA;EAAA,SACN,MAAA;EAAA,SACA,aAAA;AAAA;;;UCFM,cAAA;EAAA,SACN,EAAA;EAAA,SACA,IAAA;EAAA,SACA,IAAA,EAAM,eAAA;EAAA,SACN,QAAA;EAAA,SACA,WAAA;EAAA,SACA,MAAA,WAAiB,cAAA;AAAA;;;UCNX,eAAA;EAAA,SACN,GAAA;EAAA,SACA,OAAA;EAAA,SACA,QAAA;EAAA,SACA,KAAA,EAAO,WAAA,SAAoB,cAAA;EAAA,SAC3B,OAAA,WAAkB,eAAA;AAAA;;;iBCgBb,iBAAA,CACd,SAAA,8BACA,OAAA,GAAS,cAAA,GACR,eAAA;;;KCzBS,gBAAA,WAA2B,eAAA;;;iBCKvB,oBAAA,CACd,KAAA,EAAO,eAAA,EACP,MAAA,GAAQ,gBAAA,GACP,eAAA;AAAA,iBAOa,kBAAA,CACd,KAAA,EAAO,eAAA,EACP,MAAA,GAAQ,gBAAA;AAAA,iBAsCM,iBAAA,CACd,IAAA,EAAM,cAAA,EACN,KAAA,EAAO,eAAA,EACP,MAAA,GAAQ,gBAAA,GACP,cAAA;;;KC7DS,SAAA;;;UCEK,gBAAA;EAAA,SACN,GAAA;EAAA,SACA,gBAAA;EAAA,SACA,UAAA;EAAA,SACA,KAAA,GAAQ,SAAA;AAAA;;;iBCAH,mBAAA,CACd,KAAA,EAAO,eAAA,EACP,OAAA,GAAS,gBAAA;;;UCLM,qBAAA;EAAA,SACN,GAAA;EAAA,SACA,MAAA,GAAS,gBAAA;EAAA,SACT,KAAA,GAAQ,SAAA;AAAA;;;iBCaH,mBAAA,CACd,KAAA,EAAO,eAAA,EACP,OAAA,GAAS,qBAAA;;;iBCjBK,uBAAA,CACd,KAAA,EAAO,eAAA,EACP,OAAA;EAAA,SACW,UAAA;AAAA;;;iBC8BG,4BAAA,CACd,KAAA,EAAO,eAAA,EACP,OAAA;EAAA,SACW,MAAA,GAAS,gBAAA;AAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as getFilteredUsages, c as analyzeReactUsage, i as printDependencyTree, l as analyzeDependencies, n as graphToSerializableTree, o as getReactUsageEntries, r as printReactUsageTree, s as getReactUsageRoots, t as graphToSerializableReactTree } from "./react-
|
|
1
|
+
import { a as getFilteredUsages, c as analyzeReactUsage, i as printDependencyTree, l as analyzeDependencies, n as graphToSerializableTree, o as getReactUsageEntries, r as printReactUsageTree, s as getReactUsageRoots, t as graphToSerializableReactTree } from "./react-B0frvTuH.mjs";
|
|
2
2
|
export { analyzeDependencies, analyzeReactUsage, getFilteredUsages, getReactUsageEntries, getReactUsageRoots, graphToSerializableReactTree, graphToSerializableTree, printDependencyTree, printReactUsageTree };
|
|
@@ -617,6 +617,10 @@ function getComponentReferenceName(node) {
|
|
|
617
617
|
const name = getJsxName(node.openingElement.name);
|
|
618
618
|
return name !== void 0 && isComponentName(name) ? name : void 0;
|
|
619
619
|
}
|
|
620
|
+
function getBuiltinReferenceName(node) {
|
|
621
|
+
const name = getJsxName(node.openingElement.name);
|
|
622
|
+
return name !== void 0 && isIntrinsicElementName(name) ? name : void 0;
|
|
623
|
+
}
|
|
620
624
|
function getHookReferenceName(node) {
|
|
621
625
|
const calleeName = getIdentifierName(node.callee);
|
|
622
626
|
return calleeName !== void 0 && isHookName(calleeName) ? calleeName : void 0;
|
|
@@ -633,6 +637,9 @@ function isHookName(name) {
|
|
|
633
637
|
function isComponentName(name) {
|
|
634
638
|
return /^[A-Z]/.test(name);
|
|
635
639
|
}
|
|
640
|
+
function isIntrinsicElementName(name) {
|
|
641
|
+
return /^[a-z]/.test(name);
|
|
642
|
+
}
|
|
636
643
|
function returnsReactElement(declaration) {
|
|
637
644
|
if (declaration.type === "ArrowFunctionExpression" && declaration.expression) return containsReactElementLikeExpression(declaration.body);
|
|
638
645
|
const body = declaration.body;
|
|
@@ -668,17 +675,17 @@ function unwrapExpression(expression) {
|
|
|
668
675
|
}
|
|
669
676
|
//#endregion
|
|
670
677
|
//#region src/analyzers/react/entries.ts
|
|
671
|
-
function collectEntryUsages(program, filePath, sourceText) {
|
|
678
|
+
function collectEntryUsages(program, filePath, sourceText, includeBuiltins) {
|
|
672
679
|
const entries = /* @__PURE__ */ new Map();
|
|
673
680
|
program.body.forEach((statement) => {
|
|
674
|
-
collectStatementEntryUsages(statement, filePath, sourceText, entries);
|
|
681
|
+
collectStatementEntryUsages(statement, filePath, sourceText, entries, includeBuiltins);
|
|
675
682
|
});
|
|
676
683
|
return [...entries.values()].sort(comparePendingReactUsageEntries);
|
|
677
684
|
}
|
|
678
|
-
function collectStatementEntryUsages(statement, filePath, sourceText, entries) {
|
|
679
|
-
collectNodeEntryUsages(statement, filePath, sourceText, entries, false);
|
|
685
|
+
function collectStatementEntryUsages(statement, filePath, sourceText, entries, includeBuiltins) {
|
|
686
|
+
collectNodeEntryUsages(statement, filePath, sourceText, entries, includeBuiltins, false);
|
|
680
687
|
}
|
|
681
|
-
function collectNodeEntryUsages(node, filePath, sourceText, entries, hasComponentAncestor) {
|
|
688
|
+
function collectNodeEntryUsages(node, filePath, sourceText, entries, includeBuiltins, hasComponentAncestor) {
|
|
682
689
|
if (FUNCTION_NODE_TYPES.has(node.type)) return;
|
|
683
690
|
let nextHasComponentAncestor = hasComponentAncestor;
|
|
684
691
|
if (node.type === "JSXElement") {
|
|
@@ -686,6 +693,12 @@ function collectNodeEntryUsages(node, filePath, sourceText, entries, hasComponen
|
|
|
686
693
|
if (referenceName !== void 0) {
|
|
687
694
|
if (!hasComponentAncestor) addPendingReactUsageEntry(entries, referenceName, "component", createReactUsageLocation(filePath, sourceText, node.start));
|
|
688
695
|
nextHasComponentAncestor = true;
|
|
696
|
+
} else if (includeBuiltins) {
|
|
697
|
+
const builtinName = getBuiltinReferenceName(node);
|
|
698
|
+
if (builtinName !== void 0) {
|
|
699
|
+
if (!hasComponentAncestor) addPendingReactUsageEntry(entries, builtinName, "builtin", createReactUsageLocation(filePath, sourceText, node.start));
|
|
700
|
+
nextHasComponentAncestor = true;
|
|
701
|
+
}
|
|
689
702
|
}
|
|
690
703
|
} else if (node.type === "CallExpression") {
|
|
691
704
|
const hookReference = getHookReferenceName(node);
|
|
@@ -700,18 +713,18 @@ function collectNodeEntryUsages(node, filePath, sourceText, entries, hasComponen
|
|
|
700
713
|
if (keys === void 0) return;
|
|
701
714
|
keys.forEach((key) => {
|
|
702
715
|
const value = node[key];
|
|
703
|
-
collectEntryUsageChild(value, filePath, sourceText, entries, nextHasComponentAncestor);
|
|
716
|
+
collectEntryUsageChild(value, filePath, sourceText, entries, includeBuiltins, nextHasComponentAncestor);
|
|
704
717
|
});
|
|
705
718
|
}
|
|
706
|
-
function collectEntryUsageChild(value, filePath, sourceText, entries, hasComponentAncestor) {
|
|
719
|
+
function collectEntryUsageChild(value, filePath, sourceText, entries, includeBuiltins, hasComponentAncestor) {
|
|
707
720
|
if (Array.isArray(value)) {
|
|
708
721
|
value.forEach((entry) => {
|
|
709
|
-
collectEntryUsageChild(entry, filePath, sourceText, entries, hasComponentAncestor);
|
|
722
|
+
collectEntryUsageChild(entry, filePath, sourceText, entries, includeBuiltins, hasComponentAncestor);
|
|
710
723
|
});
|
|
711
724
|
return;
|
|
712
725
|
}
|
|
713
726
|
if (!isNode(value)) return;
|
|
714
|
-
collectNodeEntryUsages(value, filePath, sourceText, entries, hasComponentAncestor);
|
|
727
|
+
collectNodeEntryUsages(value, filePath, sourceText, entries, includeBuiltins, hasComponentAncestor);
|
|
715
728
|
}
|
|
716
729
|
function addPendingReactUsageEntry(entries, referenceName, kind, location) {
|
|
717
730
|
const key = `${location.filePath}:${location.line}:${location.column}:${kind}:${referenceName}`;
|
|
@@ -800,18 +813,23 @@ function createPendingSymbol(filePath, name, kind, declaration) {
|
|
|
800
813
|
declaration,
|
|
801
814
|
exportNames: /* @__PURE__ */ new Set(),
|
|
802
815
|
componentReferences: /* @__PURE__ */ new Set(),
|
|
803
|
-
hookReferences: /* @__PURE__ */ new Set()
|
|
816
|
+
hookReferences: /* @__PURE__ */ new Set(),
|
|
817
|
+
builtinReferences: /* @__PURE__ */ new Set()
|
|
804
818
|
};
|
|
805
819
|
}
|
|
806
820
|
//#endregion
|
|
807
821
|
//#region src/analyzers/react/usage.ts
|
|
808
|
-
function analyzeSymbolUsages(symbol) {
|
|
822
|
+
function analyzeSymbolUsages(symbol, includeBuiltins) {
|
|
809
823
|
const root = symbol.declaration.type === "ArrowFunctionExpression" ? symbol.declaration.body : symbol.declaration.body;
|
|
810
824
|
if (root === null) return;
|
|
811
825
|
walkReactUsageTree(root, (node) => {
|
|
812
826
|
if (node.type === "JSXElement") {
|
|
813
827
|
const name = getComponentReferenceName(node);
|
|
814
828
|
if (name !== void 0) symbol.componentReferences.add(name);
|
|
829
|
+
if (includeBuiltins) {
|
|
830
|
+
const builtinName = getBuiltinReferenceName(node);
|
|
831
|
+
if (builtinName !== void 0) symbol.builtinReferences.add(builtinName);
|
|
832
|
+
}
|
|
815
833
|
return;
|
|
816
834
|
}
|
|
817
835
|
if (node.type === "CallExpression") {
|
|
@@ -824,7 +842,7 @@ function analyzeSymbolUsages(symbol) {
|
|
|
824
842
|
}
|
|
825
843
|
//#endregion
|
|
826
844
|
//#region src/analyzers/react/file.ts
|
|
827
|
-
function analyzeReactFile(program, filePath, sourceText, includeNestedRenderEntries, sourceDependencies) {
|
|
845
|
+
function analyzeReactFile(program, filePath, sourceText, includeNestedRenderEntries, sourceDependencies, includeBuiltins) {
|
|
828
846
|
const symbolsByName = /* @__PURE__ */ new Map();
|
|
829
847
|
program.body.forEach((statement) => {
|
|
830
848
|
collectTopLevelReactSymbols(statement, filePath, symbolsByName);
|
|
@@ -833,12 +851,12 @@ function analyzeReactFile(program, filePath, sourceText, includeNestedRenderEntr
|
|
|
833
851
|
const exportsByName = /* @__PURE__ */ new Map();
|
|
834
852
|
const reExportBindingsByName = /* @__PURE__ */ new Map();
|
|
835
853
|
const exportAllBindings = [];
|
|
836
|
-
const directEntryUsages = includeNestedRenderEntries ? collectEntryUsages(program, filePath, sourceText) : [];
|
|
854
|
+
const directEntryUsages = includeNestedRenderEntries ? collectEntryUsages(program, filePath, sourceText, includeBuiltins) : [];
|
|
837
855
|
program.body.forEach((statement) => {
|
|
838
856
|
collectImportsAndExports(statement, sourceDependencies, symbolsByName, importsByLocalName, exportsByName, reExportBindingsByName, exportAllBindings);
|
|
839
857
|
});
|
|
840
858
|
symbolsByName.forEach((symbol) => {
|
|
841
|
-
analyzeSymbolUsages(symbol);
|
|
859
|
+
analyzeSymbolUsages(symbol, includeBuiltins);
|
|
842
860
|
});
|
|
843
861
|
return {
|
|
844
862
|
filePath,
|
|
@@ -866,6 +884,7 @@ function collectComponentDeclarationEntryUsages(symbolsByName, sourceText) {
|
|
|
866
884
|
//#endregion
|
|
867
885
|
//#region src/analyzers/react/references.ts
|
|
868
886
|
function resolveReactReference(fileAnalysis, fileAnalyses, name, kind) {
|
|
887
|
+
if (kind === "builtin") return getBuiltinNodeId(name);
|
|
869
888
|
const localSymbol = fileAnalysis.symbolsByName.get(name);
|
|
870
889
|
if (localSymbol !== void 0 && localSymbol.kind === kind) return localSymbol.id;
|
|
871
890
|
const importBinding = fileAnalysis.importsByLocalName.get(name);
|
|
@@ -909,6 +928,21 @@ function addExternalHookNodes(fileAnalyses, nodes) {
|
|
|
909
928
|
if (!nodes.has(externalNode.id)) nodes.set(externalNode.id, externalNode);
|
|
910
929
|
});
|
|
911
930
|
}
|
|
931
|
+
function addBuiltinNodes(fileAnalyses, nodes) {
|
|
932
|
+
for (const fileAnalysis of fileAnalyses.values()) {
|
|
933
|
+
fileAnalysis.entryUsages.forEach((entry) => {
|
|
934
|
+
if (entry.kind !== "builtin") return;
|
|
935
|
+
const builtinNode = createBuiltinNode(entry.referenceName);
|
|
936
|
+
if (!nodes.has(builtinNode.id)) nodes.set(builtinNode.id, builtinNode);
|
|
937
|
+
});
|
|
938
|
+
fileAnalysis.symbolsById.forEach((symbol) => {
|
|
939
|
+
symbol.builtinReferences.forEach((name) => {
|
|
940
|
+
const builtinNode = createBuiltinNode(name);
|
|
941
|
+
if (!nodes.has(builtinNode.id)) nodes.set(builtinNode.id, builtinNode);
|
|
942
|
+
});
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
}
|
|
912
946
|
function createExternalHookNode(binding, localName) {
|
|
913
947
|
const name = getExternalHookName(binding, localName);
|
|
914
948
|
return {
|
|
@@ -920,9 +954,22 @@ function createExternalHookNode(binding, localName) {
|
|
|
920
954
|
usages: []
|
|
921
955
|
};
|
|
922
956
|
}
|
|
957
|
+
function createBuiltinNode(name) {
|
|
958
|
+
return {
|
|
959
|
+
id: getBuiltinNodeId(name),
|
|
960
|
+
name,
|
|
961
|
+
kind: "builtin",
|
|
962
|
+
filePath: "html",
|
|
963
|
+
exportNames: [],
|
|
964
|
+
usages: []
|
|
965
|
+
};
|
|
966
|
+
}
|
|
923
967
|
function getExternalHookNodeId(binding, localName) {
|
|
924
968
|
return `external:${binding.sourceSpecifier}#hook:${getExternalHookName(binding, localName)}`;
|
|
925
969
|
}
|
|
970
|
+
function getBuiltinNodeId(name) {
|
|
971
|
+
return `builtin:${name}`;
|
|
972
|
+
}
|
|
926
973
|
function getExternalHookName(binding, localName) {
|
|
927
974
|
return binding.importedName === "default" ? localName : binding.importedName;
|
|
928
975
|
}
|
|
@@ -941,11 +988,19 @@ function compareReactNodes(left, right) {
|
|
|
941
988
|
//#endregion
|
|
942
989
|
//#region src/analyzers/react/index.ts
|
|
943
990
|
function analyzeReactUsage(entryFile, options = {}) {
|
|
944
|
-
return new ReactAnalyzer(entryFile, options).analyze();
|
|
991
|
+
return new ReactAnalyzer(normalizeEntryFiles(entryFile), options).analyze();
|
|
945
992
|
}
|
|
946
993
|
var ReactAnalyzer = class extends BaseAnalyzer {
|
|
994
|
+
constructor(entryFiles, options) {
|
|
995
|
+
const [firstEntryFile] = entryFiles;
|
|
996
|
+
if (firstEntryFile === void 0) throw new Error("At least one React entry file is required.");
|
|
997
|
+
super(firstEntryFile, options);
|
|
998
|
+
this.entryFiles = entryFiles;
|
|
999
|
+
}
|
|
947
1000
|
doAnalyze() {
|
|
948
|
-
const
|
|
1001
|
+
const dependencyGraphs = this.entryFiles.map((entryFile) => analyzeDependencies(entryFile, this.options));
|
|
1002
|
+
const dependencyGraph = mergeDependencyGraphs(dependencyGraphs);
|
|
1003
|
+
const entryIds = dependencyGraphs.map((graph) => graph.entryId);
|
|
949
1004
|
const fileAnalyses = this.collectFileAnalyses(dependencyGraph);
|
|
950
1005
|
const nodes = this.createNodes(fileAnalyses);
|
|
951
1006
|
this.attachUsages(fileAnalyses, nodes);
|
|
@@ -953,12 +1008,13 @@ var ReactAnalyzer = class extends BaseAnalyzer {
|
|
|
953
1008
|
return {
|
|
954
1009
|
cwd: dependencyGraph.cwd,
|
|
955
1010
|
entryId: dependencyGraph.entryId,
|
|
1011
|
+
entryIds,
|
|
956
1012
|
nodes,
|
|
957
1013
|
entries
|
|
958
1014
|
};
|
|
959
1015
|
}
|
|
960
1016
|
collectFileAnalyses(dependencyGraph) {
|
|
961
|
-
const reachableFiles = new Set([dependencyGraph.
|
|
1017
|
+
const reachableFiles = new Set([...dependencyGraph.entryIds, ...dependencyGraph.nodes.keys()]);
|
|
962
1018
|
const fileAnalyses = /* @__PURE__ */ new Map();
|
|
963
1019
|
for (const filePath of [...reachableFiles].sort()) {
|
|
964
1020
|
if (!isSourceCodeFile(filePath) || filePath.endsWith(".d.ts")) continue;
|
|
@@ -972,7 +1028,7 @@ var ReactAnalyzer = class extends BaseAnalyzer {
|
|
|
972
1028
|
dependencyNode?.dependencies.forEach((dependency) => {
|
|
973
1029
|
if (dependency.kind === "source") sourceDependencies.set(dependency.specifier, dependency.target);
|
|
974
1030
|
});
|
|
975
|
-
fileAnalyses.set(filePath, analyzeReactFile(parseResult.program, filePath, sourceText, filePath
|
|
1031
|
+
fileAnalyses.set(filePath, analyzeReactFile(parseResult.program, filePath, sourceText, dependencyGraph.entryIds.includes(filePath), sourceDependencies, this.options.includeBuiltins === true));
|
|
976
1032
|
}
|
|
977
1033
|
return fileAnalyses;
|
|
978
1034
|
}
|
|
@@ -987,6 +1043,7 @@ var ReactAnalyzer = class extends BaseAnalyzer {
|
|
|
987
1043
|
usages: []
|
|
988
1044
|
});
|
|
989
1045
|
addExternalHookNodes(fileAnalyses, nodes);
|
|
1046
|
+
if (this.options.includeBuiltins === true) addBuiltinNodes(fileAnalyses, nodes);
|
|
990
1047
|
return nodes;
|
|
991
1048
|
}
|
|
992
1049
|
attachUsages(fileAnalyses, nodes) {
|
|
@@ -1008,6 +1065,14 @@ var ReactAnalyzer = class extends BaseAnalyzer {
|
|
|
1008
1065
|
referenceName
|
|
1009
1066
|
});
|
|
1010
1067
|
});
|
|
1068
|
+
if (this.options.includeBuiltins === true) symbol.builtinReferences.forEach((referenceName) => {
|
|
1069
|
+
const targetId = getBuiltinNodeId(referenceName);
|
|
1070
|
+
usages.set(`render:${targetId}:${referenceName}`, {
|
|
1071
|
+
kind: "render",
|
|
1072
|
+
target: targetId,
|
|
1073
|
+
referenceName
|
|
1074
|
+
});
|
|
1075
|
+
});
|
|
1011
1076
|
const node = nodes.get(symbol.id);
|
|
1012
1077
|
if (node === void 0) continue;
|
|
1013
1078
|
const sortedUsages = [...usages.values()].sort((left, right) => compareReactNodeIds(left.target, right.target, nodes));
|
|
@@ -1032,6 +1097,27 @@ var ReactAnalyzer = class extends BaseAnalyzer {
|
|
|
1032
1097
|
return [...entriesByKey.values()].sort((left, right) => compareReactUsageEntries(left, right, nodes));
|
|
1033
1098
|
}
|
|
1034
1099
|
};
|
|
1100
|
+
function normalizeEntryFiles(entryFile) {
|
|
1101
|
+
const entryFiles = Array.isArray(entryFile) ? entryFile : [entryFile];
|
|
1102
|
+
const dedupedEntryFiles = [...new Set(entryFiles)];
|
|
1103
|
+
if (dedupedEntryFiles.length === 0) throw new Error("At least one React entry file is required.");
|
|
1104
|
+
return dedupedEntryFiles;
|
|
1105
|
+
}
|
|
1106
|
+
function mergeDependencyGraphs(graphs) {
|
|
1107
|
+
const firstGraph = graphs[0];
|
|
1108
|
+
if (firstGraph === void 0) throw new Error("At least one dependency graph is required.");
|
|
1109
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
1110
|
+
for (const graph of graphs) for (const [nodeId, node] of graph.nodes) if (!nodes.has(nodeId)) nodes.set(nodeId, node);
|
|
1111
|
+
const uniqueConfigPaths = [...new Set(graphs.map((graph) => graph.configPath))];
|
|
1112
|
+
const configPath = uniqueConfigPaths.length === 1 ? uniqueConfigPaths[0] : void 0;
|
|
1113
|
+
return {
|
|
1114
|
+
cwd: firstGraph.cwd,
|
|
1115
|
+
entryId: firstGraph.entryId,
|
|
1116
|
+
entryIds: graphs.map((graph) => graph.entryId),
|
|
1117
|
+
nodes,
|
|
1118
|
+
...configPath === void 0 ? {} : { configPath }
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1035
1121
|
//#endregion
|
|
1036
1122
|
//#region src/analyzers/react/queries.ts
|
|
1037
1123
|
function getReactUsageEntries(graph, filter = "all") {
|
|
@@ -1076,6 +1162,7 @@ function matchesReactFilter(node, filter) {
|
|
|
1076
1162
|
const ANSI_RESET = "\x1B[0m";
|
|
1077
1163
|
const ANSI_COMPONENT = "\x1B[36m";
|
|
1078
1164
|
const ANSI_HOOK = "\x1B[35m";
|
|
1165
|
+
const ANSI_BUILTIN = "\x1B[34m";
|
|
1079
1166
|
const ANSI_MUTED = "\x1B[38;5;244m";
|
|
1080
1167
|
const ANSI_UNUSED = "\x1B[38;5;214m";
|
|
1081
1168
|
function resolveColorSupport(mode = "auto", options = {}) {
|
|
@@ -1096,7 +1183,9 @@ function formatReactSymbolLabel(name, kind, enabled) {
|
|
|
1096
1183
|
return `${getReactSymbolColor(kind)}${label}${ANSI_RESET}`;
|
|
1097
1184
|
}
|
|
1098
1185
|
function formatReactSymbolName(name, kind) {
|
|
1099
|
-
|
|
1186
|
+
if (kind === "component") return `<${name} />`;
|
|
1187
|
+
if (kind === "hook") return `${name}()`;
|
|
1188
|
+
return `<${name}>`;
|
|
1100
1189
|
}
|
|
1101
1190
|
function colorizeReactLabel(text, kind, enabled) {
|
|
1102
1191
|
if (!enabled) return text;
|
|
@@ -1107,7 +1196,9 @@ function colorizeMuted(text, enabled) {
|
|
|
1107
1196
|
return `${ANSI_MUTED}${text}${ANSI_RESET}`;
|
|
1108
1197
|
}
|
|
1109
1198
|
function getReactSymbolColor(kind) {
|
|
1110
|
-
|
|
1199
|
+
if (kind === "component") return ANSI_COMPONENT;
|
|
1200
|
+
if (kind === "hook") return ANSI_HOOK;
|
|
1201
|
+
return ANSI_BUILTIN;
|
|
1111
1202
|
}
|
|
1112
1203
|
//#endregion
|
|
1113
1204
|
//#region src/utils/to-display-path.ts
|
|
@@ -1229,11 +1320,14 @@ function renderUsage(usage, graph, cwd, filter, color, visited, prefix, isLast)
|
|
|
1229
1320
|
return childLines;
|
|
1230
1321
|
}
|
|
1231
1322
|
function formatReactNodeLabel(node, cwd, color, referenceName) {
|
|
1232
|
-
return `${referenceName !== void 0 && referenceName !== node.name ? `${colorizeReactLabel(formatReactSymbolName(node.name, node.kind), node.kind, color)} ${colorizeMuted(`as ${referenceName}`, color)} ${colorizeReactLabel(`[${node.kind}]`, node.kind, color)}` : formatReactSymbolLabel(node.name, node.kind, color)} (${
|
|
1323
|
+
return `${referenceName !== void 0 && referenceName !== node.name ? `${colorizeReactLabel(formatReactSymbolName(node.name, node.kind), node.kind, color)} ${colorizeMuted(`as ${referenceName}`, color)} ${colorizeReactLabel(`[${node.kind}]`, node.kind, color)}` : formatReactSymbolLabel(node.name, node.kind, color)} (${formatReactNodeFilePath$1(node, cwd)})`;
|
|
1233
1324
|
}
|
|
1234
1325
|
function formatReactEntryLabel(entry, cwd) {
|
|
1235
1326
|
return `${toDisplayPath(entry.location.filePath, cwd)}:${entry.location.line}:${entry.location.column}`;
|
|
1236
1327
|
}
|
|
1328
|
+
function formatReactNodeFilePath$1(node, cwd) {
|
|
1329
|
+
return node.kind === "builtin" ? "html" : toDisplayPath(node.filePath, cwd);
|
|
1330
|
+
}
|
|
1237
1331
|
//#endregion
|
|
1238
1332
|
//#region src/output/json/import.ts
|
|
1239
1333
|
function graphToSerializableTree(graph, options = {}) {
|
|
@@ -1311,7 +1405,7 @@ function serializeReactUsageNode(nodeId, graph, filter, visited) {
|
|
|
1311
1405
|
id: node.id,
|
|
1312
1406
|
name: node.name,
|
|
1313
1407
|
symbolKind: "circular",
|
|
1314
|
-
filePath:
|
|
1408
|
+
filePath: formatReactNodeFilePath(node.filePath, node.kind, graph.cwd),
|
|
1315
1409
|
exportNames: node.exportNames,
|
|
1316
1410
|
usages: []
|
|
1317
1411
|
};
|
|
@@ -1321,7 +1415,7 @@ function serializeReactUsageNode(nodeId, graph, filter, visited) {
|
|
|
1321
1415
|
id: node.id,
|
|
1322
1416
|
name: node.name,
|
|
1323
1417
|
symbolKind: node.kind,
|
|
1324
|
-
filePath:
|
|
1418
|
+
filePath: formatReactNodeFilePath(node.filePath, node.kind, graph.cwd),
|
|
1325
1419
|
exportNames: node.exportNames,
|
|
1326
1420
|
usages: getFilteredUsages(node, graph, filter).map((usage) => ({
|
|
1327
1421
|
kind: usage.kind,
|
|
@@ -1341,7 +1435,10 @@ function serializeReactUsageEntry(entry, graph, filter) {
|
|
|
1341
1435
|
node: serializeReactUsageNode(entry.target, graph, filter, /* @__PURE__ */ new Set())
|
|
1342
1436
|
};
|
|
1343
1437
|
}
|
|
1438
|
+
function formatReactNodeFilePath(filePath, kind, cwd) {
|
|
1439
|
+
return kind === "builtin" ? "html" : toDisplayPath(filePath, cwd);
|
|
1440
|
+
}
|
|
1344
1441
|
//#endregion
|
|
1345
|
-
export { getFilteredUsages as a, analyzeReactUsage as c, printDependencyTree as i, analyzeDependencies as l, graphToSerializableTree as n, getReactUsageEntries as o, printReactUsageTree as r, getReactUsageRoots as s, graphToSerializableReactTree as t };
|
|
1442
|
+
export { getFilteredUsages as a, analyzeReactUsage as c, printDependencyTree as i, analyzeDependencies as l, graphToSerializableTree as n, getReactUsageEntries as o, printReactUsageTree as r, getReactUsageRoots as s, graphToSerializableReactTree as t, isSourceCodeFile as u };
|
|
1346
1443
|
|
|
1347
|
-
//# sourceMappingURL=react-
|
|
1444
|
+
//# sourceMappingURL=react-B0frvTuH.mjs.map
|