foresthouse 1.0.0-dev.14 → 1.0.0-dev.16
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 +2 -0
- package/dist/cli.mjs +73 -4
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{react-qiCwZtzi.mjs → react-GNHqx2A-.mjs} +148 -25
- package/dist/react-GNHqx2A-.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/react-qiCwZtzi.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -65,8 +65,10 @@ 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`
|
|
71
|
+
- `--nextjs`: allow omitting the explicit React entry file and discover Next.js page entries relative to `--cwd` or the current directory
|
|
70
72
|
- `--filter <component|hook|builtin>`: limit the output to a specific React symbol kind
|
|
71
73
|
- `--builtin`: include built-in HTML nodes such as `button` and `div`
|
|
72
74
|
- `--no-workspaces`: stop at sibling workspace package boundaries instead of expanding them
|
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-GNHqx2A-.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() });
|
|
@@ -117,7 +180,7 @@ var CliMain = class {
|
|
|
117
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) => {
|
|
118
181
|
runCli(normalizeImportCliOptions(entryFile, rawOptions));
|
|
119
182
|
});
|
|
120
|
-
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) => {
|
|
121
184
|
runCli(normalizeReactCliOptions(entryFile, rawOptions));
|
|
122
185
|
});
|
|
123
186
|
cli.help();
|
|
@@ -157,13 +220,14 @@ function normalizeImportCliOptions(entryFile, options) {
|
|
|
157
220
|
function normalizeReactCliOptions(entryFile, options) {
|
|
158
221
|
return {
|
|
159
222
|
command: "react",
|
|
160
|
-
entryFile,
|
|
223
|
+
entryFile: resolveReactEntryFile(entryFile, options.nextjs),
|
|
161
224
|
cwd: options.cwd,
|
|
162
225
|
configPath: options.config,
|
|
163
226
|
expandWorkspaces: options.workspaces !== false,
|
|
164
227
|
projectOnly: options.projectOnly === true,
|
|
165
228
|
json: options.json === true,
|
|
166
229
|
filter: normalizeReactFilter(options.filter),
|
|
230
|
+
nextjs: options.nextjs === true,
|
|
167
231
|
includeBuiltins: options.builtin === true
|
|
168
232
|
};
|
|
169
233
|
}
|
|
@@ -178,6 +242,11 @@ function normalizeReactFilter(filter) {
|
|
|
178
242
|
if (filter === "component" || filter === "hook" || filter === "builtin") return filter;
|
|
179
243
|
throw new Error(`Unknown React filter: ${filter}`);
|
|
180
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
|
+
}
|
|
181
250
|
//#endregion
|
|
182
251
|
//#region src/cli.ts
|
|
183
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 { 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(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 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 '--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((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 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,\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 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","#!/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
|
@@ -82,12 +82,13 @@ interface ReactUsageNode {
|
|
|
82
82
|
interface ReactUsageGraph {
|
|
83
83
|
readonly cwd: string;
|
|
84
84
|
readonly entryId: string;
|
|
85
|
+
readonly entryIds: readonly string[];
|
|
85
86
|
readonly nodes: ReadonlyMap<string, ReactUsageNode>;
|
|
86
87
|
readonly entries: readonly ReactUsageEntry[];
|
|
87
88
|
}
|
|
88
89
|
//#endregion
|
|
89
90
|
//#region src/analyzers/react/index.d.ts
|
|
90
|
-
declare function analyzeReactUsage(entryFile: string, options?: AnalyzeOptions): ReactUsageGraph;
|
|
91
|
+
declare function analyzeReactUsage(entryFile: string | readonly string[], options?: AnalyzeOptions): ReactUsageGraph;
|
|
91
92
|
//#endregion
|
|
92
93
|
//#region src/types/react-usage-filter.d.ts
|
|
93
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;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,KAAA,EAAO,WAAA,SAAoB,cAAA;EAAA,SAC3B,OAAA,WAAkB,eAAA;AAAA;;;iBCgBb,iBAAA,CACd,SAAA,
|
|
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-GNHqx2A-.mjs";
|
|
2
2
|
export { analyzeDependencies, analyzeReactUsage, getFilteredUsages, getReactUsageEntries, getReactUsageRoots, graphToSerializableReactTree, graphToSerializableTree, printDependencyTree, printReactUsageTree };
|
|
@@ -603,8 +603,8 @@ function isNode(value) {
|
|
|
603
603
|
return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
|
|
604
604
|
}
|
|
605
605
|
function classifyReactSymbol(name, declaration) {
|
|
606
|
-
if (isHookName(name)) return "hook";
|
|
607
|
-
if (isComponentName(name) && returnsReactElement(declaration)) return "component";
|
|
606
|
+
if (isHookName(name) && isFunctionLikeDeclaration(declaration)) return "hook";
|
|
607
|
+
if (isComponentName(name) && (isFunctionLikeDeclaration(declaration) && returnsReactElement(declaration) || isStyledComponentDeclaration(declaration))) return "component";
|
|
608
608
|
}
|
|
609
609
|
function containsReactElementLikeExpression(expression) {
|
|
610
610
|
let found = false;
|
|
@@ -631,6 +631,14 @@ function getCreateElementComponentReferenceName(node) {
|
|
|
631
631
|
if (firstArgument === void 0 || firstArgument.type !== "Identifier") return;
|
|
632
632
|
return isComponentName(firstArgument.name) ? firstArgument.name : void 0;
|
|
633
633
|
}
|
|
634
|
+
function getStyledComponentReferenceName(node) {
|
|
635
|
+
const reference = getStyledFactoryReference(node.type === "CallExpression" ? node.callee : node.tag);
|
|
636
|
+
return reference?.kind === "component" ? reference.name : void 0;
|
|
637
|
+
}
|
|
638
|
+
function getStyledBuiltinReferenceName(node) {
|
|
639
|
+
const reference = getStyledFactoryReference(node.type === "CallExpression" ? node.callee : node.tag);
|
|
640
|
+
return reference?.kind === "builtin" ? reference.name : void 0;
|
|
641
|
+
}
|
|
634
642
|
function isHookName(name) {
|
|
635
643
|
return /^use[A-Z0-9]/.test(name);
|
|
636
644
|
}
|
|
@@ -640,6 +648,9 @@ function isComponentName(name) {
|
|
|
640
648
|
function isIntrinsicElementName(name) {
|
|
641
649
|
return /^[a-z]/.test(name);
|
|
642
650
|
}
|
|
651
|
+
function isFunctionLikeDeclaration(declaration) {
|
|
652
|
+
return declaration.type === "ArrowFunctionExpression" || declaration.type === "FunctionDeclaration" || declaration.type === "FunctionExpression";
|
|
653
|
+
}
|
|
643
654
|
function returnsReactElement(declaration) {
|
|
644
655
|
if (declaration.type === "ArrowFunctionExpression" && declaration.expression) return containsReactElementLikeExpression(declaration.body);
|
|
645
656
|
const body = declaration.body;
|
|
@@ -651,6 +662,36 @@ function returnsReactElement(declaration) {
|
|
|
651
662
|
});
|
|
652
663
|
return found;
|
|
653
664
|
}
|
|
665
|
+
function isStyledComponentDeclaration(expression) {
|
|
666
|
+
if (expression.type === "CallExpression") return getStyledFactoryReference(expression.callee) !== void 0;
|
|
667
|
+
if (expression.type === "TaggedTemplateExpression") return getStyledFactoryReference(expression.tag) !== void 0;
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
function getStyledFactoryReference(expression) {
|
|
671
|
+
const unwrapped = unwrapExpression(expression);
|
|
672
|
+
if (unwrapped.type === "MemberExpression" && !unwrapped.computed) {
|
|
673
|
+
if (unwrapped.object.type !== "Identifier" || unwrapped.object.name !== "styled" || unwrapped.property.type !== "Identifier") return;
|
|
674
|
+
const name = unwrapped.property.name;
|
|
675
|
+
if (isIntrinsicElementName(name)) return {
|
|
676
|
+
kind: "builtin",
|
|
677
|
+
name
|
|
678
|
+
};
|
|
679
|
+
if (isComponentName(name)) return {
|
|
680
|
+
kind: "component",
|
|
681
|
+
name
|
|
682
|
+
};
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
if (unwrapped.type !== "CallExpression") return;
|
|
686
|
+
const callee = unwrapExpression(unwrapped.callee);
|
|
687
|
+
if (callee.type !== "Identifier" || callee.name !== "styled") return;
|
|
688
|
+
const [firstArgument] = unwrapped.arguments;
|
|
689
|
+
if (firstArgument?.type !== "Identifier") return;
|
|
690
|
+
return isComponentName(firstArgument.name) ? {
|
|
691
|
+
kind: "component",
|
|
692
|
+
name: firstArgument.name
|
|
693
|
+
} : void 0;
|
|
694
|
+
}
|
|
654
695
|
function isReactCreateElementCall(node) {
|
|
655
696
|
const callee = unwrapExpression(node.callee);
|
|
656
697
|
if (callee.type !== "MemberExpression" || callee.computed) return false;
|
|
@@ -780,49 +821,79 @@ function collectTopLevelReactSymbols(statement, filePath, symbolsByName) {
|
|
|
780
821
|
default: return;
|
|
781
822
|
}
|
|
782
823
|
}
|
|
824
|
+
function collectTopLevelDynamicComponentCandidates(statement, filePath, symbolsByName, dynamicComponentCandidatesByName) {
|
|
825
|
+
switch (statement.type) {
|
|
826
|
+
case "VariableDeclaration":
|
|
827
|
+
statement.declarations.forEach((declarator) => {
|
|
828
|
+
addDynamicComponentCandidate(declarator, filePath, symbolsByName, dynamicComponentCandidatesByName);
|
|
829
|
+
});
|
|
830
|
+
return;
|
|
831
|
+
case "ExportNamedDeclaration":
|
|
832
|
+
if (statement.declaration !== null) collectTopLevelDynamicComponentCandidates(statement.declaration, filePath, symbolsByName, dynamicComponentCandidatesByName);
|
|
833
|
+
return;
|
|
834
|
+
default: return;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
783
837
|
function addFunctionSymbol(declaration, filePath, symbolsByName) {
|
|
784
838
|
const name = declaration.id?.name;
|
|
785
839
|
if (name === void 0) return;
|
|
786
840
|
const kind = classifyReactSymbol(name, declaration);
|
|
787
841
|
if (kind === void 0) return;
|
|
788
|
-
symbolsByName.set(name, createPendingSymbol(filePath, name, kind, declaration));
|
|
842
|
+
symbolsByName.set(name, createPendingSymbol(filePath, name, kind, declaration.id?.start ?? declaration.start, getAnalysisRoot(declaration)));
|
|
789
843
|
}
|
|
790
844
|
function addVariableSymbol(declarator, filePath, symbolsByName) {
|
|
791
845
|
if (declarator.id.type !== "Identifier" || declarator.init === null) return;
|
|
792
|
-
if (declarator.init.type !== "ArrowFunctionExpression" && declarator.init.type !== "FunctionExpression") return;
|
|
793
846
|
const name = declarator.id.name;
|
|
794
847
|
const kind = classifyReactSymbol(name, declarator.init);
|
|
795
848
|
if (kind === void 0) return;
|
|
796
|
-
symbolsByName.set(name, createPendingSymbol(filePath, name, kind, declarator.init));
|
|
849
|
+
symbolsByName.set(name, createPendingSymbol(filePath, name, kind, declarator.init.start, getAnalysisRoot(declarator.init)));
|
|
797
850
|
}
|
|
798
851
|
function addDefaultExportSymbol(declaration, filePath, symbolsByName) {
|
|
799
852
|
if (declaration.declaration.type === "FunctionDeclaration" || declaration.declaration.type === "FunctionExpression") addFunctionSymbol(declaration.declaration, filePath, symbolsByName);
|
|
800
853
|
else if (declaration.declaration.type === "ArrowFunctionExpression") {
|
|
801
854
|
const name = "default";
|
|
802
855
|
const kind = declaration.declaration.body ? classifyReactSymbol(name, declaration.declaration) : void 0;
|
|
803
|
-
if (kind !== void 0) symbolsByName.set(name, createPendingSymbol(filePath, name, kind, declaration.declaration));
|
|
856
|
+
if (kind !== void 0) symbolsByName.set(name, createPendingSymbol(filePath, name, kind, declaration.declaration.start, getAnalysisRoot(declaration.declaration)));
|
|
804
857
|
}
|
|
805
858
|
}
|
|
806
|
-
function
|
|
859
|
+
function addDynamicComponentCandidate(declarator, filePath, symbolsByName, dynamicComponentCandidatesByName) {
|
|
860
|
+
if (declarator.id.type !== "Identifier" || declarator.init === null) return;
|
|
861
|
+
const name = declarator.id.name;
|
|
862
|
+
if (!isPotentialDynamicComponentName(name) || symbolsByName.has(name) || !isDynamicComponentInitializer(declarator.init)) return;
|
|
863
|
+
dynamicComponentCandidatesByName.set(name, createPendingSymbol(filePath, name, "component", declarator.init.start, getAnalysisRoot(declarator.init)));
|
|
864
|
+
}
|
|
865
|
+
function createPendingSymbol(filePath, name, kind, declarationOffset, analysisRoot) {
|
|
807
866
|
return {
|
|
808
867
|
id: `${filePath}#${kind}:${name}`,
|
|
809
868
|
name,
|
|
810
869
|
kind,
|
|
811
870
|
filePath,
|
|
812
|
-
declarationOffset
|
|
813
|
-
|
|
871
|
+
declarationOffset,
|
|
872
|
+
analysisRoot,
|
|
814
873
|
exportNames: /* @__PURE__ */ new Set(),
|
|
815
874
|
componentReferences: /* @__PURE__ */ new Set(),
|
|
816
875
|
hookReferences: /* @__PURE__ */ new Set(),
|
|
817
876
|
builtinReferences: /* @__PURE__ */ new Set()
|
|
818
877
|
};
|
|
819
878
|
}
|
|
879
|
+
function getAnalysisRoot(declaration) {
|
|
880
|
+
if (declaration.type === "FunctionDeclaration" || declaration.type === "FunctionExpression") {
|
|
881
|
+
if (declaration.body === null) throw new Error(`Expected React symbol "${declaration.id?.name ?? "anonymous"}" to have a body.`);
|
|
882
|
+
return declaration.body;
|
|
883
|
+
}
|
|
884
|
+
if (declaration.type === "ArrowFunctionExpression") return declaration.body;
|
|
885
|
+
return declaration;
|
|
886
|
+
}
|
|
887
|
+
function isDynamicComponentInitializer(expression) {
|
|
888
|
+
return expression.type === "CallExpression" || expression.type === "TaggedTemplateExpression";
|
|
889
|
+
}
|
|
890
|
+
function isPotentialDynamicComponentName(name) {
|
|
891
|
+
return /^[A-Z][A-Za-z0-9]*$/.test(name);
|
|
892
|
+
}
|
|
820
893
|
//#endregion
|
|
821
894
|
//#region src/analyzers/react/usage.ts
|
|
822
895
|
function analyzeSymbolUsages(symbol, includeBuiltins) {
|
|
823
|
-
|
|
824
|
-
if (root === null) return;
|
|
825
|
-
walkReactUsageTree(root, (node) => {
|
|
896
|
+
walkReactUsageTree(symbol.analysisRoot, (node) => {
|
|
826
897
|
if (node.type === "JSXElement") {
|
|
827
898
|
const name = getComponentReferenceName(node);
|
|
828
899
|
if (name !== void 0) symbol.componentReferences.add(name);
|
|
@@ -837,6 +908,20 @@ function analyzeSymbolUsages(symbol, includeBuiltins) {
|
|
|
837
908
|
if (hookReference !== void 0) symbol.hookReferences.add(hookReference);
|
|
838
909
|
const componentReference = getCreateElementComponentReferenceName(node);
|
|
839
910
|
if (componentReference !== void 0) symbol.componentReferences.add(componentReference);
|
|
911
|
+
const styledComponentReference = getStyledComponentReferenceName(node);
|
|
912
|
+
if (styledComponentReference !== void 0) symbol.componentReferences.add(styledComponentReference);
|
|
913
|
+
if (includeBuiltins) {
|
|
914
|
+
const styledBuiltinReference = getStyledBuiltinReferenceName(node);
|
|
915
|
+
if (styledBuiltinReference !== void 0) symbol.builtinReferences.add(styledBuiltinReference);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (node.type === "TaggedTemplateExpression") {
|
|
919
|
+
const styledComponentReference = getStyledComponentReferenceName(node);
|
|
920
|
+
if (styledComponentReference !== void 0) symbol.componentReferences.add(styledComponentReference);
|
|
921
|
+
if (includeBuiltins) {
|
|
922
|
+
const styledBuiltinReference = getStyledBuiltinReferenceName(node);
|
|
923
|
+
if (styledBuiltinReference !== void 0) symbol.builtinReferences.add(styledBuiltinReference);
|
|
924
|
+
}
|
|
840
925
|
}
|
|
841
926
|
});
|
|
842
927
|
}
|
|
@@ -844,20 +929,26 @@ function analyzeSymbolUsages(symbol, includeBuiltins) {
|
|
|
844
929
|
//#region src/analyzers/react/file.ts
|
|
845
930
|
function analyzeReactFile(program, filePath, sourceText, includeNestedRenderEntries, sourceDependencies, includeBuiltins) {
|
|
846
931
|
const symbolsByName = /* @__PURE__ */ new Map();
|
|
932
|
+
const dynamicComponentCandidatesByName = /* @__PURE__ */ new Map();
|
|
847
933
|
program.body.forEach((statement) => {
|
|
848
934
|
collectTopLevelReactSymbols(statement, filePath, symbolsByName);
|
|
849
935
|
});
|
|
936
|
+
program.body.forEach((statement) => {
|
|
937
|
+
collectTopLevelDynamicComponentCandidates(statement, filePath, symbolsByName, dynamicComponentCandidatesByName);
|
|
938
|
+
});
|
|
939
|
+
const allSymbolsByName = new Map([...dynamicComponentCandidatesByName, ...symbolsByName]);
|
|
850
940
|
const importsByLocalName = /* @__PURE__ */ new Map();
|
|
851
941
|
const exportsByName = /* @__PURE__ */ new Map();
|
|
852
942
|
const reExportBindingsByName = /* @__PURE__ */ new Map();
|
|
853
943
|
const exportAllBindings = [];
|
|
854
944
|
const directEntryUsages = includeNestedRenderEntries ? collectEntryUsages(program, filePath, sourceText, includeBuiltins) : [];
|
|
855
945
|
program.body.forEach((statement) => {
|
|
856
|
-
collectImportsAndExports(statement, sourceDependencies,
|
|
946
|
+
collectImportsAndExports(statement, sourceDependencies, allSymbolsByName, importsByLocalName, exportsByName, reExportBindingsByName, exportAllBindings);
|
|
857
947
|
});
|
|
858
|
-
|
|
948
|
+
allSymbolsByName.forEach((symbol) => {
|
|
859
949
|
analyzeSymbolUsages(symbol, includeBuiltins);
|
|
860
950
|
});
|
|
951
|
+
const allSymbolsById = new Map([...allSymbolsByName.values()].map((symbol) => [symbol.id, symbol]));
|
|
861
952
|
return {
|
|
862
953
|
filePath,
|
|
863
954
|
importsByLocalName,
|
|
@@ -865,6 +956,8 @@ function analyzeReactFile(program, filePath, sourceText, includeNestedRenderEntr
|
|
|
865
956
|
reExportBindingsByName,
|
|
866
957
|
exportAllBindings,
|
|
867
958
|
entryUsages: directEntryUsages.length > 0 ? directEntryUsages : includeNestedRenderEntries ? collectComponentDeclarationEntryUsages(symbolsByName, sourceText) : [],
|
|
959
|
+
allSymbolsById,
|
|
960
|
+
allSymbolsByName,
|
|
868
961
|
symbolsById: new Map([...symbolsByName.values()].map((symbol) => [symbol.id, symbol])),
|
|
869
962
|
symbolsByName
|
|
870
963
|
};
|
|
@@ -885,7 +978,7 @@ function collectComponentDeclarationEntryUsages(symbolsByName, sourceText) {
|
|
|
885
978
|
//#region src/analyzers/react/references.ts
|
|
886
979
|
function resolveReactReference(fileAnalysis, fileAnalyses, name, kind) {
|
|
887
980
|
if (kind === "builtin") return getBuiltinNodeId(name);
|
|
888
|
-
const localSymbol = fileAnalysis.
|
|
981
|
+
const localSymbol = fileAnalysis.allSymbolsByName.get(name);
|
|
889
982
|
if (localSymbol !== void 0 && localSymbol.kind === kind) return localSymbol.id;
|
|
890
983
|
const importBinding = fileAnalysis.importsByLocalName.get(name);
|
|
891
984
|
if (importBinding === void 0) return;
|
|
@@ -902,7 +995,7 @@ function resolveExportedSymbol(fileAnalysis, exportName, kind, fileAnalyses, vis
|
|
|
902
995
|
visited.add(visitKey);
|
|
903
996
|
const directTargetId = fileAnalysis.exportsByName.get(exportName);
|
|
904
997
|
if (directTargetId !== void 0) {
|
|
905
|
-
if (fileAnalysis.
|
|
998
|
+
if (fileAnalysis.allSymbolsById.get(directTargetId)?.kind === kind) return directTargetId;
|
|
906
999
|
}
|
|
907
1000
|
const reExportBinding = fileAnalysis.reExportBindingsByName.get(exportName);
|
|
908
1001
|
if (reExportBinding?.sourcePath !== void 0) {
|
|
@@ -935,7 +1028,7 @@ function addBuiltinNodes(fileAnalyses, nodes) {
|
|
|
935
1028
|
const builtinNode = createBuiltinNode(entry.referenceName);
|
|
936
1029
|
if (!nodes.has(builtinNode.id)) nodes.set(builtinNode.id, builtinNode);
|
|
937
1030
|
});
|
|
938
|
-
fileAnalysis.
|
|
1031
|
+
fileAnalysis.allSymbolsById.forEach((symbol) => {
|
|
939
1032
|
symbol.builtinReferences.forEach((name) => {
|
|
940
1033
|
const builtinNode = createBuiltinNode(name);
|
|
941
1034
|
if (!nodes.has(builtinNode.id)) nodes.set(builtinNode.id, builtinNode);
|
|
@@ -988,11 +1081,19 @@ function compareReactNodes(left, right) {
|
|
|
988
1081
|
//#endregion
|
|
989
1082
|
//#region src/analyzers/react/index.ts
|
|
990
1083
|
function analyzeReactUsage(entryFile, options = {}) {
|
|
991
|
-
return new ReactAnalyzer(entryFile, options).analyze();
|
|
1084
|
+
return new ReactAnalyzer(normalizeEntryFiles(entryFile), options).analyze();
|
|
992
1085
|
}
|
|
993
1086
|
var ReactAnalyzer = class extends BaseAnalyzer {
|
|
1087
|
+
constructor(entryFiles, options) {
|
|
1088
|
+
const [firstEntryFile] = entryFiles;
|
|
1089
|
+
if (firstEntryFile === void 0) throw new Error("At least one React entry file is required.");
|
|
1090
|
+
super(firstEntryFile, options);
|
|
1091
|
+
this.entryFiles = entryFiles;
|
|
1092
|
+
}
|
|
994
1093
|
doAnalyze() {
|
|
995
|
-
const
|
|
1094
|
+
const dependencyGraphs = this.entryFiles.map((entryFile) => analyzeDependencies(entryFile, this.options));
|
|
1095
|
+
const dependencyGraph = mergeDependencyGraphs(dependencyGraphs);
|
|
1096
|
+
const entryIds = dependencyGraphs.map((graph) => graph.entryId);
|
|
996
1097
|
const fileAnalyses = this.collectFileAnalyses(dependencyGraph);
|
|
997
1098
|
const nodes = this.createNodes(fileAnalyses);
|
|
998
1099
|
this.attachUsages(fileAnalyses, nodes);
|
|
@@ -1000,12 +1101,13 @@ var ReactAnalyzer = class extends BaseAnalyzer {
|
|
|
1000
1101
|
return {
|
|
1001
1102
|
cwd: dependencyGraph.cwd,
|
|
1002
1103
|
entryId: dependencyGraph.entryId,
|
|
1104
|
+
entryIds,
|
|
1003
1105
|
nodes,
|
|
1004
1106
|
entries
|
|
1005
1107
|
};
|
|
1006
1108
|
}
|
|
1007
1109
|
collectFileAnalyses(dependencyGraph) {
|
|
1008
|
-
const reachableFiles = new Set([dependencyGraph.
|
|
1110
|
+
const reachableFiles = new Set([...dependencyGraph.entryIds, ...dependencyGraph.nodes.keys()]);
|
|
1009
1111
|
const fileAnalyses = /* @__PURE__ */ new Map();
|
|
1010
1112
|
for (const filePath of [...reachableFiles].sort()) {
|
|
1011
1113
|
if (!isSourceCodeFile(filePath) || filePath.endsWith(".d.ts")) continue;
|
|
@@ -1019,13 +1121,13 @@ var ReactAnalyzer = class extends BaseAnalyzer {
|
|
|
1019
1121
|
dependencyNode?.dependencies.forEach((dependency) => {
|
|
1020
1122
|
if (dependency.kind === "source") sourceDependencies.set(dependency.specifier, dependency.target);
|
|
1021
1123
|
});
|
|
1022
|
-
fileAnalyses.set(filePath, analyzeReactFile(parseResult.program, filePath, sourceText,
|
|
1124
|
+
fileAnalyses.set(filePath, analyzeReactFile(parseResult.program, filePath, sourceText, dependencyGraph.entryIds.includes(filePath), sourceDependencies, this.options.includeBuiltins === true));
|
|
1023
1125
|
}
|
|
1024
1126
|
return fileAnalyses;
|
|
1025
1127
|
}
|
|
1026
1128
|
createNodes(fileAnalyses) {
|
|
1027
1129
|
const nodes = /* @__PURE__ */ new Map();
|
|
1028
|
-
for (const fileAnalysis of fileAnalyses.values()) for (const symbol of fileAnalysis.
|
|
1130
|
+
for (const fileAnalysis of fileAnalyses.values()) for (const symbol of fileAnalysis.allSymbolsById.values()) nodes.set(symbol.id, {
|
|
1029
1131
|
id: symbol.id,
|
|
1030
1132
|
name: symbol.name,
|
|
1031
1133
|
kind: symbol.kind,
|
|
@@ -1038,7 +1140,7 @@ var ReactAnalyzer = class extends BaseAnalyzer {
|
|
|
1038
1140
|
return nodes;
|
|
1039
1141
|
}
|
|
1040
1142
|
attachUsages(fileAnalyses, nodes) {
|
|
1041
|
-
for (const fileAnalysis of fileAnalyses.values()) for (const symbol of fileAnalysis.
|
|
1143
|
+
for (const fileAnalysis of fileAnalyses.values()) for (const symbol of fileAnalysis.allSymbolsById.values()) {
|
|
1042
1144
|
const usages = /* @__PURE__ */ new Map();
|
|
1043
1145
|
symbol.componentReferences.forEach((referenceName) => {
|
|
1044
1146
|
const targetId = resolveReactReference(fileAnalysis, fileAnalyses, referenceName, "component");
|
|
@@ -1088,6 +1190,27 @@ var ReactAnalyzer = class extends BaseAnalyzer {
|
|
|
1088
1190
|
return [...entriesByKey.values()].sort((left, right) => compareReactUsageEntries(left, right, nodes));
|
|
1089
1191
|
}
|
|
1090
1192
|
};
|
|
1193
|
+
function normalizeEntryFiles(entryFile) {
|
|
1194
|
+
const entryFiles = Array.isArray(entryFile) ? entryFile : [entryFile];
|
|
1195
|
+
const dedupedEntryFiles = [...new Set(entryFiles)];
|
|
1196
|
+
if (dedupedEntryFiles.length === 0) throw new Error("At least one React entry file is required.");
|
|
1197
|
+
return dedupedEntryFiles;
|
|
1198
|
+
}
|
|
1199
|
+
function mergeDependencyGraphs(graphs) {
|
|
1200
|
+
const firstGraph = graphs[0];
|
|
1201
|
+
if (firstGraph === void 0) throw new Error("At least one dependency graph is required.");
|
|
1202
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
1203
|
+
for (const graph of graphs) for (const [nodeId, node] of graph.nodes) if (!nodes.has(nodeId)) nodes.set(nodeId, node);
|
|
1204
|
+
const uniqueConfigPaths = [...new Set(graphs.map((graph) => graph.configPath))];
|
|
1205
|
+
const configPath = uniqueConfigPaths.length === 1 ? uniqueConfigPaths[0] : void 0;
|
|
1206
|
+
return {
|
|
1207
|
+
cwd: firstGraph.cwd,
|
|
1208
|
+
entryId: firstGraph.entryId,
|
|
1209
|
+
entryIds: graphs.map((graph) => graph.entryId),
|
|
1210
|
+
nodes,
|
|
1211
|
+
...configPath === void 0 ? {} : { configPath }
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1091
1214
|
//#endregion
|
|
1092
1215
|
//#region src/analyzers/react/queries.ts
|
|
1093
1216
|
function getReactUsageEntries(graph, filter = "all") {
|
|
@@ -1409,6 +1532,6 @@ function formatReactNodeFilePath(filePath, kind, cwd) {
|
|
|
1409
1532
|
return kind === "builtin" ? "html" : toDisplayPath(filePath, cwd);
|
|
1410
1533
|
}
|
|
1411
1534
|
//#endregion
|
|
1412
|
-
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 };
|
|
1535
|
+
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 };
|
|
1413
1536
|
|
|
1414
|
-
//# sourceMappingURL=react-
|
|
1537
|
+
//# sourceMappingURL=react-GNHqx2A-.mjs.map
|