foresthouse 1.0.0-dev.7 → 1.0.0-dev.9

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.
@@ -1,10 +1,10 @@
1
1
  import { builtinModules } from "node:module";
2
- import fs from "node:fs";
3
2
  import path from "node:path";
4
3
  import ts from "typescript";
4
+ import fs from "node:fs";
5
5
  import { parseSync, visitorKeys } from "oxc-parser";
6
6
  import process$1 from "node:process";
7
- //#region src/config.ts
7
+ //#region src/typescript/config.ts
8
8
  function loadCompilerOptions(searchFrom, explicitConfigPath) {
9
9
  const configPath = explicitConfigPath === void 0 ? findNearestConfig(searchFrom) : path.resolve(searchFrom, explicitConfigPath);
10
10
  if (configPath === void 0) return { compilerOptions: defaultCompilerOptions() };
@@ -48,7 +48,18 @@ function formatDiagnostic(diagnostic) {
48
48
  return ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
49
49
  }
50
50
  //#endregion
51
- //#region src/path-utils.ts
51
+ //#region src/analyzers/base.ts
52
+ var BaseAnalyzer = class {
53
+ constructor(entryFile, options) {
54
+ this.entryFile = entryFile;
55
+ this.options = options;
56
+ }
57
+ analyze() {
58
+ return this.doAnalyze();
59
+ }
60
+ };
61
+ //#endregion
62
+ //#region src/utils/is-source-code-file.ts
52
63
  const SOURCE_EXTENSIONS = new Set([
53
64
  ".js",
54
65
  ".jsx",
@@ -59,159 +70,24 @@ const SOURCE_EXTENSIONS = new Set([
59
70
  ".mts",
60
71
  ".cts"
61
72
  ]);
62
- function normalizeFilePath(filePath) {
63
- return path.normalize(filePath);
64
- }
65
- function toDisplayPath(filePath, cwd) {
66
- const relativePath = path.relative(cwd, filePath);
67
- if (relativePath === "") return ".";
68
- const normalizedPath = relativePath.split(path.sep).join("/");
69
- return normalizedPath.startsWith("..") ? filePath : normalizedPath;
70
- }
71
73
  function isSourceCodeFile(filePath) {
72
74
  return SOURCE_EXTENSIONS.has(path.extname(filePath).toLowerCase());
73
75
  }
74
76
  //#endregion
75
- //#region src/analyzer.ts
76
- const BUILTIN_MODULES = new Set(builtinModules.flatMap((name) => [name, `node:${name}`]));
77
- function analyzeDependencies(entryFile, options = {}) {
78
- const cwd = path.resolve(options.cwd ?? process.cwd());
79
- const resolvedEntryPath = resolveExistingPath(cwd, entryFile);
80
- const { compilerOptions, path: configPath } = loadCompilerOptions(path.dirname(resolvedEntryPath), options.configPath);
81
- const host = {
82
- fileExists: ts.sys.fileExists,
83
- readFile: ts.sys.readFile,
84
- directoryExists: ts.sys.directoryExists,
85
- getCurrentDirectory: () => cwd,
86
- getDirectories: ts.sys.getDirectories,
87
- ...ts.sys.realpath === void 0 ? {} : { realpath: ts.sys.realpath }
88
- };
89
- const nodes = /* @__PURE__ */ new Map();
90
- const program = createProgram(resolvedEntryPath, compilerOptions, cwd);
91
- visitFile(resolvedEntryPath, compilerOptions, host, program.getTypeChecker(), program, nodes);
92
- return {
93
- cwd,
94
- entryId: resolvedEntryPath,
95
- nodes,
96
- ...configPath === void 0 ? {} : { configPath }
97
- };
98
- }
99
- function graphToSerializableTree(graph, options = {}) {
100
- const visited = /* @__PURE__ */ new Set();
101
- return serializeNode(graph.entryId, graph, visited, options.omitUnused ?? false);
102
- }
103
- function serializeNode(filePath, graph, visited, omitUnused) {
104
- const node = graph.nodes.get(filePath);
105
- const displayPath = toDisplayPath(filePath, graph.cwd);
106
- if (node === void 0) return {
107
- path: displayPath,
108
- kind: "missing",
109
- dependencies: []
110
- };
111
- if (visited.has(filePath)) return {
112
- path: displayPath,
113
- kind: "circular",
114
- dependencies: []
115
- };
116
- visited.add(filePath);
117
- const dependencies = node.dependencies.filter((dependency) => !omitUnused || !dependency.unused).map((dependency) => {
118
- if (dependency.kind !== "source") return {
119
- specifier: dependency.specifier,
120
- referenceKind: dependency.referenceKind,
121
- isTypeOnly: dependency.isTypeOnly,
122
- unused: dependency.unused,
123
- kind: dependency.kind,
124
- target: dependency.kind === "missing" ? dependency.target : toDisplayPath(dependency.target, graph.cwd)
125
- };
126
- return {
127
- specifier: dependency.specifier,
128
- referenceKind: dependency.referenceKind,
129
- isTypeOnly: dependency.isTypeOnly,
130
- unused: dependency.unused,
131
- kind: dependency.kind,
132
- target: toDisplayPath(dependency.target, graph.cwd),
133
- node: serializeNode(dependency.target, graph, new Set(visited), omitUnused)
134
- };
135
- });
136
- return {
137
- path: displayPath,
138
- kind: filePath === graph.entryId ? "entry" : "source",
139
- dependencies
140
- };
141
- }
142
- function visitFile(filePath, compilerOptions, host, checker, program, nodes) {
143
- const normalizedPath = normalizeFilePath(filePath);
144
- if (nodes.has(normalizedPath)) return;
145
- const dependencies = collectModuleReferences(program.getSourceFile(normalizedPath) ?? createSourceFile(normalizedPath), checker).map((reference) => resolveDependency(reference, normalizedPath, compilerOptions, host));
146
- nodes.set(normalizedPath, {
147
- id: normalizedPath,
148
- dependencies
149
- });
150
- for (const dependency of dependencies) if (dependency.kind === "source") visitFile(dependency.target, compilerOptions, host, checker, program, nodes);
151
- }
152
- function collectModuleReferences(sourceFile, checker) {
153
- const references = /* @__PURE__ */ new Map();
154
- const unusedImports = collectUnusedImports(sourceFile, checker);
155
- function addReference(specifier, referenceKind, isTypeOnly, unused) {
156
- const key = `${referenceKind}:${isTypeOnly ? "type" : "value"}:${specifier}`;
157
- const existing = references.get(key);
158
- if (existing !== void 0) {
159
- if (existing.unused && !unused) references.set(key, {
160
- ...existing,
161
- unused: false
162
- });
163
- return;
164
- }
165
- references.set(key, {
166
- specifier,
167
- referenceKind,
168
- isTypeOnly,
169
- unused
170
- });
171
- }
172
- function visit(node) {
173
- if (ts.isImportDeclaration(node) && ts.isStringLiteralLike(node.moduleSpecifier)) addReference(node.moduleSpecifier.text, "import", node.importClause?.isTypeOnly ?? false, unusedImports.get(node) ?? false);
174
- else if (ts.isExportDeclaration(node) && node.moduleSpecifier !== void 0 && ts.isStringLiteralLike(node.moduleSpecifier)) addReference(node.moduleSpecifier.text, "export", node.isTypeOnly ?? false, false);
175
- else if (ts.isImportEqualsDeclaration(node)) {
176
- const moduleReference = node.moduleReference;
177
- if (ts.isExternalModuleReference(moduleReference) && moduleReference.expression !== void 0 && ts.isStringLiteralLike(moduleReference.expression)) addReference(moduleReference.expression.text, "import-equals", false, false);
178
- } else if (ts.isCallExpression(node)) {
179
- if (node.expression.kind === ts.SyntaxKind.ImportKeyword && node.arguments.length === 1) {
180
- const [argument] = node.arguments;
181
- if (argument !== void 0 && ts.isStringLiteralLike(argument)) addReference(argument.text, "dynamic-import", false, false);
182
- }
183
- if (ts.isIdentifier(node.expression) && node.expression.text === "require" && node.arguments.length === 1) {
184
- const [argument] = node.arguments;
185
- if (argument !== void 0 && ts.isStringLiteralLike(argument)) addReference(argument.text, "require", false, false);
186
- }
187
- }
188
- ts.forEachChild(node, visit);
189
- }
190
- visit(sourceFile);
191
- return [...references.values()];
192
- }
193
- function resolveDependency(reference, containingFile, compilerOptions, host) {
194
- const specifier = reference.specifier;
195
- if (BUILTIN_MODULES.has(specifier)) return createEdge(reference, "builtin", specifier);
196
- const resolution = ts.resolveModuleName(specifier, containingFile, compilerOptions, host).resolvedModule;
197
- if (resolution !== void 0) {
198
- const resolvedPath = normalizeFilePath(resolution.resolvedFileName);
199
- if (resolution.isExternalLibraryImport || resolvedPath.includes(`${path.sep}node_modules${path.sep}`)) return createEdge(reference, "external", specifier);
200
- if (isSourceCodeFile(resolvedPath) && !resolvedPath.endsWith(".d.ts")) return createEdge(reference, "source", resolvedPath);
201
- }
202
- if (!specifier.startsWith(".") && !path.isAbsolute(specifier)) return createEdge(reference, "external", specifier);
203
- return createEdge(reference, "missing", specifier);
77
+ //#region src/utils/normalize-file-path.ts
78
+ function normalizeFilePath(filePath) {
79
+ return path.normalize(filePath);
204
80
  }
205
- function createEdge(reference, kind, target) {
206
- return {
207
- specifier: reference.specifier,
208
- referenceKind: reference.referenceKind,
209
- isTypeOnly: reference.isTypeOnly,
210
- unused: reference.unused,
211
- kind,
212
- target
213
- };
81
+ //#endregion
82
+ //#region src/analyzers/import/entry.ts
83
+ function resolveExistingPath(cwd, entryFile) {
84
+ const normalizedPath = normalizeFilePath(path.resolve(cwd, entryFile));
85
+ if (!fs.existsSync(normalizedPath)) throw new Error(`Entry file not found: ${entryFile}`);
86
+ if (!isSourceCodeFile(normalizedPath)) throw new Error(`Entry file must be a JS/TS source file: ${entryFile}`);
87
+ return normalizedPath;
214
88
  }
89
+ //#endregion
90
+ //#region src/typescript/program.ts
215
91
  function createProgram(entryFile, compilerOptions, cwd) {
216
92
  const host = ts.createCompilerHost(compilerOptions, true);
217
93
  host.getCurrentDirectory = () => cwd;
@@ -226,6 +102,29 @@ function createSourceFile(filePath) {
226
102
  const sourceText = fs.readFileSync(filePath, "utf8");
227
103
  return ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(filePath));
228
104
  }
105
+ function createModuleResolutionHost(cwd) {
106
+ return {
107
+ fileExists: ts.sys.fileExists,
108
+ readFile: ts.sys.readFile,
109
+ directoryExists: ts.sys.directoryExists,
110
+ getCurrentDirectory: () => cwd,
111
+ getDirectories: ts.sys.getDirectories,
112
+ ...ts.sys.realpath === void 0 ? {} : { realpath: ts.sys.realpath }
113
+ };
114
+ }
115
+ function getScriptKind(filePath) {
116
+ switch (path.extname(filePath).toLowerCase()) {
117
+ case ".js":
118
+ case ".mjs":
119
+ case ".cjs": return ts.ScriptKind.JS;
120
+ case ".jsx": return ts.ScriptKind.JSX;
121
+ case ".tsx": return ts.ScriptKind.TSX;
122
+ case ".json": return ts.ScriptKind.JSON;
123
+ default: return ts.ScriptKind.TS;
124
+ }
125
+ }
126
+ //#endregion
127
+ //#region src/analyzers/import/unused.ts
229
128
  function collectUnusedImports(sourceFile, checker) {
230
129
  const importUsage = /* @__PURE__ */ new Map();
231
130
  const symbolToImportDeclaration = /* @__PURE__ */ new Map();
@@ -293,169 +192,316 @@ function tryGetSymbolAtLocation(checker, node) {
293
192
  return;
294
193
  }
295
194
  }
296
- function getScriptKind(filePath) {
297
- switch (path.extname(filePath).toLowerCase()) {
298
- case ".js":
299
- case ".mjs":
300
- case ".cjs": return ts.ScriptKind.JS;
301
- case ".jsx": return ts.ScriptKind.JSX;
302
- case ".tsx": return ts.ScriptKind.TSX;
303
- case ".json": return ts.ScriptKind.JSON;
304
- default: return ts.ScriptKind.TS;
305
- }
306
- }
307
- function resolveExistingPath(cwd, entryFile) {
308
- const normalizedPath = normalizeFilePath(path.resolve(cwd, entryFile));
309
- if (!fs.existsSync(normalizedPath)) throw new Error(`Entry file not found: ${entryFile}`);
310
- if (!isSourceCodeFile(normalizedPath)) throw new Error(`Entry file must be a JS/TS source file: ${entryFile}`);
311
- return normalizedPath;
312
- }
313
195
  //#endregion
314
- //#region src/react-analyzer.ts
315
- const FUNCTION_NODE_TYPES = new Set([
316
- "FunctionDeclaration",
317
- "FunctionExpression",
318
- "ArrowFunctionExpression",
319
- "TSDeclareFunction",
320
- "TSEmptyBodyFunctionExpression"
321
- ]);
322
- function analyzeReactUsage(entryFile, options = {}) {
323
- const dependencyGraph = analyzeDependencies(entryFile, options);
324
- const reachableFiles = new Set([dependencyGraph.entryId, ...dependencyGraph.nodes.keys()]);
325
- const fileAnalyses = /* @__PURE__ */ new Map();
326
- for (const filePath of [...reachableFiles].sort()) {
327
- if (!isSourceCodeFile(filePath) || filePath.endsWith(".d.ts")) continue;
328
- const sourceText = fs.readFileSync(filePath, "utf8");
329
- const parseResult = parseSync(filePath, sourceText, {
330
- astType: "ts",
331
- sourceType: "unambiguous"
332
- });
333
- const dependencyNode = dependencyGraph.nodes.get(filePath);
334
- const sourceDependencies = /* @__PURE__ */ new Map();
335
- dependencyNode?.dependencies.forEach((dependency) => {
336
- if (dependency.kind === "source") sourceDependencies.set(dependency.specifier, dependency.target);
337
- });
338
- fileAnalyses.set(filePath, analyzeReactFile(parseResult.program, filePath, sourceText, filePath === dependencyGraph.entryId, sourceDependencies));
339
- }
340
- const nodes = /* @__PURE__ */ new Map();
341
- for (const fileAnalysis of fileAnalyses.values()) for (const symbol of fileAnalysis.symbolsById.values()) nodes.set(symbol.id, {
342
- id: symbol.id,
343
- name: symbol.name,
344
- kind: symbol.kind,
345
- filePath: symbol.filePath,
346
- exportNames: [...symbol.exportNames].sort(),
347
- usages: []
348
- });
349
- for (const fileAnalysis of fileAnalyses.values()) fileAnalysis.importsByLocalName.forEach((binding, localName) => {
350
- if (binding.sourcePath !== void 0) return;
351
- if (!isHookName(localName) && !isHookName(binding.importedName)) return;
352
- const externalNode = createExternalHookNode(binding, localName);
353
- if (!nodes.has(externalNode.id)) nodes.set(externalNode.id, externalNode);
354
- });
355
- for (const fileAnalysis of fileAnalyses.values()) for (const symbol of fileAnalysis.symbolsById.values()) {
356
- const usages = /* @__PURE__ */ new Map();
357
- symbol.componentReferences.forEach((referenceName) => {
358
- const targetId = resolveReactReference(fileAnalysis, fileAnalyses, referenceName, "component");
359
- if (targetId !== void 0 && targetId !== symbol.id) usages.set(`render:${targetId}`, {
360
- kind: "render",
361
- target: targetId
362
- });
363
- });
364
- symbol.hookReferences.forEach((referenceName) => {
365
- const targetId = resolveReactReference(fileAnalysis, fileAnalyses, referenceName, "hook");
366
- if (targetId !== void 0 && targetId !== symbol.id) usages.set(`hook:${targetId}`, {
367
- kind: "hook-call",
368
- target: targetId
196
+ //#region src/analyzers/import/references.ts
197
+ function collectModuleReferences(sourceFile, checker) {
198
+ const references = /* @__PURE__ */ new Map();
199
+ const unusedImports = collectUnusedImports(sourceFile, checker);
200
+ function addReference(specifier, referenceKind, isTypeOnly, unused) {
201
+ const key = `${referenceKind}:${isTypeOnly ? "type" : "value"}:${specifier}`;
202
+ const existing = references.get(key);
203
+ if (existing !== void 0) {
204
+ if (existing.unused && !unused) references.set(key, {
205
+ ...existing,
206
+ unused: false
369
207
  });
370
- });
371
- const node = nodes.get(symbol.id);
372
- if (node === void 0) continue;
373
- const sortedUsages = [...usages.values()].sort((left, right) => compareReactNodeIds(left.target, right.target, nodes));
374
- nodes.set(symbol.id, {
375
- ...node,
376
- usages: sortedUsages
208
+ return;
209
+ }
210
+ references.set(key, {
211
+ specifier,
212
+ referenceKind,
213
+ isTypeOnly,
214
+ unused
377
215
  });
378
216
  }
379
- const entriesByKey = /* @__PURE__ */ new Map();
380
- for (const fileAnalysis of fileAnalyses.values()) for (const entry of fileAnalysis.entryUsages) {
381
- const targetId = resolveReactReference(fileAnalysis, fileAnalyses, entry.referenceName, entry.kind);
382
- if (targetId === void 0) continue;
383
- const key = `${entry.location.filePath}:${entry.location.line}:${entry.location.column}:${targetId}`;
384
- entriesByKey.set(key, {
385
- target: targetId,
386
- location: entry.location
387
- });
217
+ function visit(node) {
218
+ if (ts.isImportDeclaration(node) && ts.isStringLiteralLike(node.moduleSpecifier)) addReference(node.moduleSpecifier.text, "import", node.importClause?.isTypeOnly ?? false, unusedImports.get(node) ?? false);
219
+ else if (ts.isExportDeclaration(node) && node.moduleSpecifier !== void 0 && ts.isStringLiteralLike(node.moduleSpecifier)) addReference(node.moduleSpecifier.text, "export", node.isTypeOnly ?? false, false);
220
+ else if (ts.isImportEqualsDeclaration(node)) {
221
+ const moduleReference = node.moduleReference;
222
+ if (ts.isExternalModuleReference(moduleReference) && moduleReference.expression !== void 0 && ts.isStringLiteralLike(moduleReference.expression)) addReference(moduleReference.expression.text, "import-equals", false, false);
223
+ } else if (ts.isCallExpression(node)) {
224
+ if (node.expression.kind === ts.SyntaxKind.ImportKeyword && node.arguments.length === 1) {
225
+ const [argument] = node.arguments;
226
+ if (argument !== void 0 && ts.isStringLiteralLike(argument)) addReference(argument.text, "dynamic-import", false, false);
227
+ }
228
+ if (ts.isIdentifier(node.expression) && node.expression.text === "require" && node.arguments.length === 1) {
229
+ const [argument] = node.arguments;
230
+ if (argument !== void 0 && ts.isStringLiteralLike(argument)) addReference(argument.text, "require", false, false);
231
+ }
232
+ }
233
+ ts.forEachChild(node, visit);
388
234
  }
389
- const entries = [...entriesByKey.values()].sort((left, right) => compareReactUsageEntries(left, right, nodes));
390
- return {
391
- cwd: dependencyGraph.cwd,
392
- entryId: dependencyGraph.entryId,
393
- nodes,
394
- entries
395
- };
396
- }
397
- function graphToSerializableReactTree(graph, options = {}) {
398
- const filter = options.filter ?? "all";
399
- const entries = getReactUsageEntries(graph, filter);
400
- const roots = entries.length > 0 ? entries.map((entry) => serializeReactUsageNode(entry.target, graph, filter, /* @__PURE__ */ new Set())) : getReactUsageRoots(graph, filter).map((rootId) => serializeReactUsageNode(rootId, graph, filter, /* @__PURE__ */ new Set()));
401
- return {
402
- kind: "react-usage",
403
- entries: entries.map((entry) => serializeReactUsageEntry(entry, graph, filter)),
404
- roots
405
- };
406
- }
407
- function getReactUsageEntries(graph, filter = "all") {
408
- return graph.entries.filter((entry) => {
409
- const targetNode = graph.nodes.get(entry.target);
410
- return targetNode !== void 0 && matchesReactFilter(targetNode, filter);
411
- });
235
+ visit(sourceFile);
236
+ return [...references.values()];
412
237
  }
413
- function getReactUsageRoots(graph, filter = "all") {
414
- const entries = getReactUsageEntries(graph, filter);
415
- if (entries.length > 0) return [...new Set(entries.map((entry) => entry.target))];
416
- const filteredNodes = getFilteredReactUsageNodes(graph, filter);
417
- const inboundCounts = /* @__PURE__ */ new Map();
418
- filteredNodes.forEach((node) => {
419
- inboundCounts.set(node.id, 0);
420
- });
421
- filteredNodes.forEach((node) => {
422
- getFilteredUsages(node, graph, filter).forEach((usage) => {
423
- inboundCounts.set(usage.target, (inboundCounts.get(usage.target) ?? 0) + 1);
238
+ //#endregion
239
+ //#region src/analyzers/import/resolver.ts
240
+ const BUILTIN_MODULES = new Set(builtinModules.flatMap((name) => [name, `node:${name}`]));
241
+ function resolveDependency(reference, containingFile, compilerOptions, host) {
242
+ const specifier = reference.specifier;
243
+ if (BUILTIN_MODULES.has(specifier)) return createEdge(reference, "builtin", specifier);
244
+ const resolution = ts.resolveModuleName(specifier, containingFile, compilerOptions, host).resolvedModule;
245
+ if (resolution !== void 0) {
246
+ const resolvedPath = normalizeFilePath(resolution.resolvedFileName);
247
+ if (resolution.isExternalLibraryImport || resolvedPath.includes(`${path.sep}node_modules${path.sep}`)) return createEdge(reference, "external", specifier);
248
+ if (isSourceCodeFile(resolvedPath) && !resolvedPath.endsWith(".d.ts")) return createEdge(reference, "source", resolvedPath);
249
+ }
250
+ if (!specifier.startsWith(".") && !path.isAbsolute(specifier)) return createEdge(reference, "external", specifier);
251
+ return createEdge(reference, "missing", specifier);
252
+ }
253
+ function createEdge(reference, kind, target) {
254
+ return {
255
+ specifier: reference.specifier,
256
+ referenceKind: reference.referenceKind,
257
+ isTypeOnly: reference.isTypeOnly,
258
+ unused: reference.unused,
259
+ kind,
260
+ target
261
+ };
262
+ }
263
+ //#endregion
264
+ //#region src/analyzers/import/graph.ts
265
+ function buildDependencyGraph(entryPath, compilerOptions, cwd) {
266
+ return new DependencyGraphBuilder(entryPath, compilerOptions, cwd).build();
267
+ }
268
+ var DependencyGraphBuilder = class {
269
+ host;
270
+ nodes = /* @__PURE__ */ new Map();
271
+ program;
272
+ checker;
273
+ constructor(entryPath, compilerOptions, cwd) {
274
+ this.entryPath = entryPath;
275
+ this.compilerOptions = compilerOptions;
276
+ this.host = createModuleResolutionHost(cwd);
277
+ this.program = createProgram(entryPath, compilerOptions, cwd);
278
+ this.checker = this.program.getTypeChecker();
279
+ }
280
+ build() {
281
+ this.visitFile(this.entryPath);
282
+ return this.nodes;
283
+ }
284
+ visitFile(filePath) {
285
+ const normalizedPath = normalizeFilePath(filePath);
286
+ if (this.nodes.has(normalizedPath)) return;
287
+ const dependencies = collectModuleReferences(this.program.getSourceFile(normalizedPath) ?? createSourceFile(normalizedPath), this.checker).map((reference) => resolveDependency(reference, normalizedPath, this.compilerOptions, this.host));
288
+ this.nodes.set(normalizedPath, {
289
+ id: normalizedPath,
290
+ dependencies
291
+ });
292
+ for (const dependency of dependencies) if (dependency.kind === "source") this.visitFile(dependency.target);
293
+ }
294
+ };
295
+ //#endregion
296
+ //#region src/analyzers/import/index.ts
297
+ function analyzeDependencies(entryFile, options = {}) {
298
+ return new ImportAnalyzer(entryFile, options).analyze();
299
+ }
300
+ var ImportAnalyzer = class extends BaseAnalyzer {
301
+ cwd;
302
+ entryPath;
303
+ constructor(entryFile, options) {
304
+ super(entryFile, options);
305
+ this.cwd = path.resolve(options.cwd ?? process.cwd());
306
+ this.entryPath = resolveExistingPath(this.cwd, entryFile);
307
+ }
308
+ doAnalyze() {
309
+ const { compilerOptions, path: configPath } = loadCompilerOptions(path.dirname(this.entryPath), this.options.configPath);
310
+ const nodes = buildDependencyGraph(this.entryPath, compilerOptions, this.cwd);
311
+ return {
312
+ cwd: this.cwd,
313
+ entryId: this.entryPath,
314
+ nodes,
315
+ ...configPath === void 0 ? {} : { configPath }
316
+ };
317
+ }
318
+ };
319
+ //#endregion
320
+ //#region src/analyzers/react/bindings.ts
321
+ function collectImportsAndExports(statement, sourceDependencies, symbolsByName, importsByLocalName, exportsByName) {
322
+ switch (statement.type) {
323
+ case "ImportDeclaration":
324
+ collectImportBindings(statement, sourceDependencies, importsByLocalName);
325
+ return;
326
+ case "ExportNamedDeclaration":
327
+ collectNamedExports(statement, symbolsByName, exportsByName);
328
+ return;
329
+ case "ExportDefaultDeclaration":
330
+ collectDefaultExport(statement, symbolsByName, exportsByName);
331
+ return;
332
+ default: return;
333
+ }
334
+ }
335
+ function collectImportBindings(declaration, sourceDependencies, importsByLocalName) {
336
+ if (declaration.importKind === "type") return;
337
+ const sourceSpecifier = declaration.source.value;
338
+ const sourcePath = sourceDependencies.get(declaration.source.value);
339
+ declaration.specifiers.forEach((specifier) => {
340
+ const binding = getImportBinding(specifier, sourceSpecifier, sourcePath);
341
+ if (binding === void 0) return;
342
+ importsByLocalName.set(binding.localName, {
343
+ importedName: binding.importedName,
344
+ sourceSpecifier: binding.sourceSpecifier,
345
+ ...binding.sourcePath === void 0 ? {} : { sourcePath: binding.sourcePath }
424
346
  });
425
347
  });
426
- const roots = filteredNodes.filter((node) => (inboundCounts.get(node.id) ?? 0) === 0).map((node) => node.id);
427
- if (roots.length > 0) return roots.sort((left, right) => compareReactNodeIds(left, right, graph.nodes));
428
- return filteredNodes.map((node) => node.id).sort((left, right) => compareReactNodeIds(left, right, graph.nodes));
429
348
  }
430
- function getFilteredUsages(node, graph, filter = "all") {
431
- return node.usages.filter((usage) => {
432
- const targetNode = graph.nodes.get(usage.target);
433
- return targetNode !== void 0 && matchesReactFilter(targetNode, filter);
349
+ function getImportBinding(specifier, sourceSpecifier, sourcePath) {
350
+ if (specifier.type === "ImportSpecifier") {
351
+ if (specifier.importKind === "type") return;
352
+ return {
353
+ localName: specifier.local.name,
354
+ importedName: toModuleExportName(specifier.imported),
355
+ sourceSpecifier,
356
+ ...sourcePath === void 0 ? {} : { sourcePath }
357
+ };
358
+ }
359
+ if (specifier.type === "ImportDefaultSpecifier") return {
360
+ localName: specifier.local.name,
361
+ importedName: "default",
362
+ sourceSpecifier,
363
+ ...sourcePath === void 0 ? {} : { sourcePath }
364
+ };
365
+ }
366
+ function collectNamedExports(declaration, symbolsByName, exportsByName) {
367
+ if (declaration.exportKind === "type") return;
368
+ if (declaration.declaration !== null) {
369
+ if (declaration.declaration.type === "FunctionDeclaration") {
370
+ const name = declaration.declaration.id?.name;
371
+ if (name !== void 0) addExportBinding(name, name, symbolsByName, exportsByName);
372
+ } else if (declaration.declaration.type === "VariableDeclaration") declaration.declaration.declarations.forEach((declarator) => {
373
+ if (declarator.id.type === "Identifier") addExportBinding(declarator.id.name, declarator.id.name, symbolsByName, exportsByName);
374
+ });
375
+ return;
376
+ }
377
+ if (declaration.source !== null) return;
378
+ declaration.specifiers.forEach((specifier) => {
379
+ if (specifier.exportKind === "type") return;
380
+ addExportBinding(toModuleExportName(specifier.local), toModuleExportName(specifier.exported), symbolsByName, exportsByName);
434
381
  });
435
382
  }
436
- function analyzeReactFile(program, filePath, sourceText, includeNestedRenderEntries, sourceDependencies) {
437
- const symbolsByName = /* @__PURE__ */ new Map();
438
- program.body.forEach((statement) => {
439
- collectTopLevelReactSymbols(statement, filePath, symbolsByName);
383
+ function collectDefaultExport(declaration, symbolsByName, exportsByName) {
384
+ if (declaration.declaration.type === "FunctionDeclaration" || declaration.declaration.type === "FunctionExpression") {
385
+ const localName = declaration.declaration.id?.name;
386
+ if (localName !== void 0) addExportBinding(localName, "default", symbolsByName, exportsByName);
387
+ return;
388
+ }
389
+ if (declaration.declaration.type === "Identifier") {
390
+ addExportBinding(declaration.declaration.name, "default", symbolsByName, exportsByName);
391
+ return;
392
+ }
393
+ if (declaration.declaration.type === "ArrowFunctionExpression") addExportBinding("default", "default", symbolsByName, exportsByName);
394
+ }
395
+ function addExportBinding(localName, exportedName, symbolsByName, exportsByName) {
396
+ const symbol = symbolsByName.get(localName);
397
+ if (symbol === void 0) return;
398
+ symbol.exportNames.add(exportedName);
399
+ exportsByName.set(exportedName, symbol.id);
400
+ }
401
+ function toModuleExportName(name) {
402
+ return name.type === "Literal" ? name.value : name.name;
403
+ }
404
+ //#endregion
405
+ //#region src/analyzers/react/walk.ts
406
+ const FUNCTION_NODE_TYPES = new Set([
407
+ "FunctionDeclaration",
408
+ "FunctionExpression",
409
+ "ArrowFunctionExpression",
410
+ "TSDeclareFunction",
411
+ "TSEmptyBodyFunctionExpression"
412
+ ]);
413
+ function walkReactUsageTree(root, visit) {
414
+ walkNode(root, visit, true);
415
+ }
416
+ function walkNode(node, visit, allowNestedFunctions = false) {
417
+ visit(node);
418
+ const keys = visitorKeys[node.type];
419
+ if (keys === void 0) return;
420
+ keys.forEach((key) => {
421
+ const value = node[key];
422
+ walkChild(value, visit, allowNestedFunctions);
440
423
  });
441
- const importsByLocalName = /* @__PURE__ */ new Map();
442
- const exportsByName = /* @__PURE__ */ new Map();
443
- const entryUsages = collectEntryUsages(program, filePath, sourceText, includeNestedRenderEntries);
444
- program.body.forEach((statement) => {
445
- collectImportsAndExports(statement, sourceDependencies, symbolsByName, importsByLocalName, exportsByName);
424
+ }
425
+ function walkChild(value, visit, allowNestedFunctions) {
426
+ if (Array.isArray(value)) {
427
+ value.forEach((entry) => {
428
+ walkChild(entry, visit, allowNestedFunctions);
429
+ });
430
+ return;
431
+ }
432
+ if (!isNode(value)) return;
433
+ if (!allowNestedFunctions && FUNCTION_NODE_TYPES.has(value.type)) return;
434
+ walkNode(value, visit, false);
435
+ }
436
+ function isNode(value) {
437
+ return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
438
+ }
439
+ function classifyReactSymbol(name, declaration) {
440
+ if (isHookName(name)) return "hook";
441
+ if (isComponentName(name) && returnsReactElement(declaration)) return "component";
442
+ }
443
+ function containsReactElementLikeExpression(expression) {
444
+ let found = false;
445
+ walkNode(expression, (node) => {
446
+ if (node.type === "JSXElement" || node.type === "JSXFragment" || node.type === "CallExpression" && isReactCreateElementCall(node)) found = true;
446
447
  });
447
- symbolsByName.forEach((symbol) => {
448
- analyzeSymbolUsages(symbol);
448
+ return found;
449
+ }
450
+ function getComponentReferenceName(node) {
451
+ const name = getJsxName(node.openingElement.name);
452
+ return name !== void 0 && isComponentName(name) ? name : void 0;
453
+ }
454
+ function getHookReferenceName(node) {
455
+ const calleeName = getIdentifierName(node.callee);
456
+ return calleeName !== void 0 && isHookName(calleeName) ? calleeName : void 0;
457
+ }
458
+ function getCreateElementComponentReferenceName(node) {
459
+ if (!isReactCreateElementCall(node)) return;
460
+ const [firstArgument] = node.arguments;
461
+ if (firstArgument === void 0 || firstArgument.type !== "Identifier") return;
462
+ return isComponentName(firstArgument.name) ? firstArgument.name : void 0;
463
+ }
464
+ function isHookName(name) {
465
+ return /^use[A-Z0-9]/.test(name);
466
+ }
467
+ function isComponentName(name) {
468
+ return /^[A-Z]/.test(name);
469
+ }
470
+ function returnsReactElement(declaration) {
471
+ if (declaration.type === "ArrowFunctionExpression" && declaration.expression) return containsReactElementLikeExpression(declaration.body);
472
+ const body = declaration.body;
473
+ if (body === null) return false;
474
+ let found = false;
475
+ walkReactUsageTree(body, (node) => {
476
+ if (node.type !== "ReturnStatement" || node.argument === null) return;
477
+ if (containsReactElementLikeExpression(node.argument)) found = true;
449
478
  });
450
- return {
451
- filePath,
452
- importsByLocalName,
453
- exportsByName,
454
- entryUsages,
455
- symbolsById: new Map([...symbolsByName.values()].map((symbol) => [symbol.id, symbol])),
456
- symbolsByName
457
- };
479
+ return found;
480
+ }
481
+ function isReactCreateElementCall(node) {
482
+ const callee = unwrapExpression(node.callee);
483
+ if (callee.type !== "MemberExpression" || callee.computed) return false;
484
+ return callee.object.type === "Identifier" && callee.object.name === "React" && callee.property.name === "createElement";
485
+ }
486
+ function getJsxName(name) {
487
+ if (name.type === "JSXIdentifier") return name.name;
488
+ }
489
+ function getIdentifierName(expression) {
490
+ const unwrapped = unwrapExpression(expression);
491
+ return unwrapped.type === "Identifier" ? unwrapped.name : void 0;
492
+ }
493
+ function unwrapExpression(expression) {
494
+ let current = expression;
495
+ while (true) {
496
+ if (current.type === "ParenthesizedExpression" || current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSTypeAssertion" || current.type === "TSNonNullExpression") {
497
+ current = current.expression;
498
+ continue;
499
+ }
500
+ return current;
501
+ }
458
502
  }
503
+ //#endregion
504
+ //#region src/analyzers/react/entries.ts
459
505
  function collectEntryUsages(program, filePath, sourceText, includeNestedFunctions) {
460
506
  const entries = /* @__PURE__ */ new Map();
461
507
  program.body.forEach((statement) => {
@@ -531,6 +577,11 @@ function offsetToLineAndColumn(sourceText, offset) {
531
577
  column
532
578
  };
533
579
  }
580
+ function comparePendingReactUsageEntries(left, right) {
581
+ return left.location.filePath.localeCompare(right.location.filePath) || left.location.line - right.location.line || left.location.column - right.location.column || left.kind.localeCompare(right.kind) || left.referenceName.localeCompare(right.referenceName);
582
+ }
583
+ //#endregion
584
+ //#region src/analyzers/react/symbols.ts
534
585
  function collectTopLevelReactSymbols(statement, filePath, symbolsByName) {
535
586
  switch (statement.type) {
536
587
  case "FunctionDeclaration":
@@ -585,86 +636,8 @@ function createPendingSymbol(filePath, name, kind, declaration) {
585
636
  hookReferences: /* @__PURE__ */ new Set()
586
637
  };
587
638
  }
588
- function collectImportsAndExports(statement, sourceDependencies, symbolsByName, importsByLocalName, exportsByName) {
589
- switch (statement.type) {
590
- case "ImportDeclaration":
591
- collectImportBindings(statement, sourceDependencies, importsByLocalName);
592
- return;
593
- case "ExportNamedDeclaration":
594
- collectNamedExports(statement, symbolsByName, exportsByName);
595
- return;
596
- case "ExportDefaultDeclaration":
597
- collectDefaultExport(statement, symbolsByName, exportsByName);
598
- return;
599
- default: return;
600
- }
601
- }
602
- function collectImportBindings(declaration, sourceDependencies, importsByLocalName) {
603
- if (declaration.importKind === "type") return;
604
- const sourceSpecifier = declaration.source.value;
605
- const sourcePath = sourceDependencies.get(declaration.source.value);
606
- declaration.specifiers.forEach((specifier) => {
607
- const binding = getImportBinding(specifier, sourceSpecifier, sourcePath);
608
- if (binding === void 0) return;
609
- importsByLocalName.set(binding.localName, {
610
- importedName: binding.importedName,
611
- sourceSpecifier: binding.sourceSpecifier,
612
- ...binding.sourcePath === void 0 ? {} : { sourcePath: binding.sourcePath }
613
- });
614
- });
615
- }
616
- function getImportBinding(specifier, sourceSpecifier, sourcePath) {
617
- if (specifier.type === "ImportSpecifier") {
618
- if (specifier.importKind === "type") return;
619
- return {
620
- localName: specifier.local.name,
621
- importedName: toModuleExportName(specifier.imported),
622
- sourceSpecifier,
623
- ...sourcePath === void 0 ? {} : { sourcePath }
624
- };
625
- }
626
- if (specifier.type === "ImportDefaultSpecifier") return {
627
- localName: specifier.local.name,
628
- importedName: "default",
629
- sourceSpecifier,
630
- ...sourcePath === void 0 ? {} : { sourcePath }
631
- };
632
- }
633
- function collectNamedExports(declaration, symbolsByName, exportsByName) {
634
- if (declaration.exportKind === "type") return;
635
- if (declaration.declaration !== null) {
636
- if (declaration.declaration.type === "FunctionDeclaration") {
637
- const name = declaration.declaration.id?.name;
638
- if (name !== void 0) addExportBinding(name, name, symbolsByName, exportsByName);
639
- } else if (declaration.declaration.type === "VariableDeclaration") declaration.declaration.declarations.forEach((declarator) => {
640
- if (declarator.id.type === "Identifier") addExportBinding(declarator.id.name, declarator.id.name, symbolsByName, exportsByName);
641
- });
642
- return;
643
- }
644
- if (declaration.source !== null) return;
645
- declaration.specifiers.forEach((specifier) => {
646
- if (specifier.exportKind === "type") return;
647
- addExportBinding(toModuleExportName(specifier.local), toModuleExportName(specifier.exported), symbolsByName, exportsByName);
648
- });
649
- }
650
- function collectDefaultExport(declaration, symbolsByName, exportsByName) {
651
- if (declaration.declaration.type === "FunctionDeclaration" || declaration.declaration.type === "FunctionExpression") {
652
- const localName = declaration.declaration.id?.name;
653
- if (localName !== void 0) addExportBinding(localName, "default", symbolsByName, exportsByName);
654
- return;
655
- }
656
- if (declaration.declaration.type === "Identifier") {
657
- addExportBinding(declaration.declaration.name, "default", symbolsByName, exportsByName);
658
- return;
659
- }
660
- if (declaration.declaration.type === "ArrowFunctionExpression") addExportBinding("default", "default", symbolsByName, exportsByName);
661
- }
662
- function addExportBinding(localName, exportedName, symbolsByName, exportsByName) {
663
- const symbol = symbolsByName.get(localName);
664
- if (symbol === void 0) return;
665
- symbol.exportNames.add(exportedName);
666
- exportsByName.set(exportedName, symbol.id);
667
- }
639
+ //#endregion
640
+ //#region src/analyzers/react/usage.ts
668
641
  function analyzeSymbolUsages(symbol) {
669
642
  const root = symbol.declaration.type === "ArrowFunctionExpression" ? symbol.declaration.body : symbol.declaration.body;
670
643
  if (root === null) return;
@@ -682,90 +655,33 @@ function analyzeSymbolUsages(symbol) {
682
655
  }
683
656
  });
684
657
  }
685
- function classifyReactSymbol(name, declaration) {
686
- if (isHookName(name)) return "hook";
687
- if (isComponentName(name) && returnsReactElement(declaration)) return "component";
688
- }
689
- function returnsReactElement(declaration) {
690
- if (declaration.type === "ArrowFunctionExpression" && declaration.expression) return containsReactElementLikeExpression(declaration.body);
691
- const body = declaration.body;
692
- if (body === null) return false;
693
- let found = false;
694
- walkReactUsageTree(body, (node) => {
695
- if (node.type !== "ReturnStatement" || node.argument === null) return;
696
- if (containsReactElementLikeExpression(node.argument)) found = true;
658
+ //#endregion
659
+ //#region src/analyzers/react/file.ts
660
+ function analyzeReactFile(program, filePath, sourceText, includeNestedRenderEntries, sourceDependencies) {
661
+ const symbolsByName = /* @__PURE__ */ new Map();
662
+ program.body.forEach((statement) => {
663
+ collectTopLevelReactSymbols(statement, filePath, symbolsByName);
697
664
  });
698
- return found;
699
- }
700
- function containsReactElementLikeExpression(expression) {
701
- let found = false;
702
- walkNode(expression, (node) => {
703
- if (node.type === "JSXElement" || node.type === "JSXFragment" || node.type === "CallExpression" && isReactCreateElementCall(node)) found = true;
665
+ const importsByLocalName = /* @__PURE__ */ new Map();
666
+ const exportsByName = /* @__PURE__ */ new Map();
667
+ const entryUsages = collectEntryUsages(program, filePath, sourceText, includeNestedRenderEntries);
668
+ program.body.forEach((statement) => {
669
+ collectImportsAndExports(statement, sourceDependencies, symbolsByName, importsByLocalName, exportsByName);
704
670
  });
705
- return found;
706
- }
707
- function getComponentReferenceName(node) {
708
- const name = getJsxName(node.openingElement.name);
709
- return name !== void 0 && isComponentName(name) ? name : void 0;
710
- }
711
- function getHookReferenceName(node) {
712
- const calleeName = getIdentifierName(node.callee);
713
- return calleeName !== void 0 && isHookName(calleeName) ? calleeName : void 0;
714
- }
715
- function getCreateElementComponentReferenceName(node) {
716
- if (!isReactCreateElementCall(node)) return;
717
- const [firstArgument] = node.arguments;
718
- if (firstArgument === void 0 || firstArgument.type !== "Identifier") return;
719
- return isComponentName(firstArgument.name) ? firstArgument.name : void 0;
720
- }
721
- function isReactCreateElementCall(node) {
722
- const callee = unwrapExpression(node.callee);
723
- if (callee.type !== "MemberExpression" || callee.computed) return false;
724
- return callee.object.type === "Identifier" && callee.object.name === "React" && callee.property.name === "createElement";
725
- }
726
- function getJsxName(name) {
727
- if (name.type === "JSXIdentifier") return name.name;
728
- }
729
- function getIdentifierName(expression) {
730
- const unwrapped = unwrapExpression(expression);
731
- return unwrapped.type === "Identifier" ? unwrapped.name : void 0;
732
- }
733
- function unwrapExpression(expression) {
734
- let current = expression;
735
- while (true) {
736
- if (current.type === "ParenthesizedExpression" || current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSTypeAssertion" || current.type === "TSNonNullExpression") {
737
- current = current.expression;
738
- continue;
739
- }
740
- return current;
741
- }
742
- }
743
- function walkReactUsageTree(root, visit) {
744
- walkNode(root, visit, true);
745
- }
746
- function walkNode(node, visit, allowNestedFunctions = false) {
747
- visit(node);
748
- const keys = visitorKeys[node.type];
749
- if (keys === void 0) return;
750
- keys.forEach((key) => {
751
- const value = node[key];
752
- walkChild(value, visit, allowNestedFunctions);
671
+ symbolsByName.forEach((symbol) => {
672
+ analyzeSymbolUsages(symbol);
753
673
  });
674
+ return {
675
+ filePath,
676
+ importsByLocalName,
677
+ exportsByName,
678
+ entryUsages,
679
+ symbolsById: new Map([...symbolsByName.values()].map((symbol) => [symbol.id, symbol])),
680
+ symbolsByName
681
+ };
754
682
  }
755
- function walkChild(value, visit, allowNestedFunctions) {
756
- if (Array.isArray(value)) {
757
- value.forEach((entry) => {
758
- walkChild(entry, visit, allowNestedFunctions);
759
- });
760
- return;
761
- }
762
- if (!isNode(value)) return;
763
- if (!allowNestedFunctions && FUNCTION_NODE_TYPES.has(value.type)) return;
764
- walkNode(value, visit, false);
765
- }
766
- function isNode(value) {
767
- return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
768
- }
683
+ //#endregion
684
+ //#region src/analyzers/react/references.ts
769
685
  function resolveReactReference(fileAnalysis, fileAnalyses, name, kind) {
770
686
  const localSymbol = fileAnalysis.symbolsByName.get(name);
771
687
  if (localSymbol !== void 0 && localSymbol.kind === kind) return localSymbol.id;
@@ -778,6 +694,14 @@ function resolveReactReference(fileAnalysis, fileAnalyses, name, kind) {
778
694
  if (targetId === void 0) return;
779
695
  return sourceFileAnalysis.symbolsById.get(targetId)?.kind === kind ? targetId : void 0;
780
696
  }
697
+ function addExternalHookNodes(fileAnalyses, nodes) {
698
+ for (const fileAnalysis of fileAnalyses.values()) fileAnalysis.importsByLocalName.forEach((binding, localName) => {
699
+ if (binding.sourcePath !== void 0) return;
700
+ if (!isHookName(localName) && !isHookName(binding.importedName)) return;
701
+ const externalNode = createExternalHookNode(binding, localName);
702
+ if (!nodes.has(externalNode.id)) nodes.set(externalNode.id, externalNode);
703
+ });
704
+ }
781
705
  function createExternalHookNode(binding, localName) {
782
706
  const name = getExternalHookName(binding, localName);
783
707
  return {
@@ -795,54 +719,6 @@ function getExternalHookNodeId(binding, localName) {
795
719
  function getExternalHookName(binding, localName) {
796
720
  return binding.importedName === "default" ? localName : binding.importedName;
797
721
  }
798
- function getFilteredReactUsageNodes(graph, filter) {
799
- return [...graph.nodes.values()].filter((node) => matchesReactFilter(node, filter)).sort((left, right) => compareReactNodes(left, right));
800
- }
801
- function matchesReactFilter(node, filter) {
802
- return filter === "all" || node.kind === filter;
803
- }
804
- function serializeReactUsageNode(nodeId, graph, filter, visited) {
805
- const node = graph.nodes.get(nodeId);
806
- if (node === void 0) return {
807
- id: nodeId,
808
- name: nodeId,
809
- symbolKind: "circular",
810
- filePath: "",
811
- exportNames: [],
812
- usages: []
813
- };
814
- if (visited.has(nodeId)) return {
815
- id: node.id,
816
- name: node.name,
817
- symbolKind: "circular",
818
- filePath: toDisplayPath(node.filePath, graph.cwd),
819
- exportNames: node.exportNames,
820
- usages: []
821
- };
822
- const nextVisited = new Set(visited);
823
- nextVisited.add(nodeId);
824
- return {
825
- id: node.id,
826
- name: node.name,
827
- symbolKind: node.kind,
828
- filePath: toDisplayPath(node.filePath, graph.cwd),
829
- exportNames: node.exportNames,
830
- usages: getFilteredUsages(node, graph, filter).map((usage) => ({
831
- kind: usage.kind,
832
- targetId: usage.target,
833
- node: serializeReactUsageNode(usage.target, graph, filter, nextVisited)
834
- }))
835
- };
836
- }
837
- function serializeReactUsageEntry(entry, graph, filter) {
838
- return {
839
- targetId: entry.target,
840
- filePath: toDisplayPath(entry.location.filePath, graph.cwd),
841
- line: entry.location.line,
842
- column: entry.location.column,
843
- node: serializeReactUsageNode(entry.target, graph, filter, /* @__PURE__ */ new Set())
844
- };
845
- }
846
722
  function compareReactNodeIds(leftId, rightId, nodes) {
847
723
  const left = nodes.get(leftId);
848
724
  const right = nodes.get(rightId);
@@ -850,28 +726,150 @@ function compareReactNodeIds(leftId, rightId, nodes) {
850
726
  return compareReactNodes(left, right);
851
727
  }
852
728
  function compareReactUsageEntries(left, right, nodes) {
853
- return left.location.filePath.localeCompare(right.location.filePath) || left.location.line - right.location.line || left.location.column - right.location.column || compareReactNodeIds(left.target, right.target, nodes);
854
- }
855
- function comparePendingReactUsageEntries(left, right) {
856
- return left.location.filePath.localeCompare(right.location.filePath) || left.location.line - right.location.line || left.location.column - right.location.column || left.kind.localeCompare(right.kind) || left.referenceName.localeCompare(right.referenceName);
729
+ return left.location.filePath.localeCompare(right.location.filePath) || left.location.line - right.location.line || left.location.column - right.location.column || left.referenceName.localeCompare(right.referenceName) || compareReactNodeIds(left.target, right.target, nodes);
857
730
  }
858
731
  function compareReactNodes(left, right) {
859
732
  return left.filePath.localeCompare(right.filePath) || left.name.localeCompare(right.name) || left.kind.localeCompare(right.kind);
860
733
  }
861
- function toModuleExportName(name) {
862
- return name.type === "Literal" ? name.value : name.name;
734
+ //#endregion
735
+ //#region src/analyzers/react/index.ts
736
+ function analyzeReactUsage(entryFile, options = {}) {
737
+ return new ReactAnalyzer(entryFile, options).analyze();
738
+ }
739
+ var ReactAnalyzer = class extends BaseAnalyzer {
740
+ doAnalyze() {
741
+ const dependencyGraph = analyzeDependencies(this.entryFile, this.options);
742
+ const fileAnalyses = this.collectFileAnalyses(dependencyGraph);
743
+ const nodes = this.createNodes(fileAnalyses);
744
+ this.attachUsages(fileAnalyses, nodes);
745
+ const entries = this.collectEntries(fileAnalyses, nodes);
746
+ return {
747
+ cwd: dependencyGraph.cwd,
748
+ entryId: dependencyGraph.entryId,
749
+ nodes,
750
+ entries
751
+ };
752
+ }
753
+ collectFileAnalyses(dependencyGraph) {
754
+ const reachableFiles = new Set([dependencyGraph.entryId, ...dependencyGraph.nodes.keys()]);
755
+ const fileAnalyses = /* @__PURE__ */ new Map();
756
+ for (const filePath of [...reachableFiles].sort()) {
757
+ if (!isSourceCodeFile(filePath) || filePath.endsWith(".d.ts")) continue;
758
+ const sourceText = fs.readFileSync(filePath, "utf8");
759
+ const parseResult = parseSync(filePath, sourceText, {
760
+ astType: "ts",
761
+ sourceType: "unambiguous"
762
+ });
763
+ const dependencyNode = dependencyGraph.nodes.get(filePath);
764
+ const sourceDependencies = /* @__PURE__ */ new Map();
765
+ dependencyNode?.dependencies.forEach((dependency) => {
766
+ if (dependency.kind === "source") sourceDependencies.set(dependency.specifier, dependency.target);
767
+ });
768
+ fileAnalyses.set(filePath, analyzeReactFile(parseResult.program, filePath, sourceText, filePath === dependencyGraph.entryId, sourceDependencies));
769
+ }
770
+ return fileAnalyses;
771
+ }
772
+ createNodes(fileAnalyses) {
773
+ const nodes = /* @__PURE__ */ new Map();
774
+ for (const fileAnalysis of fileAnalyses.values()) for (const symbol of fileAnalysis.symbolsById.values()) nodes.set(symbol.id, {
775
+ id: symbol.id,
776
+ name: symbol.name,
777
+ kind: symbol.kind,
778
+ filePath: symbol.filePath,
779
+ exportNames: [...symbol.exportNames].sort(),
780
+ usages: []
781
+ });
782
+ addExternalHookNodes(fileAnalyses, nodes);
783
+ return nodes;
784
+ }
785
+ attachUsages(fileAnalyses, nodes) {
786
+ for (const fileAnalysis of fileAnalyses.values()) for (const symbol of fileAnalysis.symbolsById.values()) {
787
+ const usages = /* @__PURE__ */ new Map();
788
+ symbol.componentReferences.forEach((referenceName) => {
789
+ const targetId = resolveReactReference(fileAnalysis, fileAnalyses, referenceName, "component");
790
+ if (targetId !== void 0 && targetId !== symbol.id) usages.set(`render:${targetId}:${referenceName}`, {
791
+ kind: "render",
792
+ target: targetId,
793
+ referenceName
794
+ });
795
+ });
796
+ symbol.hookReferences.forEach((referenceName) => {
797
+ const targetId = resolveReactReference(fileAnalysis, fileAnalyses, referenceName, "hook");
798
+ if (targetId !== void 0 && targetId !== symbol.id) usages.set(`hook:${targetId}:${referenceName}`, {
799
+ kind: "hook-call",
800
+ target: targetId,
801
+ referenceName
802
+ });
803
+ });
804
+ const node = nodes.get(symbol.id);
805
+ if (node === void 0) continue;
806
+ const sortedUsages = [...usages.values()].sort((left, right) => compareReactNodeIds(left.target, right.target, nodes));
807
+ nodes.set(symbol.id, {
808
+ ...node,
809
+ usages: sortedUsages
810
+ });
811
+ }
812
+ }
813
+ collectEntries(fileAnalyses, nodes) {
814
+ const entriesByKey = /* @__PURE__ */ new Map();
815
+ for (const fileAnalysis of fileAnalyses.values()) for (const entry of fileAnalysis.entryUsages) {
816
+ const targetId = resolveReactReference(fileAnalysis, fileAnalyses, entry.referenceName, entry.kind);
817
+ if (targetId === void 0) continue;
818
+ const key = `${entry.location.filePath}:${entry.location.line}:${entry.location.column}:${targetId}`;
819
+ entriesByKey.set(key, {
820
+ target: targetId,
821
+ referenceName: entry.referenceName,
822
+ location: entry.location
823
+ });
824
+ }
825
+ return [...entriesByKey.values()].sort((left, right) => compareReactUsageEntries(left, right, nodes));
826
+ }
827
+ };
828
+ //#endregion
829
+ //#region src/analyzers/react/queries.ts
830
+ function getReactUsageEntries(graph, filter = "all") {
831
+ return graph.entries.filter((entry) => {
832
+ const targetNode = graph.nodes.get(entry.target);
833
+ return targetNode !== void 0 && matchesReactFilter(targetNode, filter);
834
+ });
863
835
  }
864
- function isHookName(name) {
865
- return /^use[A-Z0-9]/.test(name);
836
+ function getReactUsageRoots(graph, filter = "all") {
837
+ const entries = getReactUsageEntries(graph, filter);
838
+ if (entries.length > 0) return [...new Set(entries.map((entry) => entry.target))];
839
+ const filteredNodes = getFilteredReactUsageNodes(graph, filter);
840
+ const inboundCounts = /* @__PURE__ */ new Map();
841
+ filteredNodes.forEach((node) => {
842
+ inboundCounts.set(node.id, 0);
843
+ });
844
+ filteredNodes.forEach((node) => {
845
+ getFilteredUsages(node, graph, filter).forEach((usage) => {
846
+ inboundCounts.set(usage.target, (inboundCounts.get(usage.target) ?? 0) + 1);
847
+ });
848
+ });
849
+ const roots = filteredNodes.filter((node) => (inboundCounts.get(node.id) ?? 0) === 0).map((node) => node.id);
850
+ if (roots.length > 0) return roots.sort((left, right) => compareReactNodeIds(left, right, graph.nodes));
851
+ return filteredNodes.map((node) => node.id).sort((left, right) => compareReactNodeIds(left, right, graph.nodes));
866
852
  }
867
- function isComponentName(name) {
868
- return /^[A-Z]/.test(name);
853
+ function getFilteredUsages(node, graph, filter = "all") {
854
+ return node.usages.filter((usage) => {
855
+ const targetNode = graph.nodes.get(usage.target);
856
+ return targetNode !== void 0 && matchesReactFilter(targetNode, filter);
857
+ });
858
+ }
859
+ function getFilteredReactUsageNodes(graph, filter) {
860
+ return [...graph.nodes.values()].filter((node) => matchesReactFilter(node, filter)).sort((left, right) => {
861
+ return left.filePath.localeCompare(right.filePath) || left.name.localeCompare(right.name) || left.kind.localeCompare(right.kind);
862
+ });
863
+ }
864
+ function matchesReactFilter(node, filter) {
865
+ return filter === "all" || node.kind === filter;
869
866
  }
870
867
  //#endregion
871
868
  //#region src/color.ts
872
869
  const ANSI_RESET = "\x1B[0m";
873
870
  const ANSI_COMPONENT = "\x1B[36m";
874
871
  const ANSI_HOOK = "\x1B[35m";
872
+ const ANSI_MUTED = "\x1B[38;5;244m";
875
873
  const ANSI_UNUSED = "\x1B[38;5;214m";
876
874
  function resolveColorSupport(mode = "auto", options = {}) {
877
875
  if (mode === true) return true;
@@ -888,10 +886,86 @@ function colorizeUnusedMarker(text, enabled) {
888
886
  function formatReactSymbolLabel(name, kind, enabled) {
889
887
  const label = `${name} [${kind}]`;
890
888
  if (!enabled) return label;
891
- return `${kind === "component" ? ANSI_COMPONENT : ANSI_HOOK}${label}${ANSI_RESET}`;
889
+ return `${getReactSymbolColor(kind)}${label}${ANSI_RESET}`;
890
+ }
891
+ function colorizeReactLabel(text, kind, enabled) {
892
+ if (!enabled) return text;
893
+ return `${getReactSymbolColor(kind)}${text}${ANSI_RESET}`;
894
+ }
895
+ function colorizeMuted(text, enabled) {
896
+ if (!enabled) return text;
897
+ return `${ANSI_MUTED}${text}${ANSI_RESET}`;
898
+ }
899
+ function getReactSymbolColor(kind) {
900
+ return kind === "component" ? ANSI_COMPONENT : ANSI_HOOK;
901
+ }
902
+ //#endregion
903
+ //#region src/utils/to-display-path.ts
904
+ function toDisplayPath(filePath, cwd) {
905
+ const relativePath = path.relative(cwd, filePath);
906
+ if (relativePath === "") return ".";
907
+ const normalizedPath = relativePath.split(path.sep).join("/");
908
+ return normalizedPath.startsWith("..") ? filePath : normalizedPath;
909
+ }
910
+ //#endregion
911
+ //#region src/output/ascii/import.ts
912
+ function printDependencyTree(graph, options = {}) {
913
+ const cwd = options.cwd ?? graph.cwd;
914
+ const color = resolveColorSupport(options.color);
915
+ const includeExternals = options.includeExternals ?? false;
916
+ const omitUnused = options.omitUnused ?? false;
917
+ const rootLines = [toDisplayPath(graph.entryId, cwd)];
918
+ const visited = new Set([graph.entryId]);
919
+ const entryNode = graph.nodes.get(graph.entryId);
920
+ if (entryNode === void 0) return rootLines.join("\n");
921
+ const rootDependencies = filterDependencies(entryNode.dependencies, includeExternals, omitUnused);
922
+ rootDependencies.forEach((dependency, index) => {
923
+ rootLines.push(...renderDependency(dependency, graph, visited, "", index === rootDependencies.length - 1, includeExternals, omitUnused, color, cwd));
924
+ });
925
+ return rootLines.join("\n");
926
+ }
927
+ function renderDependency(dependency, graph, visited, prefix, isLast, includeExternals, omitUnused, color, cwd) {
928
+ const branch = `${prefix}${isLast ? "└─ " : "├─ "}`;
929
+ const label = formatDependencyLabel(dependency, cwd, color);
930
+ if (dependency.kind !== "source") return [`${branch}${label}`];
931
+ if (visited.has(dependency.target)) return [`${branch}${label} (circular)`];
932
+ const childNode = graph.nodes.get(dependency.target);
933
+ if (childNode === void 0) return [`${branch}${label}`];
934
+ const nextPrefix = `${prefix}${isLast ? " " : "│ "}`;
935
+ const nextVisited = new Set(visited);
936
+ nextVisited.add(dependency.target);
937
+ const childLines = [`${branch}${label}`];
938
+ const childDependencies = filterDependencies(childNode.dependencies, includeExternals, omitUnused);
939
+ childDependencies.forEach((childDependency, index) => {
940
+ childLines.push(...renderDependency(childDependency, graph, nextVisited, nextPrefix, index === childDependencies.length - 1, includeExternals, omitUnused, color, cwd));
941
+ });
942
+ return childLines;
943
+ }
944
+ function filterDependencies(dependencies, includeExternals, omitUnused) {
945
+ return dependencies.filter((dependency) => {
946
+ if (omitUnused && dependency.unused) return false;
947
+ if (dependency.kind === "source" || dependency.kind === "missing") return true;
948
+ return includeExternals;
949
+ });
950
+ }
951
+ function formatDependencyLabel(dependency, cwd, color) {
952
+ const prefixes = [];
953
+ if (dependency.isTypeOnly) prefixes.push("type");
954
+ if (dependency.referenceKind === "require") prefixes.push("require");
955
+ else if (dependency.referenceKind === "dynamic-import") prefixes.push("dynamic");
956
+ else if (dependency.referenceKind === "export") prefixes.push("re-export");
957
+ else if (dependency.referenceKind === "import-equals") prefixes.push("import=");
958
+ const annotation = prefixes.length > 0 ? `[${prefixes.join(", ")}] ` : "";
959
+ if (dependency.kind === "source") return colorizeUnusedMarker(withUnusedSuffix(`${annotation}${toDisplayPath(dependency.target, cwd)}`, dependency.unused), color);
960
+ if (dependency.kind === "missing") return colorizeUnusedMarker(withUnusedSuffix(`${annotation}${dependency.specifier} [missing]`, dependency.unused), color);
961
+ if (dependency.kind === "builtin") return colorizeUnusedMarker(withUnusedSuffix(`${annotation}${dependency.target} [builtin]`, dependency.unused), color);
962
+ return colorizeUnusedMarker(withUnusedSuffix(`${annotation}${dependency.target} [external]`, dependency.unused), color);
963
+ }
964
+ function withUnusedSuffix(label, unused) {
965
+ return unused ? `${label} (unused)` : label;
892
966
  }
893
967
  //#endregion
894
- //#region src/react-tree.ts
968
+ //#region src/output/ascii/react.ts
895
969
  function printReactUsageTree(graph, options = {}) {
896
970
  const cwd = options.cwd ?? graph.cwd;
897
971
  const color = resolveColorSupport(options.color);
@@ -919,7 +993,7 @@ function renderReactUsageEntries(graph, entries, cwd, filter, color) {
919
993
  const root = graph.nodes.get(entry.target);
920
994
  if (root === void 0) return;
921
995
  lines.push(formatReactEntryLabel(entry, cwd));
922
- lines.push(formatReactNodeLabel(root, cwd, color));
996
+ lines.push(formatReactNodeLabel(root, cwd, color, entry.referenceName));
923
997
  const usages = getFilteredUsages(root, graph, filter);
924
998
  usages.forEach((usage, usageIndex) => {
925
999
  lines.push(...renderUsage(usage, graph, cwd, filter, color, new Set([root.id]), "", usageIndex === usages.length - 1));
@@ -932,8 +1006,8 @@ function renderUsage(usage, graph, cwd, filter, color, visited, prefix, isLast)
932
1006
  const branch = `${prefix}${isLast ? "└─ " : "├─ "}`;
933
1007
  const target = graph.nodes.get(usage.target);
934
1008
  if (target === void 0) return [`${branch}${usage.target}`];
935
- if (visited.has(target.id)) return [`${branch}${formatReactNodeLabel(target, cwd, color)} (circular)`];
936
- const childLines = [`${branch}${formatReactNodeLabel(target, cwd, color)}`];
1009
+ if (visited.has(target.id)) return [`${branch}${formatReactNodeLabel(target, cwd, color, usage.referenceName)} (circular)`];
1010
+ const childLines = [`${branch}${formatReactNodeLabel(target, cwd, color, usage.referenceName)}`];
937
1011
  const nextVisited = new Set(visited);
938
1012
  nextVisited.add(target.id);
939
1013
  const nextPrefix = `${prefix}${isLast ? " " : "│ "}`;
@@ -943,72 +1017,114 @@ function renderUsage(usage, graph, cwd, filter, color, visited, prefix, isLast)
943
1017
  });
944
1018
  return childLines;
945
1019
  }
946
- function formatReactNodeLabel(node, cwd, color) {
947
- return `${formatReactSymbolLabel(node.name, node.kind, color)} (${toDisplayPath(node.filePath, cwd)})`;
1020
+ function formatReactNodeLabel(node, cwd, color, referenceName) {
1021
+ return `${referenceName !== void 0 && referenceName !== node.name ? `${colorizeReactLabel(node.name, node.kind, color)} ${colorizeMuted(`as ${referenceName}`, color)} ${colorizeReactLabel(`[${node.kind}]`, node.kind, color)}` : formatReactSymbolLabel(node.name, node.kind, color)} (${toDisplayPath(node.filePath, cwd)})`;
948
1022
  }
949
1023
  function formatReactEntryLabel(entry, cwd) {
950
1024
  return `${toDisplayPath(entry.location.filePath, cwd)}:${entry.location.line}:${entry.location.column}`;
951
1025
  }
952
1026
  //#endregion
953
- //#region src/tree.ts
954
- function printDependencyTree(graph, options = {}) {
955
- const cwd = options.cwd ?? graph.cwd;
956
- const color = resolveColorSupport(options.color);
957
- const includeExternals = options.includeExternals ?? false;
958
- const omitUnused = options.omitUnused ?? false;
959
- const rootLines = [toDisplayPath(graph.entryId, cwd)];
960
- const visited = new Set([graph.entryId]);
961
- const entryNode = graph.nodes.get(graph.entryId);
962
- if (entryNode === void 0) return rootLines.join("\n");
963
- const rootDependencies = filterDependencies(entryNode.dependencies, includeExternals, omitUnused);
964
- rootDependencies.forEach((dependency, index) => {
965
- const lines = renderDependency(dependency, graph, visited, "", index === rootDependencies.length - 1, includeExternals, omitUnused, color, cwd);
966
- rootLines.push(...lines);
967
- });
968
- return rootLines.join("\n");
1027
+ //#region src/output/json/import.ts
1028
+ function graphToSerializableTree(graph, options = {}) {
1029
+ const visited = /* @__PURE__ */ new Set();
1030
+ return serializeNode(graph.entryId, graph, visited, options.omitUnused ?? false);
969
1031
  }
970
- function renderDependency(dependency, graph, visited, prefix, isLast, includeExternals, omitUnused, color, cwd) {
971
- const branch = `${prefix}${isLast ? "└─ " : "├─ "}`;
972
- const label = formatDependencyLabel(dependency, cwd, color);
973
- if (dependency.kind !== "source") return [`${branch}${label}`];
974
- if (visited.has(dependency.target)) return [`${branch}${label} (circular)`];
975
- const childNode = graph.nodes.get(dependency.target);
976
- if (childNode === void 0) return [`${branch}${label}`];
977
- const nextPrefix = `${prefix}${isLast ? " " : "│ "}`;
978
- const nextVisited = new Set(visited);
979
- nextVisited.add(dependency.target);
980
- const childLines = [`${branch}${label}`];
981
- const childDependencies = filterDependencies(childNode.dependencies, includeExternals, omitUnused);
982
- childDependencies.forEach((childDependency, index) => {
983
- const isChildLast = index === childDependencies.length - 1;
984
- childLines.push(...renderDependency(childDependency, graph, nextVisited, nextPrefix, isChildLast, includeExternals, omitUnused, color, cwd));
1032
+ function serializeNode(filePath, graph, visited, omitUnused) {
1033
+ const node = graph.nodes.get(filePath);
1034
+ const displayPath = toDisplayPath(filePath, graph.cwd);
1035
+ if (node === void 0) return {
1036
+ path: displayPath,
1037
+ kind: "missing",
1038
+ dependencies: []
1039
+ };
1040
+ if (visited.has(filePath)) return {
1041
+ path: displayPath,
1042
+ kind: "circular",
1043
+ dependencies: []
1044
+ };
1045
+ visited.add(filePath);
1046
+ const dependencies = node.dependencies.filter((dependency) => !omitUnused || !dependency.unused).map((dependency) => {
1047
+ if (dependency.kind !== "source") return {
1048
+ specifier: dependency.specifier,
1049
+ referenceKind: dependency.referenceKind,
1050
+ isTypeOnly: dependency.isTypeOnly,
1051
+ unused: dependency.unused,
1052
+ kind: dependency.kind,
1053
+ target: dependency.kind === "missing" ? dependency.target : toDisplayPath(dependency.target, graph.cwd)
1054
+ };
1055
+ return {
1056
+ specifier: dependency.specifier,
1057
+ referenceKind: dependency.referenceKind,
1058
+ isTypeOnly: dependency.isTypeOnly,
1059
+ unused: dependency.unused,
1060
+ kind: dependency.kind,
1061
+ target: toDisplayPath(dependency.target, graph.cwd),
1062
+ node: serializeNode(dependency.target, graph, new Set(visited), omitUnused)
1063
+ };
985
1064
  });
986
- return childLines;
1065
+ return {
1066
+ path: displayPath,
1067
+ kind: filePath === graph.entryId ? "entry" : "source",
1068
+ dependencies
1069
+ };
987
1070
  }
988
- function filterDependencies(dependencies, includeExternals, omitUnused) {
989
- return dependencies.filter((dependency) => {
990
- if (omitUnused && dependency.unused) return false;
991
- if (dependency.kind === "source" || dependency.kind === "missing") return true;
992
- return includeExternals;
993
- });
1071
+ //#endregion
1072
+ //#region src/output/json/react.ts
1073
+ function graphToSerializableReactTree(graph, options = {}) {
1074
+ const filter = options.filter ?? "all";
1075
+ const entries = getReactUsageEntries(graph, filter);
1076
+ const roots = entries.length > 0 ? entries.map((entry) => serializeReactUsageNode(entry.target, graph, filter, /* @__PURE__ */ new Set())) : getReactUsageRoots(graph, filter).map((rootId) => serializeReactUsageNode(rootId, graph, filter, /* @__PURE__ */ new Set()));
1077
+ return {
1078
+ kind: "react-usage",
1079
+ entries: entries.map((entry) => serializeReactUsageEntry(entry, graph, filter)),
1080
+ roots
1081
+ };
994
1082
  }
995
- function formatDependencyLabel(dependency, cwd, color) {
996
- const prefixes = [];
997
- if (dependency.isTypeOnly) prefixes.push("type");
998
- if (dependency.referenceKind === "require") prefixes.push("require");
999
- else if (dependency.referenceKind === "dynamic-import") prefixes.push("dynamic");
1000
- else if (dependency.referenceKind === "export") prefixes.push("re-export");
1001
- else if (dependency.referenceKind === "import-equals") prefixes.push("import=");
1002
- const annotation = prefixes.length > 0 ? `[${prefixes.join(", ")}] ` : "";
1003
- if (dependency.kind === "source") return colorizeUnusedMarker(withUnusedSuffix(`${annotation}${toDisplayPath(dependency.target, cwd)}`, dependency.unused), color);
1004
- if (dependency.kind === "missing") return colorizeUnusedMarker(withUnusedSuffix(`${annotation}${dependency.specifier} [missing]`, dependency.unused), color);
1005
- if (dependency.kind === "builtin") return colorizeUnusedMarker(withUnusedSuffix(`${annotation}${dependency.target} [builtin]`, dependency.unused), color);
1006
- return colorizeUnusedMarker(withUnusedSuffix(`${annotation}${dependency.target} [external]`, dependency.unused), color);
1083
+ function serializeReactUsageNode(nodeId, graph, filter, visited) {
1084
+ const node = graph.nodes.get(nodeId);
1085
+ if (node === void 0) return {
1086
+ id: nodeId,
1087
+ name: nodeId,
1088
+ symbolKind: "circular",
1089
+ filePath: "",
1090
+ exportNames: [],
1091
+ usages: []
1092
+ };
1093
+ if (visited.has(nodeId)) return {
1094
+ id: node.id,
1095
+ name: node.name,
1096
+ symbolKind: "circular",
1097
+ filePath: toDisplayPath(node.filePath, graph.cwd),
1098
+ exportNames: node.exportNames,
1099
+ usages: []
1100
+ };
1101
+ const nextVisited = new Set(visited);
1102
+ nextVisited.add(nodeId);
1103
+ return {
1104
+ id: node.id,
1105
+ name: node.name,
1106
+ symbolKind: node.kind,
1107
+ filePath: toDisplayPath(node.filePath, graph.cwd),
1108
+ exportNames: node.exportNames,
1109
+ usages: getFilteredUsages(node, graph, filter).map((usage) => ({
1110
+ kind: usage.kind,
1111
+ targetId: usage.target,
1112
+ referenceName: usage.referenceName,
1113
+ node: serializeReactUsageNode(usage.target, graph, filter, nextVisited)
1114
+ }))
1115
+ };
1007
1116
  }
1008
- function withUnusedSuffix(label, unused) {
1009
- return unused ? `${label} (unused)` : label;
1117
+ function serializeReactUsageEntry(entry, graph, filter) {
1118
+ return {
1119
+ targetId: entry.target,
1120
+ referenceName: entry.referenceName,
1121
+ filePath: toDisplayPath(entry.location.filePath, graph.cwd),
1122
+ line: entry.location.line,
1123
+ column: entry.location.column,
1124
+ node: serializeReactUsageNode(entry.target, graph, filter, /* @__PURE__ */ new Set())
1125
+ };
1010
1126
  }
1011
1127
  //#endregion
1012
- export { graphToSerializableReactTree as a, getReactUsageRoots as i, printReactUsageTree as n, analyzeDependencies as o, analyzeReactUsage as r, graphToSerializableTree as s, printDependencyTree as t };
1128
+ 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 };
1013
1129
 
1014
- //# sourceMappingURL=tree-BX1YFcqX.mjs.map
1130
+ //# sourceMappingURL=react-BHPy_fw5.mjs.map