foresthouse 1.0.0-dev.9 → 1.0.1-dev.1

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,1130 +0,0 @@
1
- import { builtinModules } from "node:module";
2
- import path from "node:path";
3
- import ts from "typescript";
4
- import fs from "node:fs";
5
- import { parseSync, visitorKeys } from "oxc-parser";
6
- import process$1 from "node:process";
7
- //#region src/typescript/config.ts
8
- function loadCompilerOptions(searchFrom, explicitConfigPath) {
9
- const configPath = explicitConfigPath === void 0 ? findNearestConfig(searchFrom) : path.resolve(searchFrom, explicitConfigPath);
10
- if (configPath === void 0) return { compilerOptions: defaultCompilerOptions() };
11
- const readResult = ts.readConfigFile(configPath, ts.sys.readFile);
12
- if (readResult.error !== void 0) throw new Error(`Failed to read TypeScript config at ${configPath}: ${formatDiagnostic(readResult.error)}`);
13
- const parsed = ts.parseJsonConfigFileContent(readResult.config, ts.sys, path.dirname(configPath), defaultCompilerOptions(), configPath);
14
- if (parsed.errors.length > 0) {
15
- const [firstError] = parsed.errors;
16
- if (firstError === void 0) throw new Error(`Failed to parse TypeScript config at ${configPath}.`);
17
- throw new Error(`Failed to parse TypeScript config at ${configPath}: ${formatDiagnostic(firstError)}`);
18
- }
19
- return {
20
- path: configPath,
21
- compilerOptions: parsed.options
22
- };
23
- }
24
- function findNearestConfig(searchFrom) {
25
- let currentDirectory = path.resolve(searchFrom);
26
- while (true) {
27
- const tsconfigPath = path.join(currentDirectory, "tsconfig.json");
28
- if (ts.sys.fileExists(tsconfigPath)) return tsconfigPath;
29
- const jsconfigPath = path.join(currentDirectory, "jsconfig.json");
30
- if (ts.sys.fileExists(jsconfigPath)) return jsconfigPath;
31
- const parentDirectory = path.dirname(currentDirectory);
32
- if (parentDirectory === currentDirectory) return;
33
- currentDirectory = parentDirectory;
34
- }
35
- }
36
- function defaultCompilerOptions() {
37
- return {
38
- allowJs: true,
39
- jsx: ts.JsxEmit.ReactJSX,
40
- module: ts.ModuleKind.NodeNext,
41
- moduleResolution: ts.ModuleResolutionKind.NodeNext,
42
- target: ts.ScriptTarget.ESNext,
43
- resolveJsonModule: true,
44
- esModuleInterop: true
45
- };
46
- }
47
- function formatDiagnostic(diagnostic) {
48
- return ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
49
- }
50
- //#endregion
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
63
- const SOURCE_EXTENSIONS = new Set([
64
- ".js",
65
- ".jsx",
66
- ".ts",
67
- ".tsx",
68
- ".mjs",
69
- ".cjs",
70
- ".mts",
71
- ".cts"
72
- ]);
73
- function isSourceCodeFile(filePath) {
74
- return SOURCE_EXTENSIONS.has(path.extname(filePath).toLowerCase());
75
- }
76
- //#endregion
77
- //#region src/utils/normalize-file-path.ts
78
- function normalizeFilePath(filePath) {
79
- return path.normalize(filePath);
80
- }
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;
88
- }
89
- //#endregion
90
- //#region src/typescript/program.ts
91
- function createProgram(entryFile, compilerOptions, cwd) {
92
- const host = ts.createCompilerHost(compilerOptions, true);
93
- host.getCurrentDirectory = () => cwd;
94
- if (ts.sys.realpath !== void 0) host.realpath = ts.sys.realpath;
95
- return ts.createProgram({
96
- rootNames: [entryFile],
97
- options: compilerOptions,
98
- host
99
- });
100
- }
101
- function createSourceFile(filePath) {
102
- const sourceText = fs.readFileSync(filePath, "utf8");
103
- return ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(filePath));
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
128
- function collectUnusedImports(sourceFile, checker) {
129
- const importUsage = /* @__PURE__ */ new Map();
130
- const symbolToImportDeclaration = /* @__PURE__ */ new Map();
131
- const importedLocalNames = /* @__PURE__ */ new Set();
132
- sourceFile.statements.forEach((statement) => {
133
- if (!ts.isImportDeclaration(statement) || statement.importClause === void 0) return;
134
- const identifiers = getImportBindingIdentifiers(statement.importClause);
135
- if (identifiers.length === 0) return;
136
- importUsage.set(statement, {
137
- canTrack: false,
138
- used: false
139
- });
140
- identifiers.forEach((identifier) => {
141
- importedLocalNames.add(identifier.text);
142
- const symbol = tryGetSymbolAtLocation(checker, identifier);
143
- if (symbol === void 0) return;
144
- symbolToImportDeclaration.set(symbol, statement);
145
- const state = importUsage.get(statement);
146
- if (state !== void 0) state.canTrack = true;
147
- });
148
- });
149
- function visit(node) {
150
- if (ts.isImportDeclaration(node)) return;
151
- if (ts.isIdentifier(node) && importedLocalNames.has(node.text) && isReferenceIdentifier(node)) {
152
- const symbol = tryGetSymbolAtLocation(checker, node);
153
- const declaration = symbol === void 0 ? void 0 : symbolToImportDeclaration.get(symbol);
154
- if (declaration !== void 0) {
155
- const state = importUsage.get(declaration);
156
- if (state !== void 0) state.used = true;
157
- }
158
- }
159
- ts.forEachChild(node, visit);
160
- }
161
- visit(sourceFile);
162
- return new Map([...importUsage.entries()].map(([declaration, state]) => [declaration, state.canTrack && !state.used]));
163
- }
164
- function getImportBindingIdentifiers(importClause) {
165
- const identifiers = [];
166
- if (importClause.name !== void 0) identifiers.push(importClause.name);
167
- const namedBindings = importClause.namedBindings;
168
- if (namedBindings === void 0) return identifiers;
169
- if (ts.isNamespaceImport(namedBindings)) {
170
- identifiers.push(namedBindings.name);
171
- return identifiers;
172
- }
173
- namedBindings.elements.forEach((element) => {
174
- identifiers.push(element.name);
175
- });
176
- return identifiers;
177
- }
178
- function isReferenceIdentifier(node) {
179
- const parent = node.parent;
180
- if (ts.isPropertyAccessExpression(parent) && parent.name === node) return false;
181
- if (ts.isQualifiedName(parent) && parent.right === node) return false;
182
- if (ts.isPropertyAssignment(parent) && parent.name === node) return false;
183
- if (ts.isBindingElement(parent) && parent.propertyName === node) return false;
184
- if (ts.isJsxAttribute(parent) && parent.name === node) return false;
185
- if (ts.isExportSpecifier(parent)) return parent.propertyName === node || parent.propertyName === void 0;
186
- return true;
187
- }
188
- function tryGetSymbolAtLocation(checker, node) {
189
- try {
190
- return checker.getSymbolAtLocation(node);
191
- } catch {
192
- return;
193
- }
194
- }
195
- //#endregion
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
207
- });
208
- return;
209
- }
210
- references.set(key, {
211
- specifier,
212
- referenceKind,
213
- isTypeOnly,
214
- unused
215
- });
216
- }
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);
234
- }
235
- visit(sourceFile);
236
- return [...references.values()];
237
- }
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 }
346
- });
347
- });
348
- }
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);
381
- });
382
- }
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);
423
- });
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;
447
- });
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;
478
- });
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
- }
502
- }
503
- //#endregion
504
- //#region src/analyzers/react/entries.ts
505
- function collectEntryUsages(program, filePath, sourceText, includeNestedFunctions) {
506
- const entries = /* @__PURE__ */ new Map();
507
- program.body.forEach((statement) => {
508
- collectStatementEntryUsages(statement, filePath, sourceText, entries, includeNestedFunctions);
509
- });
510
- return [...entries.values()].sort(comparePendingReactUsageEntries);
511
- }
512
- function collectStatementEntryUsages(statement, filePath, sourceText, entries, includeNestedFunctions) {
513
- collectNodeEntryUsages(statement, filePath, sourceText, entries, false, includeNestedFunctions);
514
- }
515
- function collectNodeEntryUsages(node, filePath, sourceText, entries, hasComponentAncestor, includeNestedFunctions) {
516
- if (!includeNestedFunctions && FUNCTION_NODE_TYPES.has(node.type)) return;
517
- let nextHasComponentAncestor = hasComponentAncestor;
518
- if (node.type === "JSXElement") {
519
- const referenceName = getComponentReferenceName(node);
520
- if (referenceName !== void 0) {
521
- if (!hasComponentAncestor) addPendingReactUsageEntry(entries, referenceName, "component", createReactUsageLocation(filePath, sourceText, node.start));
522
- nextHasComponentAncestor = true;
523
- }
524
- } else if (node.type === "CallExpression") {
525
- const hookReference = getHookReferenceName(node);
526
- if (hookReference !== void 0) addPendingReactUsageEntry(entries, hookReference, "hook", createReactUsageLocation(filePath, sourceText, node.start));
527
- const referenceName = getCreateElementComponentReferenceName(node);
528
- if (referenceName !== void 0) {
529
- if (!hasComponentAncestor) addPendingReactUsageEntry(entries, referenceName, "component", createReactUsageLocation(filePath, sourceText, node.start));
530
- nextHasComponentAncestor = true;
531
- }
532
- }
533
- const keys = visitorKeys[node.type];
534
- if (keys === void 0) return;
535
- keys.forEach((key) => {
536
- const value = node[key];
537
- collectEntryUsageChild(value, filePath, sourceText, entries, nextHasComponentAncestor, includeNestedFunctions);
538
- });
539
- }
540
- function collectEntryUsageChild(value, filePath, sourceText, entries, hasComponentAncestor, includeNestedFunctions) {
541
- if (Array.isArray(value)) {
542
- value.forEach((entry) => {
543
- collectEntryUsageChild(entry, filePath, sourceText, entries, hasComponentAncestor, includeNestedFunctions);
544
- });
545
- return;
546
- }
547
- if (!isNode(value)) return;
548
- collectNodeEntryUsages(value, filePath, sourceText, entries, hasComponentAncestor, includeNestedFunctions);
549
- }
550
- function addPendingReactUsageEntry(entries, referenceName, kind, location) {
551
- const key = `${location.filePath}:${location.line}:${location.column}:${kind}:${referenceName}`;
552
- entries.set(key, {
553
- referenceName,
554
- kind,
555
- location
556
- });
557
- }
558
- function createReactUsageLocation(filePath, sourceText, offset) {
559
- return {
560
- filePath,
561
- ...offsetToLineAndColumn(sourceText, offset)
562
- };
563
- }
564
- function offsetToLineAndColumn(sourceText, offset) {
565
- let line = 1;
566
- let column = 1;
567
- for (let index = 0; index < offset && index < sourceText.length; index += 1) {
568
- if (sourceText[index] === "\n") {
569
- line += 1;
570
- column = 1;
571
- continue;
572
- }
573
- column += 1;
574
- }
575
- return {
576
- line,
577
- column
578
- };
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
585
- function collectTopLevelReactSymbols(statement, filePath, symbolsByName) {
586
- switch (statement.type) {
587
- case "FunctionDeclaration":
588
- addFunctionSymbol(statement, filePath, symbolsByName);
589
- return;
590
- case "VariableDeclaration":
591
- statement.declarations.forEach((declarator) => {
592
- addVariableSymbol(declarator, filePath, symbolsByName);
593
- });
594
- return;
595
- case "ExportNamedDeclaration":
596
- if (statement.declaration !== null) collectTopLevelReactSymbols(statement.declaration, filePath, symbolsByName);
597
- return;
598
- case "ExportDefaultDeclaration":
599
- addDefaultExportSymbol(statement, filePath, symbolsByName);
600
- return;
601
- default: return;
602
- }
603
- }
604
- function addFunctionSymbol(declaration, filePath, symbolsByName) {
605
- const name = declaration.id?.name;
606
- if (name === void 0) return;
607
- const kind = classifyReactSymbol(name, declaration);
608
- if (kind === void 0) return;
609
- symbolsByName.set(name, createPendingSymbol(filePath, name, kind, declaration));
610
- }
611
- function addVariableSymbol(declarator, filePath, symbolsByName) {
612
- if (declarator.id.type !== "Identifier" || declarator.init === null) return;
613
- if (declarator.init.type !== "ArrowFunctionExpression" && declarator.init.type !== "FunctionExpression") return;
614
- const name = declarator.id.name;
615
- const kind = classifyReactSymbol(name, declarator.init);
616
- if (kind === void 0) return;
617
- symbolsByName.set(name, createPendingSymbol(filePath, name, kind, declarator.init));
618
- }
619
- function addDefaultExportSymbol(declaration, filePath, symbolsByName) {
620
- if (declaration.declaration.type === "FunctionDeclaration" || declaration.declaration.type === "FunctionExpression") addFunctionSymbol(declaration.declaration, filePath, symbolsByName);
621
- else if (declaration.declaration.type === "ArrowFunctionExpression") {
622
- const name = "default";
623
- const kind = declaration.declaration.body ? classifyReactSymbol(name, declaration.declaration) : void 0;
624
- if (kind !== void 0) symbolsByName.set(name, createPendingSymbol(filePath, name, kind, declaration.declaration));
625
- }
626
- }
627
- function createPendingSymbol(filePath, name, kind, declaration) {
628
- return {
629
- id: `${filePath}#${kind}:${name}`,
630
- name,
631
- kind,
632
- filePath,
633
- declaration,
634
- exportNames: /* @__PURE__ */ new Set(),
635
- componentReferences: /* @__PURE__ */ new Set(),
636
- hookReferences: /* @__PURE__ */ new Set()
637
- };
638
- }
639
- //#endregion
640
- //#region src/analyzers/react/usage.ts
641
- function analyzeSymbolUsages(symbol) {
642
- const root = symbol.declaration.type === "ArrowFunctionExpression" ? symbol.declaration.body : symbol.declaration.body;
643
- if (root === null) return;
644
- walkReactUsageTree(root, (node) => {
645
- if (node.type === "JSXElement") {
646
- const name = getComponentReferenceName(node);
647
- if (name !== void 0) symbol.componentReferences.add(name);
648
- return;
649
- }
650
- if (node.type === "CallExpression") {
651
- const hookReference = getHookReferenceName(node);
652
- if (hookReference !== void 0) symbol.hookReferences.add(hookReference);
653
- const componentReference = getCreateElementComponentReferenceName(node);
654
- if (componentReference !== void 0) symbol.componentReferences.add(componentReference);
655
- }
656
- });
657
- }
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);
664
- });
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);
670
- });
671
- symbolsByName.forEach((symbol) => {
672
- analyzeSymbolUsages(symbol);
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
- };
682
- }
683
- //#endregion
684
- //#region src/analyzers/react/references.ts
685
- function resolveReactReference(fileAnalysis, fileAnalyses, name, kind) {
686
- const localSymbol = fileAnalysis.symbolsByName.get(name);
687
- if (localSymbol !== void 0 && localSymbol.kind === kind) return localSymbol.id;
688
- const importBinding = fileAnalysis.importsByLocalName.get(name);
689
- if (importBinding === void 0) return;
690
- if (importBinding.sourcePath === void 0) return kind === "hook" ? getExternalHookNodeId(importBinding, name) : void 0;
691
- const sourceFileAnalysis = fileAnalyses.get(importBinding.sourcePath);
692
- if (sourceFileAnalysis === void 0) return;
693
- const targetId = sourceFileAnalysis.exportsByName.get(importBinding.importedName);
694
- if (targetId === void 0) return;
695
- return sourceFileAnalysis.symbolsById.get(targetId)?.kind === kind ? targetId : void 0;
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
- }
705
- function createExternalHookNode(binding, localName) {
706
- const name = getExternalHookName(binding, localName);
707
- return {
708
- id: getExternalHookNodeId(binding, localName),
709
- name,
710
- kind: "hook",
711
- filePath: binding.sourceSpecifier,
712
- exportNames: [binding.importedName],
713
- usages: []
714
- };
715
- }
716
- function getExternalHookNodeId(binding, localName) {
717
- return `external:${binding.sourceSpecifier}#hook:${getExternalHookName(binding, localName)}`;
718
- }
719
- function getExternalHookName(binding, localName) {
720
- return binding.importedName === "default" ? localName : binding.importedName;
721
- }
722
- function compareReactNodeIds(leftId, rightId, nodes) {
723
- const left = nodes.get(leftId);
724
- const right = nodes.get(rightId);
725
- if (left === void 0 || right === void 0) return leftId.localeCompare(rightId);
726
- return compareReactNodes(left, right);
727
- }
728
- function compareReactUsageEntries(left, right, nodes) {
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);
730
- }
731
- function compareReactNodes(left, right) {
732
- return left.filePath.localeCompare(right.filePath) || left.name.localeCompare(right.name) || left.kind.localeCompare(right.kind);
733
- }
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
- });
835
- }
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));
852
- }
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;
866
- }
867
- //#endregion
868
- //#region src/color.ts
869
- const ANSI_RESET = "\x1B[0m";
870
- const ANSI_COMPONENT = "\x1B[36m";
871
- const ANSI_HOOK = "\x1B[35m";
872
- const ANSI_MUTED = "\x1B[38;5;244m";
873
- const ANSI_UNUSED = "\x1B[38;5;214m";
874
- function resolveColorSupport(mode = "auto", options = {}) {
875
- if (mode === true) return true;
876
- if (mode === false) return false;
877
- const forceColor = "forceColor" in options ? options.forceColor : process$1.env.FORCE_COLOR;
878
- if (forceColor !== void 0) return forceColor !== "0";
879
- if (("noColor" in options ? options.noColor : process$1.env.NO_COLOR) !== void 0) return false;
880
- return ("isTTY" in options ? options.isTTY : process$1.stdout.isTTY) === true;
881
- }
882
- function colorizeUnusedMarker(text, enabled) {
883
- if (!enabled) return text;
884
- return text.replaceAll("(unused)", `${ANSI_UNUSED}(unused)${ANSI_RESET}`);
885
- }
886
- function formatReactSymbolLabel(name, kind, enabled) {
887
- const label = `${name} [${kind}]`;
888
- if (!enabled) return label;
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;
966
- }
967
- //#endregion
968
- //#region src/output/ascii/react.ts
969
- function printReactUsageTree(graph, options = {}) {
970
- const cwd = options.cwd ?? graph.cwd;
971
- const color = resolveColorSupport(options.color);
972
- const filter = options.filter ?? "all";
973
- const entries = getReactUsageEntries(graph, filter);
974
- if (entries.length > 0) return renderReactUsageEntries(graph, entries, cwd, filter, color);
975
- const roots = getReactUsageRoots(graph, filter);
976
- if (roots.length === 0) return "No React symbols found.";
977
- const lines = [];
978
- roots.forEach((rootId, index) => {
979
- const root = graph.nodes.get(rootId);
980
- if (root === void 0) return;
981
- lines.push(formatReactNodeLabel(root, cwd, color));
982
- const usages = getFilteredUsages(root, graph, filter);
983
- usages.forEach((usage, usageIndex) => {
984
- lines.push(...renderUsage(usage, graph, cwd, filter, color, new Set([root.id]), "", usageIndex === usages.length - 1));
985
- });
986
- if (index < roots.length - 1) lines.push("");
987
- });
988
- return lines.join("\n");
989
- }
990
- function renderReactUsageEntries(graph, entries, cwd, filter, color) {
991
- const lines = [];
992
- entries.forEach((entry, index) => {
993
- const root = graph.nodes.get(entry.target);
994
- if (root === void 0) return;
995
- lines.push(formatReactEntryLabel(entry, cwd));
996
- lines.push(formatReactNodeLabel(root, cwd, color, entry.referenceName));
997
- const usages = getFilteredUsages(root, graph, filter);
998
- usages.forEach((usage, usageIndex) => {
999
- lines.push(...renderUsage(usage, graph, cwd, filter, color, new Set([root.id]), "", usageIndex === usages.length - 1));
1000
- });
1001
- if (index < entries.length - 1) lines.push("");
1002
- });
1003
- return lines.join("\n");
1004
- }
1005
- function renderUsage(usage, graph, cwd, filter, color, visited, prefix, isLast) {
1006
- const branch = `${prefix}${isLast ? "└─ " : "├─ "}`;
1007
- const target = graph.nodes.get(usage.target);
1008
- if (target === void 0) return [`${branch}${usage.target}`];
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)}`];
1011
- const nextVisited = new Set(visited);
1012
- nextVisited.add(target.id);
1013
- const nextPrefix = `${prefix}${isLast ? " " : "│ "}`;
1014
- const childUsages = getFilteredUsages(target, graph, filter);
1015
- childUsages.forEach((childUsage, index) => {
1016
- childLines.push(...renderUsage(childUsage, graph, cwd, filter, color, nextVisited, nextPrefix, index === childUsages.length - 1));
1017
- });
1018
- return childLines;
1019
- }
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)})`;
1022
- }
1023
- function formatReactEntryLabel(entry, cwd) {
1024
- return `${toDisplayPath(entry.location.filePath, cwd)}:${entry.location.line}:${entry.location.column}`;
1025
- }
1026
- //#endregion
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);
1031
- }
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
- };
1064
- });
1065
- return {
1066
- path: displayPath,
1067
- kind: filePath === graph.entryId ? "entry" : "source",
1068
- dependencies
1069
- };
1070
- }
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
- };
1082
- }
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
- };
1116
- }
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
- };
1126
- }
1127
- //#endregion
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 };
1129
-
1130
- //# sourceMappingURL=react-BHPy_fw5.mjs.map