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 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-qiCwZtzi.mjs";
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.entryFile, this.getAnalyzeOptions());
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 <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("--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) => {
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;
@@ -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,UACA,OAAA,GAAS,cAAA,GACR,eAAA;;;KCxBS,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"}
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-qiCwZtzi.mjs";
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 createPendingSymbol(filePath, name, kind, declaration) {
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: declaration.id?.start ?? declaration.start,
813
- declaration,
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
- const root = symbol.declaration.type === "ArrowFunctionExpression" ? symbol.declaration.body : symbol.declaration.body;
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, symbolsByName, importsByLocalName, exportsByName, reExportBindingsByName, exportAllBindings);
946
+ collectImportsAndExports(statement, sourceDependencies, allSymbolsByName, importsByLocalName, exportsByName, reExportBindingsByName, exportAllBindings);
857
947
  });
858
- symbolsByName.forEach((symbol) => {
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.symbolsByName.get(name);
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.symbolsById.get(directTargetId)?.kind === kind) return directTargetId;
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.symbolsById.forEach((symbol) => {
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 dependencyGraph = analyzeDependencies(this.entryFile, this.options);
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.entryId, ...dependencyGraph.nodes.keys()]);
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, filePath === dependencyGraph.entryId, sourceDependencies, this.options.includeBuiltins === true));
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.symbolsById.values()) nodes.set(symbol.id, {
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.symbolsById.values()) {
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-qiCwZtzi.mjs.map
1537
+ //# sourceMappingURL=react-GNHqx2A-.mjs.map