hermex 0.0.1-alpha.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -21
- package/dist/cli.js +1163 -2550
- package/dist/cli.js.map +1 -1
- package/package.json +18 -20
package/dist/cli.js
CHANGED
|
@@ -3,2694 +3,1307 @@
|
|
|
3
3
|
|
|
4
4
|
var commander = require('commander');
|
|
5
5
|
var glob = require('glob');
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var path = require('path');
|
|
9
|
-
var Table2 = require('cli-table3');
|
|
6
|
+
var ora = require('ora');
|
|
7
|
+
var chalk = require('chalk');
|
|
10
8
|
var core = require('@swc/core');
|
|
11
|
-
var
|
|
12
|
-
var
|
|
13
|
-
var
|
|
14
|
-
var yaml = require('js-yaml');
|
|
15
|
-
var lockfile = require('@yarnpkg/lockfile');
|
|
9
|
+
var fs = require('fs');
|
|
10
|
+
var path = require('path');
|
|
11
|
+
var Table = require('cli-table3');
|
|
16
12
|
|
|
17
13
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
18
14
|
|
|
19
|
-
var
|
|
20
|
-
var
|
|
15
|
+
var ora__default = /*#__PURE__*/_interopDefault(ora);
|
|
16
|
+
var chalk__default = /*#__PURE__*/_interopDefault(chalk);
|
|
17
|
+
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
21
18
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
22
|
-
var
|
|
23
|
-
var fs5__default = /*#__PURE__*/_interopDefault(fs5);
|
|
24
|
-
var simpleGit__default = /*#__PURE__*/_interopDefault(simpleGit);
|
|
25
|
-
var tmp__default = /*#__PURE__*/_interopDefault(tmp);
|
|
26
|
-
var yaml__default = /*#__PURE__*/_interopDefault(yaml);
|
|
27
|
-
var lockfile__default = /*#__PURE__*/_interopDefault(lockfile);
|
|
19
|
+
var Table__default = /*#__PURE__*/_interopDefault(Table);
|
|
28
20
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const ast = core.parseSync(code, {
|
|
70
|
-
syntax: "typescript",
|
|
71
|
-
tsx: true,
|
|
72
|
-
decorators: true,
|
|
73
|
-
dynamicImport: true
|
|
74
|
-
});
|
|
75
|
-
this.visitNode(ast);
|
|
76
|
-
return this.generateReport();
|
|
77
|
-
} catch (error) {
|
|
78
|
-
console.error(`\u274C Error parsing ${filePath}:`, error.message);
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
visitNode(node, parent = null) {
|
|
83
|
-
if (!node) return;
|
|
84
|
-
switch (node.type) {
|
|
85
|
-
case "Module":
|
|
86
|
-
node.body.forEach((item) => this.visitNode(item, node));
|
|
87
|
-
break;
|
|
88
|
-
case "ImportDeclaration":
|
|
89
|
-
this.analyzeImport(node);
|
|
90
|
-
break;
|
|
91
|
-
case "CallExpression":
|
|
92
|
-
this.analyzeCallExpression(node, parent);
|
|
93
|
-
break;
|
|
94
|
-
case "VariableDeclaration":
|
|
95
|
-
this.analyzeVariableDeclaration(node);
|
|
96
|
-
break;
|
|
97
|
-
case "JSXElement":
|
|
98
|
-
case "JSXFragment":
|
|
99
|
-
this.analyzeJSXElement(node, parent);
|
|
100
|
-
break;
|
|
101
|
-
case "JSXOpeningElement":
|
|
102
|
-
this.analyzeJSXOpeningElement(node, parent);
|
|
103
|
-
break;
|
|
104
|
-
case "ArrayExpression":
|
|
105
|
-
this.analyzeArrayExpression(node, parent);
|
|
106
|
-
break;
|
|
107
|
-
case "ObjectExpression":
|
|
108
|
-
this.analyzeObjectExpression(node, parent);
|
|
109
|
-
break;
|
|
110
|
-
case "MemberExpression":
|
|
111
|
-
this.analyzeMemberExpression(node, parent);
|
|
112
|
-
break;
|
|
113
|
-
case "ConditionalExpression":
|
|
114
|
-
this.analyzeConditionalExpression(node, parent);
|
|
115
|
-
break;
|
|
116
|
-
case "FunctionDeclaration":
|
|
117
|
-
case "FunctionExpression":
|
|
118
|
-
case "ArrowFunctionExpression":
|
|
119
|
-
this.analyzeFunctionDeclaration(node);
|
|
21
|
+
// src/swc-parser/core/state.ts
|
|
22
|
+
function createState() {
|
|
23
|
+
const usagePatterns = {
|
|
24
|
+
directImports: /* @__PURE__ */ new Set(),
|
|
25
|
+
namedImports: /* @__PURE__ */ new Set(),
|
|
26
|
+
namespaceImports: /* @__PURE__ */ new Set(),
|
|
27
|
+
defaultImports: /* @__PURE__ */ new Set(),
|
|
28
|
+
aliasedImports: /* @__PURE__ */ new Map(),
|
|
29
|
+
variableAssignments: /* @__PURE__ */ new Map(),
|
|
30
|
+
componentMappings: /* @__PURE__ */ new Set(),
|
|
31
|
+
lazyImports: /* @__PURE__ */ new Set(),
|
|
32
|
+
dynamicImports: /* @__PURE__ */ new Set(),
|
|
33
|
+
conditionalUsage: /* @__PURE__ */ new Set(),
|
|
34
|
+
arrayMappings: /* @__PURE__ */ new Set(),
|
|
35
|
+
objectMappings: /* @__PURE__ */ new Set(),
|
|
36
|
+
hocUsage: /* @__PURE__ */ new Set(),
|
|
37
|
+
renderProps: /* @__PURE__ */ new Set(),
|
|
38
|
+
contextUsage: /* @__PURE__ */ new Set(),
|
|
39
|
+
forwardedRefs: /* @__PURE__ */ new Set(),
|
|
40
|
+
memoizedComponents: /* @__PURE__ */ new Set(),
|
|
41
|
+
portalUsage: /* @__PURE__ */ new Set(),
|
|
42
|
+
jsxUsage: /* @__PURE__ */ new Map(),
|
|
43
|
+
destructuredUsage: /* @__PURE__ */ new Set(),
|
|
44
|
+
propsAnalysis: /* @__PURE__ */ new Map()
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
usagePatterns,
|
|
48
|
+
componentNames: /* @__PURE__ */ new Set(),
|
|
49
|
+
allIdentifiers: /* @__PURE__ */ new Set()
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/swc-parser/patterns/imports.ts
|
|
54
|
+
function analyzeImportDeclaration(node, state) {
|
|
55
|
+
const source = node.source.value;
|
|
56
|
+
console.log(`\u{1F4E6} Found import: ${source}`);
|
|
57
|
+
for (const spec of node.specifiers) {
|
|
58
|
+
switch (spec.type) {
|
|
59
|
+
case "ImportDefaultSpecifier":
|
|
60
|
+
analyzeDefaultImport(spec, source, node, state);
|
|
120
61
|
break;
|
|
121
|
-
case "
|
|
122
|
-
|
|
62
|
+
case "ImportNamespaceSpecifier":
|
|
63
|
+
analyzeNamespaceImport(spec, source, node, state);
|
|
123
64
|
break;
|
|
124
|
-
|
|
125
|
-
|
|
65
|
+
case "ImportSpecifier":
|
|
66
|
+
analyzeNamedImport(spec, source, node, state);
|
|
126
67
|
break;
|
|
127
68
|
}
|
|
128
69
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const localName = spec.local.value;
|
|
166
|
-
this.usagePatterns.namedImports.add({
|
|
167
|
-
imported: importedName,
|
|
168
|
-
local: localName,
|
|
169
|
-
source,
|
|
170
|
-
line: ((_c = node.span) == null ? void 0 : _c.start) || 0
|
|
171
|
-
});
|
|
172
|
-
if (importedName !== localName) {
|
|
173
|
-
this.usagePatterns.aliasedImports.set(localName, {
|
|
174
|
-
original: importedName,
|
|
175
|
-
source,
|
|
176
|
-
line: ((_d = node.span) == null ? void 0 : _d.start) || 0
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
this.componentNames.add(localName);
|
|
180
|
-
break;
|
|
181
|
-
}
|
|
70
|
+
}
|
|
71
|
+
function analyzeDefaultImport(spec, source, node, state) {
|
|
72
|
+
var _a;
|
|
73
|
+
const name = spec.local.value;
|
|
74
|
+
state.usagePatterns.defaultImports.add({
|
|
75
|
+
name,
|
|
76
|
+
source,
|
|
77
|
+
line: ((_a = node.span) == null ? void 0 : _a.start) || 0
|
|
78
|
+
});
|
|
79
|
+
state.componentNames.add(name);
|
|
80
|
+
}
|
|
81
|
+
function analyzeNamespaceImport(spec, source, node, state) {
|
|
82
|
+
var _a;
|
|
83
|
+
const name = spec.local.value;
|
|
84
|
+
state.usagePatterns.namespaceImports.add({
|
|
85
|
+
name,
|
|
86
|
+
source,
|
|
87
|
+
line: ((_a = node.span) == null ? void 0 : _a.start) || 0
|
|
88
|
+
});
|
|
89
|
+
state.allIdentifiers.add(name);
|
|
90
|
+
}
|
|
91
|
+
function analyzeNamedImport(spec, source, node, state) {
|
|
92
|
+
var _a, _b;
|
|
93
|
+
const importedName = spec.imported ? spec.imported.value : spec.local.value;
|
|
94
|
+
const localName = spec.local.value;
|
|
95
|
+
state.usagePatterns.namedImports.add({
|
|
96
|
+
name: importedName,
|
|
97
|
+
source,
|
|
98
|
+
line: ((_a = node.span) == null ? void 0 : _a.start) || 0
|
|
99
|
+
});
|
|
100
|
+
if (importedName !== localName) {
|
|
101
|
+
state.usagePatterns.aliasedImports.set(localName, {
|
|
102
|
+
imported: importedName,
|
|
103
|
+
local: localName,
|
|
104
|
+
source,
|
|
105
|
+
line: ((_b = node.span) == null ? void 0 : _b.start) || 0
|
|
182
106
|
});
|
|
183
107
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
108
|
+
state.componentNames.add(localName);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/swc-parser/utils/jsx-helpers.ts
|
|
112
|
+
function getJSXElementName(nameNode) {
|
|
113
|
+
if (!nameNode) return "";
|
|
114
|
+
switch (nameNode.type) {
|
|
115
|
+
case "Identifier":
|
|
116
|
+
return nameNode.value;
|
|
117
|
+
case "JSXMemberExpression":
|
|
118
|
+
return `${getJSXElementName(nameNode.object)}.${nameNode.property.value}`;
|
|
119
|
+
default:
|
|
120
|
+
return "";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function isMemberExpressionComponent(nameNode, state) {
|
|
124
|
+
if ((nameNode == null ? void 0 : nameNode.type) === "JSXMemberExpression") {
|
|
125
|
+
const objectName = getJSXElementName(nameNode.object);
|
|
126
|
+
return state.allIdentifiers.has(objectName);
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
function extractJSXProps(attributes) {
|
|
131
|
+
if (!attributes) return [];
|
|
132
|
+
return attributes.map((attr) => {
|
|
133
|
+
var _a, _b, _c;
|
|
134
|
+
if (attr.type === "JSXAttribute") {
|
|
135
|
+
return {
|
|
136
|
+
name: ((_a = attr.name) == null ? void 0 : _a.value) || ((_c = (_b = attr.name) == null ? void 0 : _b.name) == null ? void 0 : _c.value),
|
|
137
|
+
value: extractJSXAttributeValue(attr.value)
|
|
138
|
+
};
|
|
201
139
|
}
|
|
202
|
-
if (
|
|
203
|
-
|
|
140
|
+
if (attr.type === "SpreadElement") {
|
|
141
|
+
return {
|
|
142
|
+
name: "...",
|
|
143
|
+
value: "[spread]",
|
|
144
|
+
isSpread: true
|
|
145
|
+
};
|
|
204
146
|
}
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
147
|
+
return null;
|
|
148
|
+
}).filter(Boolean);
|
|
149
|
+
}
|
|
150
|
+
function extractJSXAttributeValue(value) {
|
|
151
|
+
if (!value) return true;
|
|
152
|
+
switch (value.type) {
|
|
153
|
+
case "StringLiteral":
|
|
154
|
+
return value.value;
|
|
155
|
+
case "JSXExpressionContainer":
|
|
156
|
+
return extractExpressionValue(value.expression);
|
|
157
|
+
default:
|
|
158
|
+
return "[complex]";
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function extractExpressionValue(expr) {
|
|
162
|
+
if (!expr) return "[unknown]";
|
|
163
|
+
switch (expr.type) {
|
|
164
|
+
case "StringLiteral":
|
|
165
|
+
case "NumericLiteral":
|
|
166
|
+
case "BooleanLiteral":
|
|
167
|
+
return expr.value;
|
|
168
|
+
case "Identifier":
|
|
169
|
+
return `{${expr.value}}`;
|
|
170
|
+
case "ArrowFunctionExpression":
|
|
171
|
+
case "FunctionExpression":
|
|
172
|
+
return "[function]";
|
|
173
|
+
case "ObjectExpression":
|
|
174
|
+
return "[object]";
|
|
175
|
+
case "ArrayExpression":
|
|
176
|
+
return "[array]";
|
|
177
|
+
default:
|
|
178
|
+
return "[expression]";
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function getUsageContext(parent) {
|
|
182
|
+
if (!parent) return "direct";
|
|
183
|
+
switch (parent.type) {
|
|
184
|
+
case "ConditionalExpression":
|
|
185
|
+
return "conditional";
|
|
186
|
+
case "ArrayExpression":
|
|
187
|
+
return "array";
|
|
188
|
+
case "ObjectExpression":
|
|
189
|
+
return "object";
|
|
190
|
+
case "CallExpression":
|
|
191
|
+
return "hoc";
|
|
192
|
+
case "VariableDeclarator":
|
|
193
|
+
return "variable";
|
|
194
|
+
default:
|
|
195
|
+
return "jsx";
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/swc-parser/patterns/props.ts
|
|
200
|
+
function analyzePropsInDetail(attributes, componentName, state) {
|
|
201
|
+
var _a, _b, _c;
|
|
202
|
+
const analysis = {
|
|
203
|
+
namedProps: [],
|
|
204
|
+
hasSpread: false,
|
|
205
|
+
hasComplexProps: false,
|
|
206
|
+
hasEventHandlers: false,
|
|
207
|
+
propDetails: []
|
|
208
|
+
};
|
|
209
|
+
if (!attributes) return analysis;
|
|
210
|
+
for (const attr of attributes) {
|
|
211
|
+
if (attr.type === "JSXAttribute") {
|
|
212
|
+
const propName = ((_a = attr.name) == null ? void 0 : _a.value) || ((_c = (_b = attr.name) == null ? void 0 : _b.name) == null ? void 0 : _c.value);
|
|
213
|
+
if (propName) {
|
|
214
|
+
analysis.namedProps.push(propName);
|
|
215
|
+
const propDetail = {
|
|
216
|
+
name: propName,
|
|
217
|
+
type: getPropType(attr.value),
|
|
218
|
+
isEventHandler: propName.startsWith("on"),
|
|
219
|
+
isComplex: isComplexProp(attr.value)
|
|
220
|
+
};
|
|
221
|
+
if (propDetail.isEventHandler) {
|
|
222
|
+
analysis.hasEventHandlers = true;
|
|
223
|
+
}
|
|
224
|
+
if (propDetail.isComplex) {
|
|
225
|
+
analysis.hasComplexProps = true;
|
|
220
226
|
}
|
|
227
|
+
analysis.propDetails.push(propDetail);
|
|
221
228
|
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
229
|
+
} else if (attr.type === "SpreadElement") {
|
|
230
|
+
analysis.hasSpread = true;
|
|
231
|
+
analysis.propDetails.push({
|
|
232
|
+
name: "...",
|
|
233
|
+
type: "spread",
|
|
234
|
+
isSpread: true,
|
|
235
|
+
isComplex: true,
|
|
236
|
+
isEventHandler: false,
|
|
237
|
+
warning: "Spread props cannot be statically analyzed"
|
|
231
238
|
});
|
|
232
|
-
|
|
239
|
+
analysis.hasComplexProps = true;
|
|
233
240
|
}
|
|
234
241
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
242
|
+
state.usagePatterns.propsAnalysis.set(componentName, analysis);
|
|
243
|
+
return analysis;
|
|
244
|
+
}
|
|
245
|
+
function getPropType(value) {
|
|
246
|
+
if (!value) return "boolean";
|
|
247
|
+
switch (value.type) {
|
|
248
|
+
case "StringLiteral":
|
|
249
|
+
return "string";
|
|
250
|
+
case "JSXExpressionContainer": {
|
|
251
|
+
const expr = value.expression;
|
|
252
|
+
if (!expr) return "unknown";
|
|
253
|
+
switch (expr.type) {
|
|
254
|
+
case "NumericLiteral":
|
|
255
|
+
return "number";
|
|
256
|
+
case "BooleanLiteral":
|
|
257
|
+
return "boolean";
|
|
258
|
+
case "StringLiteral":
|
|
259
|
+
return "string";
|
|
260
|
+
case "ArrowFunctionExpression":
|
|
261
|
+
case "FunctionExpression":
|
|
262
|
+
return "function";
|
|
263
|
+
case "ObjectExpression":
|
|
264
|
+
return "object";
|
|
265
|
+
case "ArrayExpression":
|
|
266
|
+
return "array";
|
|
267
|
+
case "Identifier":
|
|
268
|
+
return "variable";
|
|
269
|
+
default:
|
|
270
|
+
return "expression";
|
|
255
271
|
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
272
|
+
}
|
|
273
|
+
default:
|
|
274
|
+
return "unknown";
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function isComplexProp(value) {
|
|
278
|
+
if (!value) return false;
|
|
279
|
+
if (value.type === "JSXExpressionContainer") {
|
|
280
|
+
const expr = value.expression;
|
|
281
|
+
if (!expr) return false;
|
|
282
|
+
return expr.type === "ObjectExpression" || expr.type === "ArrayExpression" || expr.type === "CallExpression" || expr.type === "ConditionalExpression";
|
|
283
|
+
}
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// src/swc-parser/patterns/jsx.ts
|
|
288
|
+
function analyzeJSXElement(node, state) {
|
|
289
|
+
if (node.opening) {
|
|
290
|
+
analyzeJSXOpeningElement(node.opening, state, node);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function analyzeJSXOpeningElement(node, state, parent) {
|
|
294
|
+
var _a;
|
|
295
|
+
const elementName = getJSXElementName(node.name);
|
|
296
|
+
if (!state.componentNames.has(elementName) && !isMemberExpressionComponent(node.name, state)) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const propsAnalysis = analyzePropsInDetail(
|
|
300
|
+
node.attributes,
|
|
301
|
+
elementName,
|
|
302
|
+
state
|
|
303
|
+
);
|
|
304
|
+
const usage = {
|
|
305
|
+
component: elementName,
|
|
306
|
+
props: extractJSXProps(node.attributes).map((p) => p.name),
|
|
307
|
+
propsAnalysis,
|
|
308
|
+
line: ((_a = node.span) == null ? void 0 : _a.start) || 0,
|
|
309
|
+
context: getUsageContext(parent)
|
|
310
|
+
};
|
|
311
|
+
if (!state.usagePatterns.jsxUsage.has(elementName)) {
|
|
312
|
+
state.usagePatterns.jsxUsage.set(elementName, usage);
|
|
313
|
+
}
|
|
314
|
+
console.log(`\u{1F3A8} JSX Usage: <${elementName}>`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/swc-parser/utils/matchers.ts
|
|
318
|
+
function isKnownComponent(name, state) {
|
|
319
|
+
return state.componentNames.has(name) || state.allIdentifiers.has(name);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/swc-parser/patterns/variables.ts
|
|
323
|
+
function analyzeVariableDeclaration(node, state) {
|
|
324
|
+
var _a, _b, _c;
|
|
325
|
+
if (!node.declarations) return;
|
|
326
|
+
for (const decl of node.declarations) {
|
|
327
|
+
if (((_a = decl.id) == null ? void 0 : _a.type) === "Identifier") {
|
|
328
|
+
const varName = decl.id.value;
|
|
329
|
+
if (decl.init) {
|
|
330
|
+
const assignment = extractAssignmentInfo(decl.init);
|
|
331
|
+
if (assignment && isKnownComponent(assignment, state)) {
|
|
332
|
+
state.usagePatterns.variableAssignments.set(varName, {
|
|
333
|
+
assignment,
|
|
334
|
+
line: ((_b = node.span) == null ? void 0 : _b.start) || 0
|
|
270
335
|
});
|
|
271
|
-
|
|
272
|
-
console.log(`\u{
|
|
336
|
+
state.componentNames.add(varName);
|
|
337
|
+
console.log(`\u{1F4DD} Variable assignment: ${varName} = ${assignment}`);
|
|
273
338
|
}
|
|
274
339
|
}
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
analyzeJSXElement(node, parent) {
|
|
278
|
-
if (node.opening) {
|
|
279
|
-
this.analyzeJSXOpeningElement(node.opening, node);
|
|
280
340
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
this.usagePatterns.jsxUsage.get(elementName).push(usage);
|
|
299
|
-
if (!this.usagePatterns.propsAnalysis.has(elementName)) {
|
|
300
|
-
this.usagePatterns.propsAnalysis.set(elementName, {
|
|
301
|
-
namedProps: /* @__PURE__ */ new Set(),
|
|
302
|
-
spreadProps: 0,
|
|
303
|
-
totalUsages: 0,
|
|
304
|
-
complexProps: 0
|
|
341
|
+
if (((_c = decl.id) == null ? void 0 : _c.type) === "ObjectPattern") {
|
|
342
|
+
analyzeDestructuringPattern(decl.id, decl.init, state);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function analyzeDestructuringPattern(pattern, init, state) {
|
|
347
|
+
var _a, _b;
|
|
348
|
+
if (!pattern.properties) return;
|
|
349
|
+
for (const prop of pattern.properties) {
|
|
350
|
+
if (prop.type === "AssignmentPatternProperty" && ((_a = prop.key) == null ? void 0 : _a.type) === "Identifier") {
|
|
351
|
+
const propName = prop.key.value;
|
|
352
|
+
if ((init == null ? void 0 : init.type) === "Identifier" && state.allIdentifiers.has(init.value)) {
|
|
353
|
+
state.usagePatterns.destructuredUsage.add({
|
|
354
|
+
property: propName,
|
|
355
|
+
source: init.value,
|
|
356
|
+
line: ((_b = pattern.span) == null ? void 0 : _b.start) || 0
|
|
305
357
|
});
|
|
358
|
+
state.componentNames.add(propName);
|
|
359
|
+
console.log(`\u{1F527} Destructuring: ${propName} from ${init.value}`);
|
|
306
360
|
}
|
|
307
|
-
const componentPropsStats = this.usagePatterns.propsAnalysis.get(elementName);
|
|
308
|
-
componentPropsStats.totalUsages++;
|
|
309
|
-
propsAnalysis.namedProps.forEach(
|
|
310
|
-
(prop) => componentPropsStats.namedProps.add(prop)
|
|
311
|
-
);
|
|
312
|
-
if (propsAnalysis.hasSpread) componentPropsStats.spreadProps++;
|
|
313
|
-
if (propsAnalysis.hasComplexProps) componentPropsStats.complexProps++;
|
|
314
|
-
console.log(`\u{1F3A8} JSX Usage: <${elementName}>`);
|
|
315
361
|
}
|
|
316
362
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
363
|
+
}
|
|
364
|
+
function extractAssignmentInfo(node) {
|
|
365
|
+
switch (node.type) {
|
|
366
|
+
case "Identifier":
|
|
367
|
+
return node.value;
|
|
368
|
+
case "MemberExpression":
|
|
369
|
+
return `${extractAssignmentInfo(node.object)}.${node.property.value}`;
|
|
370
|
+
case "ConditionalExpression":
|
|
371
|
+
return `${extractAssignmentInfo(node.consequent)} | ${extractAssignmentInfo(node.alternate)}`;
|
|
372
|
+
default:
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// src/swc-parser/patterns/conditionals.ts
|
|
378
|
+
function analyzeConditionalExpression(node, state) {
|
|
379
|
+
var _a, _b, _c;
|
|
380
|
+
const consequent = ((_a = node.consequent) == null ? void 0 : _a.type) === "Identifier" ? node.consequent.value : null;
|
|
381
|
+
const alternate = ((_b = node.alternate) == null ? void 0 : _b.type) === "Identifier" ? node.alternate.value : null;
|
|
382
|
+
if (consequent && state.componentNames.has(consequent) || alternate && state.componentNames.has(alternate)) {
|
|
383
|
+
state.usagePatterns.conditionalUsage.add({
|
|
384
|
+
consequent: consequent || "",
|
|
385
|
+
alternate: alternate || "",
|
|
386
|
+
line: ((_c = node.span) == null ? void 0 : _c.start) || 0
|
|
387
|
+
});
|
|
388
|
+
console.log("\u{1F500} Conditional component usage found");
|
|
327
389
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/swc-parser/patterns/collections.ts
|
|
393
|
+
function analyzeArrayExpression(node, state) {
|
|
394
|
+
var _a, _b, _c;
|
|
395
|
+
const hasComponents = (_a = node.elements) == null ? void 0 : _a.some((elem) => {
|
|
396
|
+
if ((elem == null ? void 0 : elem.type) === "Identifier") {
|
|
397
|
+
return state.componentNames.has(elem.value);
|
|
332
398
|
}
|
|
333
399
|
return false;
|
|
400
|
+
});
|
|
401
|
+
if (hasComponents) {
|
|
402
|
+
state.usagePatterns.arrayMappings.add({
|
|
403
|
+
components: (_b = node.elements) == null ? void 0 : _b.map((elem) => elem == null ? void 0 : elem.value).filter(Boolean),
|
|
404
|
+
line: ((_c = node.span) == null ? void 0 : _c.start) || 0
|
|
405
|
+
});
|
|
406
|
+
console.log("\u{1F4CB} Array with components found");
|
|
334
407
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
408
|
+
}
|
|
409
|
+
function analyzeObjectExpression(node, state) {
|
|
410
|
+
var _a, _b;
|
|
411
|
+
const componentProps = (_a = node.properties) == null ? void 0 : _a.filter((prop) => {
|
|
412
|
+
var _a2;
|
|
413
|
+
if (prop.type === "KeyValueProperty" && ((_a2 = prop.value) == null ? void 0 : _a2.type) === "Identifier") {
|
|
414
|
+
return state.componentNames.has(prop.value.value);
|
|
415
|
+
}
|
|
416
|
+
return false;
|
|
417
|
+
});
|
|
418
|
+
if ((componentProps == null ? void 0 : componentProps.length) > 0) {
|
|
419
|
+
state.usagePatterns.objectMappings.add({
|
|
420
|
+
mappings: componentProps.map((prop) => {
|
|
421
|
+
var _a2, _b2;
|
|
345
422
|
return {
|
|
346
|
-
|
|
347
|
-
value:
|
|
348
|
-
isSpread: true
|
|
423
|
+
key: ((_a2 = prop.key) == null ? void 0 : _a2.value) || "[computed]",
|
|
424
|
+
component: (_b2 = prop.value) == null ? void 0 : _b2.value
|
|
349
425
|
};
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
})
|
|
353
|
-
|
|
354
|
-
extractJSXAttributeValue(value) {
|
|
355
|
-
if (!value) return true;
|
|
356
|
-
switch (value.type) {
|
|
357
|
-
case "StringLiteral":
|
|
358
|
-
return value.value;
|
|
359
|
-
case "JSXExpressionContainer":
|
|
360
|
-
return this.extractExpressionValue(value.expression);
|
|
361
|
-
default:
|
|
362
|
-
return "[complex]";
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
extractExpressionValue(expr) {
|
|
366
|
-
if (!expr) return "[unknown]";
|
|
367
|
-
switch (expr.type) {
|
|
368
|
-
case "StringLiteral":
|
|
369
|
-
case "NumericLiteral":
|
|
370
|
-
case "BooleanLiteral":
|
|
371
|
-
return expr.value;
|
|
372
|
-
case "Identifier":
|
|
373
|
-
return `{${expr.value}}`;
|
|
374
|
-
case "ArrowFunctionExpression":
|
|
375
|
-
case "FunctionExpression":
|
|
376
|
-
return "[function]";
|
|
377
|
-
case "ObjectExpression":
|
|
378
|
-
return "[object]";
|
|
379
|
-
case "ArrayExpression":
|
|
380
|
-
return "[array]";
|
|
381
|
-
default:
|
|
382
|
-
return "[expression]";
|
|
383
|
-
}
|
|
426
|
+
}),
|
|
427
|
+
line: ((_b = node.span) == null ? void 0 : _b.start) || 0
|
|
428
|
+
});
|
|
429
|
+
console.log("\u{1F5FA}\uFE0F Object mapping with components found");
|
|
384
430
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
if (
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
analysis.namedProps.add(propName);
|
|
400
|
-
const propDetail = {
|
|
401
|
-
name: propName,
|
|
402
|
-
type: this.getPropType(attr.value),
|
|
403
|
-
isEventHandler: propName.startsWith("on"),
|
|
404
|
-
isComplex: this.isComplexProp(attr.value)
|
|
405
|
-
};
|
|
406
|
-
if (propDetail.isEventHandler) {
|
|
407
|
-
analysis.hasEventHandlers = true;
|
|
408
|
-
}
|
|
409
|
-
if (propDetail.isComplex) {
|
|
410
|
-
analysis.hasComplexProps = true;
|
|
411
|
-
}
|
|
412
|
-
analysis.propDetails.push(propDetail);
|
|
413
|
-
}
|
|
414
|
-
} else if (attr.type === "SpreadElement") {
|
|
415
|
-
analysis.hasSpread = true;
|
|
416
|
-
analysis.propDetails.push({
|
|
417
|
-
name: "...",
|
|
418
|
-
type: "spread",
|
|
419
|
-
isSpread: true,
|
|
420
|
-
isComplex: true,
|
|
421
|
-
warning: "Spread props cannot be statically analyzed"
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// src/swc-parser/patterns/lazy-dynamic.ts
|
|
434
|
+
function analyzeLazyImport(node, state) {
|
|
435
|
+
var _a, _b, _c, _d, _e, _f;
|
|
436
|
+
const arg = (_a = node.arguments) == null ? void 0 : _a[0];
|
|
437
|
+
if ((arg == null ? void 0 : arg.type) === "ArrowFunctionExpression" && ((_b = arg.body) == null ? void 0 : _b.type) === "CallExpression") {
|
|
438
|
+
const importCall = arg.body;
|
|
439
|
+
if (((_c = importCall.callee) == null ? void 0 : _c.type) === "Import") {
|
|
440
|
+
const source = (_e = (_d = importCall.arguments) == null ? void 0 : _d[0]) == null ? void 0 : _e.value;
|
|
441
|
+
if (source) {
|
|
442
|
+
state.usagePatterns.lazyImports.add({
|
|
443
|
+
source,
|
|
444
|
+
line: ((_f = node.span) == null ? void 0 : _f.start) || 0
|
|
422
445
|
});
|
|
423
|
-
|
|
446
|
+
console.log(`\u{1F504} Found lazy import: ${source}`);
|
|
424
447
|
}
|
|
425
|
-
});
|
|
426
|
-
return analysis;
|
|
427
|
-
}
|
|
428
|
-
getPropType(value) {
|
|
429
|
-
if (!value) return "boolean";
|
|
430
|
-
switch (value.type) {
|
|
431
|
-
case "StringLiteral":
|
|
432
|
-
return "string";
|
|
433
|
-
case "JSXExpressionContainer":
|
|
434
|
-
const expr = value.expression;
|
|
435
|
-
if (!expr) return "unknown";
|
|
436
|
-
switch (expr.type) {
|
|
437
|
-
case "NumericLiteral":
|
|
438
|
-
return "number";
|
|
439
|
-
case "BooleanLiteral":
|
|
440
|
-
return "boolean";
|
|
441
|
-
case "StringLiteral":
|
|
442
|
-
return "string";
|
|
443
|
-
case "ArrowFunctionExpression":
|
|
444
|
-
case "FunctionExpression":
|
|
445
|
-
return "function";
|
|
446
|
-
case "ObjectExpression":
|
|
447
|
-
return "object";
|
|
448
|
-
case "ArrayExpression":
|
|
449
|
-
return "array";
|
|
450
|
-
case "Identifier":
|
|
451
|
-
return "variable";
|
|
452
|
-
default:
|
|
453
|
-
return "expression";
|
|
454
|
-
}
|
|
455
|
-
default:
|
|
456
|
-
return "unknown";
|
|
457
448
|
}
|
|
458
449
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
450
|
+
}
|
|
451
|
+
function analyzeDynamicImport(node, state) {
|
|
452
|
+
var _a, _b, _c;
|
|
453
|
+
const source = (_b = (_a = node.arguments) == null ? void 0 : _a[0]) == null ? void 0 : _b.value;
|
|
454
|
+
if (source) {
|
|
455
|
+
state.usagePatterns.dynamicImports.add({
|
|
456
|
+
source,
|
|
457
|
+
line: ((_c = node.span) == null ? void 0 : _c.start) || 0
|
|
458
|
+
});
|
|
459
|
+
console.log(`\u26A1 Found dynamic import: ${source}`);
|
|
467
460
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/swc-parser/patterns/advanced.ts
|
|
464
|
+
function analyzeHOCUsage(node, state) {
|
|
465
|
+
var _a, _b, _c, _d;
|
|
466
|
+
state.usagePatterns.hocUsage.add({
|
|
467
|
+
function: ((_a = node.callee) == null ? void 0 : _a.value) || "[unknown]",
|
|
468
|
+
component: ((_c = (_b = node.arguments) == null ? void 0 : _b[0]) == null ? void 0 : _c.value) || "[unknown]",
|
|
469
|
+
line: ((_d = node.span) == null ? void 0 : _d.start) || 0
|
|
470
|
+
});
|
|
471
|
+
console.log(`\u{1F381} HOC usage found`);
|
|
472
|
+
}
|
|
473
|
+
function analyzeMemoUsage(node, state) {
|
|
474
|
+
var _a, _b;
|
|
475
|
+
const component = (_a = node.arguments) == null ? void 0 : _a[0];
|
|
476
|
+
if ((component == null ? void 0 : component.type) === "Identifier" && state.componentNames.has(component.value)) {
|
|
477
|
+
state.usagePatterns.memoizedComponents.add({
|
|
478
|
+
component: component.value,
|
|
479
|
+
line: ((_b = node.span) == null ? void 0 : _b.start) || 0
|
|
475
480
|
});
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
481
|
+
console.log(`\u{1F9E0} Memoized component: ${component.value}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function analyzeForwardRefUsage(node, state) {
|
|
485
|
+
var _a;
|
|
486
|
+
state.usagePatterns.forwardedRefs.add({
|
|
487
|
+
line: ((_a = node.span) == null ? void 0 : _a.start) || 0
|
|
488
|
+
});
|
|
489
|
+
console.log("\u2197\uFE0F ForwardRef usage found");
|
|
490
|
+
}
|
|
491
|
+
function analyzePortalUsage(node, state) {
|
|
492
|
+
var _a;
|
|
493
|
+
state.usagePatterns.portalUsage.add({
|
|
494
|
+
line: ((_a = node.span) == null ? void 0 : _a.start) || 0
|
|
495
|
+
});
|
|
496
|
+
console.log("\u{1F300} Portal usage found");
|
|
497
|
+
}
|
|
498
|
+
function analyzeMemberExpression(node, state) {
|
|
499
|
+
var _a, _b;
|
|
500
|
+
if (((_a = node.object) == null ? void 0 : _a.type) === "Identifier" && state.allIdentifiers.has(node.object.value)) {
|
|
501
|
+
const namespaceName = node.object.value;
|
|
502
|
+
const propertyName = (_b = node.property) == null ? void 0 : _b.value;
|
|
503
|
+
if (propertyName) {
|
|
504
|
+
state.componentNames.add(propertyName);
|
|
505
|
+
console.log(`\u{1F517} Namespace access: ${namespaceName}.${propertyName}`);
|
|
482
506
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function isHOCPattern(node, state) {
|
|
510
|
+
var _a, _b;
|
|
511
|
+
return ((_a = node.callee) == null ? void 0 : _a.type) === "Identifier" && ((_b = node.arguments) == null ? void 0 : _b.some(
|
|
512
|
+
(arg) => arg.type === "Identifier" && state.componentNames.has(arg.value)
|
|
513
|
+
));
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// src/swc-parser/core/visitor.ts
|
|
517
|
+
function visitNode(node, state, context = {}) {
|
|
518
|
+
if (!node) return;
|
|
519
|
+
switch (node.type) {
|
|
520
|
+
case "Module":
|
|
521
|
+
if (node.body) {
|
|
522
|
+
for (const item of node.body) {
|
|
523
|
+
if (item.type === "ImportDeclaration") {
|
|
524
|
+
visitNode(item, state, context);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
for (const item of node.body) {
|
|
528
|
+
if (item.type !== "ImportDeclaration") {
|
|
529
|
+
visitNode(item, state, { ...context, parent: node });
|
|
530
|
+
}
|
|
531
|
+
}
|
|
491
532
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
533
|
+
break;
|
|
534
|
+
case "ImportDeclaration":
|
|
535
|
+
analyzeImportDeclaration(node, state);
|
|
536
|
+
break;
|
|
537
|
+
case "CallExpression":
|
|
538
|
+
analyzeCallExpression(node, state, context);
|
|
539
|
+
break;
|
|
540
|
+
case "VariableDeclaration":
|
|
541
|
+
analyzeVariableDeclaration(node, state);
|
|
542
|
+
visitChildren(node, state, context);
|
|
543
|
+
break;
|
|
544
|
+
case "JSXElement":
|
|
545
|
+
case "JSXFragment":
|
|
546
|
+
analyzeJSXElement(node, state);
|
|
547
|
+
visitChildren(node, state, context);
|
|
548
|
+
break;
|
|
549
|
+
case "JSXOpeningElement":
|
|
550
|
+
analyzeJSXOpeningElement(node, state, context.parent);
|
|
551
|
+
break;
|
|
552
|
+
case "ArrayExpression":
|
|
553
|
+
analyzeArrayExpression(node, state);
|
|
554
|
+
visitChildren(node, state, context);
|
|
555
|
+
break;
|
|
556
|
+
case "ObjectExpression":
|
|
557
|
+
analyzeObjectExpression(node, state);
|
|
558
|
+
visitChildren(node, state, context);
|
|
559
|
+
break;
|
|
560
|
+
case "MemberExpression":
|
|
561
|
+
analyzeMemberExpression(node, state);
|
|
562
|
+
visitChildren(node, state, context);
|
|
563
|
+
break;
|
|
564
|
+
case "ConditionalExpression":
|
|
565
|
+
analyzeConditionalExpression(node, state);
|
|
566
|
+
visitChildren(node, state, context);
|
|
567
|
+
break;
|
|
568
|
+
case "FunctionDeclaration":
|
|
569
|
+
case "ClassDeclaration":
|
|
570
|
+
case "ExpressionStatement":
|
|
571
|
+
case "ReturnStatement":
|
|
572
|
+
case "VariableDeclarator":
|
|
573
|
+
case "ArrowFunctionExpression":
|
|
574
|
+
case "FunctionExpression":
|
|
575
|
+
visitChildren(node, state, { ...context, parent: node });
|
|
576
|
+
break;
|
|
577
|
+
default:
|
|
578
|
+
visitChildren(node, state, context);
|
|
579
|
+
break;
|
|
508
580
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
this.usagePatterns.conditionalUsage.add({
|
|
515
|
-
consequent,
|
|
516
|
-
alternate,
|
|
517
|
-
line: ((_c = node.span) == null ? void 0 : _c.start) || 0
|
|
518
|
-
});
|
|
519
|
-
console.log(`\u{1F500} Conditional component usage found`);
|
|
520
|
-
}
|
|
521
|
-
this.visitChildren(node);
|
|
581
|
+
}
|
|
582
|
+
function analyzeCallExpression(node, state, context) {
|
|
583
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
|
|
584
|
+
if (((_a = node.callee) == null ? void 0 : _a.value) === "lazy" || ((_c = (_b = node.callee) == null ? void 0 : _b.object) == null ? void 0 : _c.value) === "React" && ((_e = (_d = node.callee) == null ? void 0 : _d.property) == null ? void 0 : _e.value) === "lazy") {
|
|
585
|
+
analyzeLazyImport(node, state);
|
|
522
586
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
if (this.isHOCFunction(node)) {
|
|
526
|
-
this.usagePatterns.hocUsage.add({
|
|
527
|
-
name: ((_a = node.identifier) == null ? void 0 : _a.value) || "[anonymous]",
|
|
528
|
-
line: ((_b = node.span) == null ? void 0 : _b.start) || 0
|
|
529
|
-
});
|
|
530
|
-
console.log(`\u{1F527} HOC function found: ${(_c = node.identifier) == null ? void 0 : _c.value}`);
|
|
531
|
-
}
|
|
532
|
-
this.visitChildren(node);
|
|
533
|
-
}
|
|
534
|
-
analyzeClassDeclaration(node) {
|
|
535
|
-
this.visitChildren(node);
|
|
536
|
-
}
|
|
537
|
-
isHOCPattern(node) {
|
|
538
|
-
var _a, _b;
|
|
539
|
-
return ((_a = node.callee) == null ? void 0 : _a.type) === "Identifier" && ((_b = node.arguments) == null ? void 0 : _b.some(
|
|
540
|
-
(arg) => arg.type === "Identifier" && this.componentNames.has(arg.value)
|
|
541
|
-
));
|
|
542
|
-
}
|
|
543
|
-
isHOCFunction(node) {
|
|
544
|
-
const params = node.params || [];
|
|
545
|
-
return params.some((param) => {
|
|
546
|
-
var _a;
|
|
547
|
-
return ((_a = param.pat) == null ? void 0 : _a.type) === "Identifier";
|
|
548
|
-
});
|
|
587
|
+
if (((_f = node.callee) == null ? void 0 : _f.type) === "Import") {
|
|
588
|
+
analyzeDynamicImport(node, state);
|
|
549
589
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
this.usagePatterns.hocUsage.add({
|
|
553
|
-
function: (_a = node.callee) == null ? void 0 : _a.value,
|
|
554
|
-
component: (_c = (_b = node.arguments) == null ? void 0 : _b[0]) == null ? void 0 : _c.value,
|
|
555
|
-
line: ((_d = node.span) == null ? void 0 : _d.start) || 0
|
|
556
|
-
});
|
|
590
|
+
if (isHOCPattern(node, state)) {
|
|
591
|
+
analyzeHOCUsage(node, state);
|
|
557
592
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
if ((
|
|
562
|
-
|
|
563
|
-
component: component.value,
|
|
564
|
-
line: ((_b = node.span) == null ? void 0 : _b.start) || 0
|
|
565
|
-
});
|
|
566
|
-
console.log(`\u{1F9E0} Memoized component: ${component.value}`);
|
|
593
|
+
if (((_h = (_g = node.callee) == null ? void 0 : _g.object) == null ? void 0 : _h.value) === "React") {
|
|
594
|
+
if (((_j = (_i = node.callee) == null ? void 0 : _i.property) == null ? void 0 : _j.value) === "memo") {
|
|
595
|
+
analyzeMemoUsage(node, state);
|
|
596
|
+
} else if (((_l = (_k = node.callee) == null ? void 0 : _k.property) == null ? void 0 : _l.value) === "forwardRef") {
|
|
597
|
+
analyzeForwardRefUsage(node, state);
|
|
567
598
|
}
|
|
568
599
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
this.usagePatterns.forwardedRefs.add({
|
|
572
|
-
line: ((_a = node.span) == null ? void 0 : _a.start) || 0
|
|
573
|
-
});
|
|
574
|
-
console.log(`\u2197\uFE0F ForwardRef usage found`);
|
|
600
|
+
if (((_n = (_m = node.callee) == null ? void 0 : _m.property) == null ? void 0 : _n.value) === "createPortal" || ((_o = node.callee) == null ? void 0 : _o.value) === "createPortal") {
|
|
601
|
+
analyzePortalUsage(node, state);
|
|
575
602
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
const propertyName = (_b = node.property) == null ? void 0 : _b.value;
|
|
588
|
-
if (propertyName) {
|
|
589
|
-
this.componentNames.add(propertyName);
|
|
590
|
-
console.log(`\u{1F517} Namespace access: ${namespaceName}.${propertyName}`);
|
|
603
|
+
visitChildren(node, state, context);
|
|
604
|
+
}
|
|
605
|
+
function visitChildren(node, state, context) {
|
|
606
|
+
if (!node) return;
|
|
607
|
+
for (const key in node) {
|
|
608
|
+
const value = node[key];
|
|
609
|
+
if (Array.isArray(value)) {
|
|
610
|
+
for (const item of value) {
|
|
611
|
+
if (item && typeof item === "object") {
|
|
612
|
+
visitNode(item, state, { ...context, parent: node });
|
|
613
|
+
}
|
|
591
614
|
}
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
}
|
|
595
|
-
extractAssignmentInfo(node) {
|
|
596
|
-
switch (node.type) {
|
|
597
|
-
case "Identifier":
|
|
598
|
-
return node.value;
|
|
599
|
-
case "MemberExpression":
|
|
600
|
-
return `${this.extractAssignmentInfo(node.object)}.${node.property.value}`;
|
|
601
|
-
case "ConditionalExpression":
|
|
602
|
-
return `${this.extractAssignmentInfo(node.consequent)} | ${this.extractAssignmentInfo(node.alternate)}`;
|
|
603
|
-
default:
|
|
604
|
-
return null;
|
|
615
|
+
} else if (value && typeof value === "object" && value.type) {
|
|
616
|
+
visitNode(value, state, { ...context, parent: node });
|
|
605
617
|
}
|
|
606
618
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// src/swc-parser/core/report.ts
|
|
622
|
+
function generateReport(state) {
|
|
623
|
+
const report = {
|
|
624
|
+
summary: {
|
|
625
|
+
totalImports: state.usagePatterns.defaultImports.size + state.usagePatterns.namedImports.size + state.usagePatterns.namespaceImports.size,
|
|
626
|
+
totalComponents: state.componentNames.size,
|
|
627
|
+
totalUsagePatterns: calculateTotalPatterns(state)
|
|
628
|
+
},
|
|
629
|
+
patterns: {
|
|
630
|
+
imports: {
|
|
631
|
+
default: Array.from(state.usagePatterns.defaultImports),
|
|
632
|
+
named: Array.from(state.usagePatterns.namedImports),
|
|
633
|
+
namespace: Array.from(state.usagePatterns.namespaceImports),
|
|
634
|
+
aliased: Array.from(state.usagePatterns.aliasedImports.values())
|
|
635
|
+
},
|
|
636
|
+
usage: {
|
|
637
|
+
jsx: Array.from(state.usagePatterns.jsxUsage.values()),
|
|
638
|
+
variables: Array.from(
|
|
639
|
+
state.usagePatterns.variableAssignments.entries()
|
|
640
|
+
).map(([key, value]) => ({
|
|
641
|
+
variable: key,
|
|
642
|
+
assignment: value.assignment
|
|
643
|
+
})),
|
|
644
|
+
destructuring: Array.from(state.usagePatterns.destructuredUsage),
|
|
645
|
+
conditional: Array.from(state.usagePatterns.conditionalUsage),
|
|
646
|
+
arrays: Array.from(state.usagePatterns.arrayMappings),
|
|
647
|
+
objects: Array.from(state.usagePatterns.objectMappings)
|
|
648
|
+
},
|
|
649
|
+
advanced: {
|
|
650
|
+
lazy: Array.from(state.usagePatterns.lazyImports),
|
|
651
|
+
dynamic: Array.from(state.usagePatterns.dynamicImports),
|
|
652
|
+
hoc: Array.from(state.usagePatterns.hocUsage),
|
|
653
|
+
memo: Array.from(state.usagePatterns.memoizedComponents),
|
|
654
|
+
forwardRef: Array.from(state.usagePatterns.forwardedRefs),
|
|
655
|
+
portal: Array.from(state.usagePatterns.portalUsage)
|
|
656
|
+
},
|
|
657
|
+
props: Array.from(state.usagePatterns.propsAnalysis.entries()).map(
|
|
658
|
+
([component, analysis]) => ({
|
|
659
|
+
component,
|
|
660
|
+
analysis
|
|
661
|
+
})
|
|
662
|
+
)
|
|
663
|
+
},
|
|
664
|
+
components: Array.from(state.componentNames).sort()
|
|
665
|
+
};
|
|
666
|
+
return report;
|
|
667
|
+
}
|
|
668
|
+
function calculateTotalPatterns(state) {
|
|
669
|
+
let sum = 0;
|
|
670
|
+
const patterns = state.usagePatterns;
|
|
671
|
+
for (const key in patterns) {
|
|
672
|
+
const pattern = patterns[key];
|
|
673
|
+
if (pattern instanceof Set) {
|
|
674
|
+
sum += pattern.size;
|
|
675
|
+
} else if (pattern instanceof Map) {
|
|
676
|
+
sum += pattern.size;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return sum;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// src/swc-parser/index.ts
|
|
683
|
+
function parseCode(code, options = {}) {
|
|
684
|
+
const state = createState();
|
|
685
|
+
const ast = core.parseSync(code, {
|
|
686
|
+
syntax: "typescript",
|
|
687
|
+
tsx: true,
|
|
688
|
+
decorators: true,
|
|
689
|
+
dynamicImport: true
|
|
690
|
+
});
|
|
691
|
+
visitNode(ast, state);
|
|
692
|
+
const report = generateReport(state);
|
|
693
|
+
if (options.libraryName) {
|
|
694
|
+
return filterReportByLibrary(report, options.libraryName);
|
|
621
695
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
696
|
+
return report;
|
|
697
|
+
}
|
|
698
|
+
function parseFile(filePath, options = {}) {
|
|
699
|
+
console.log(`
|
|
700
|
+
\u{1F4C1} Analyzing: ${filePath}`);
|
|
701
|
+
try {
|
|
702
|
+
const code = fs__default.default.readFileSync(filePath, "utf8");
|
|
703
|
+
return parseCode(code, options);
|
|
704
|
+
} catch (error) {
|
|
705
|
+
console.error(`\u274C Error parsing ${filePath}:`, error.message);
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
function filterReportByLibrary(report, libraryName) {
|
|
710
|
+
const isFromLibrary = (source) => source.startsWith(libraryName) || source.includes(libraryName);
|
|
711
|
+
return {
|
|
712
|
+
...report,
|
|
713
|
+
patterns: {
|
|
714
|
+
imports: {
|
|
715
|
+
default: report.patterns.imports.default.filter(
|
|
716
|
+
(imp) => isFromLibrary(imp.source)
|
|
717
|
+
),
|
|
718
|
+
named: report.patterns.imports.named.filter(
|
|
719
|
+
(imp) => isFromLibrary(imp.source)
|
|
720
|
+
),
|
|
721
|
+
namespace: report.patterns.imports.namespace.filter(
|
|
722
|
+
(imp) => isFromLibrary(imp.source)
|
|
723
|
+
),
|
|
724
|
+
aliased: report.patterns.imports.aliased.filter(
|
|
725
|
+
(imp) => isFromLibrary(imp.source)
|
|
641
726
|
)
|
|
642
727
|
},
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
usage: {
|
|
656
|
-
jsx: Array.from(this.usagePatterns.jsxUsage.entries()).map(
|
|
657
|
-
([component, usages]) => ({
|
|
658
|
-
component,
|
|
659
|
-
count: usages.length,
|
|
660
|
-
usages
|
|
661
|
-
})
|
|
662
|
-
),
|
|
663
|
-
variables: Array.from(
|
|
664
|
-
this.usagePatterns.variableAssignments.entries()
|
|
665
|
-
).map(([key, value]) => ({
|
|
666
|
-
variable: key,
|
|
667
|
-
...value
|
|
668
|
-
})),
|
|
669
|
-
destructuring: Array.from(this.usagePatterns.destructuredUsage),
|
|
670
|
-
conditional: Array.from(this.usagePatterns.conditionalUsage),
|
|
671
|
-
arrays: Array.from(this.usagePatterns.arrayMappings),
|
|
672
|
-
objects: Array.from(this.usagePatterns.objectMappings)
|
|
673
|
-
},
|
|
674
|
-
advanced: {
|
|
675
|
-
lazy: Array.from(this.usagePatterns.lazyImports),
|
|
676
|
-
dynamic: Array.from(this.usagePatterns.dynamicImports),
|
|
677
|
-
hoc: Array.from(this.usagePatterns.hocUsage),
|
|
678
|
-
memo: Array.from(this.usagePatterns.memoizedComponents),
|
|
679
|
-
forwardRef: Array.from(this.usagePatterns.forwardedRefs),
|
|
680
|
-
portal: Array.from(this.usagePatterns.portalUsage)
|
|
681
|
-
},
|
|
682
|
-
props: Array.from(this.usagePatterns.propsAnalysis.entries()).map(
|
|
683
|
-
([component, stats]) => ({
|
|
684
|
-
component,
|
|
685
|
-
namedProps: Array.from(stats.namedProps),
|
|
686
|
-
spreadPropsCount: stats.spreadProps,
|
|
687
|
-
totalUsages: stats.totalUsages,
|
|
688
|
-
complexPropsCount: stats.complexProps,
|
|
689
|
-
spreadWarning: stats.spreadProps > 0 ? `${stats.spreadProps} usage(s) with spread props - cannot analyze statically` : null
|
|
690
|
-
})
|
|
691
|
-
)
|
|
728
|
+
usage: report.patterns.usage,
|
|
729
|
+
advanced: {
|
|
730
|
+
lazy: report.patterns.advanced.lazy.filter(
|
|
731
|
+
(imp) => isFromLibrary(imp.source)
|
|
732
|
+
),
|
|
733
|
+
dynamic: report.patterns.advanced.dynamic.filter(
|
|
734
|
+
(imp) => isFromLibrary(imp.source)
|
|
735
|
+
),
|
|
736
|
+
hoc: report.patterns.advanced.hoc,
|
|
737
|
+
memo: report.patterns.advanced.memo,
|
|
738
|
+
forwardRef: report.patterns.advanced.forwardRef,
|
|
739
|
+
portal: report.patterns.advanced.portal
|
|
692
740
|
},
|
|
693
|
-
|
|
694
|
-
};
|
|
695
|
-
return report;
|
|
696
|
-
}
|
|
697
|
-
printReport(report) {
|
|
698
|
-
console.log("\n" + "=".repeat(80));
|
|
699
|
-
console.log("\u{1F4CA} REACT COMPONENT USAGE ANALYSIS REPORT");
|
|
700
|
-
console.log("=".repeat(80));
|
|
701
|
-
console.log(`
|
|
702
|
-
\u{1F4C8} SUMMARY:`);
|
|
703
|
-
console.log(` Total Imports: ${report.summary.totalImports}`);
|
|
704
|
-
console.log(` Components Found: ${report.summary.totalComponents}`);
|
|
705
|
-
console.log(` Usage Patterns: ${report.summary.totalUsagePatterns}`);
|
|
706
|
-
console.log(`
|
|
707
|
-
\u{1F4E6} IMPORT PATTERNS:`);
|
|
708
|
-
if (report.patterns.imports.default.length > 0) {
|
|
709
|
-
console.log(
|
|
710
|
-
` Default Imports (${report.patterns.imports.default.length}):`
|
|
711
|
-
);
|
|
712
|
-
report.patterns.imports.default.forEach((imp) => {
|
|
713
|
-
console.log(` - ${imp.name} from "${imp.source}"`);
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
if (report.patterns.imports.named.length > 0) {
|
|
717
|
-
console.log(
|
|
718
|
-
` Named Imports (${report.patterns.imports.named.length}):`
|
|
719
|
-
);
|
|
720
|
-
report.patterns.imports.named.forEach((imp) => {
|
|
721
|
-
console.log(
|
|
722
|
-
` - {${imp.imported}${imp.imported !== imp.local ? ` as ${imp.local}` : ""}} from "${imp.source}"`
|
|
723
|
-
);
|
|
724
|
-
});
|
|
741
|
+
props: report.patterns.props
|
|
725
742
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// src/utils/aggregator.ts
|
|
747
|
+
function aggregateReports(reports) {
|
|
748
|
+
const componentUsageMap = /* @__PURE__ */ new Map();
|
|
749
|
+
let totalImports = 0;
|
|
750
|
+
let totalUsagePatterns = 0;
|
|
751
|
+
const patternCountMap = /* @__PURE__ */ new Map();
|
|
752
|
+
for (const report of reports) {
|
|
753
|
+
totalImports += report.summary.totalImports;
|
|
754
|
+
totalUsagePatterns += report.summary.totalUsagePatterns;
|
|
755
|
+
for (const jsx of report.patterns.usage.jsx) {
|
|
756
|
+
const key = jsx.component;
|
|
757
|
+
const existing = componentUsageMap.get(key);
|
|
758
|
+
if (existing) {
|
|
759
|
+
existing.count++;
|
|
760
|
+
} else {
|
|
761
|
+
const source = findComponentSource(jsx.component, report);
|
|
762
|
+
componentUsageMap.set(key, {
|
|
763
|
+
name: jsx.component,
|
|
764
|
+
source,
|
|
765
|
+
count: 1,
|
|
766
|
+
files: /* @__PURE__ */ new Set()
|
|
767
|
+
});
|
|
768
|
+
}
|
|
733
769
|
}
|
|
734
|
-
|
|
770
|
+
countPatterns(report, patternCountMap);
|
|
771
|
+
}
|
|
772
|
+
const topComponents = Array.from(componentUsageMap.values()).sort(
|
|
773
|
+
(a, b) => b.count - a.count
|
|
774
|
+
);
|
|
775
|
+
const allComponents = Array.from(componentUsageMap.keys()).sort();
|
|
776
|
+
const patternCounts = Array.from(patternCountMap.entries()).map(([type, count]) => ({
|
|
777
|
+
patternType: type,
|
|
778
|
+
displayName: getPatternDisplayName(type),
|
|
779
|
+
count
|
|
780
|
+
})).sort((a, b) => b.count - a.count);
|
|
781
|
+
return {
|
|
782
|
+
filesAnalyzed: reports.length,
|
|
783
|
+
totalImports,
|
|
784
|
+
totalComponents: componentUsageMap.size,
|
|
785
|
+
totalUsagePatterns,
|
|
786
|
+
patternCounts,
|
|
787
|
+
componentUsage: componentUsageMap,
|
|
788
|
+
topComponents,
|
|
789
|
+
allComponents,
|
|
790
|
+
reports
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
function findComponentSource(componentName, report) {
|
|
794
|
+
const namedImport = report.patterns.imports.named.find(
|
|
795
|
+
(imp) => imp.name === componentName
|
|
796
|
+
);
|
|
797
|
+
if (namedImport) return namedImport.source;
|
|
798
|
+
const defaultImport = report.patterns.imports.default.find(
|
|
799
|
+
(imp) => imp.name === componentName
|
|
800
|
+
);
|
|
801
|
+
if (defaultImport) return defaultImport.source;
|
|
802
|
+
const aliasedImport = report.patterns.imports.aliased.find(
|
|
803
|
+
(imp) => imp.local === componentName
|
|
804
|
+
);
|
|
805
|
+
if (aliasedImport) return aliasedImport.source;
|
|
806
|
+
return "unknown";
|
|
807
|
+
}
|
|
808
|
+
function countPatterns(report, patternMap) {
|
|
809
|
+
increment(
|
|
810
|
+
patternMap,
|
|
811
|
+
"imports.default",
|
|
812
|
+
report.patterns.imports.default.length
|
|
813
|
+
);
|
|
814
|
+
increment(patternMap, "imports.named", report.patterns.imports.named.length);
|
|
815
|
+
increment(
|
|
816
|
+
patternMap,
|
|
817
|
+
"imports.namespace",
|
|
818
|
+
report.patterns.imports.namespace.length
|
|
819
|
+
);
|
|
820
|
+
increment(
|
|
821
|
+
patternMap,
|
|
822
|
+
"imports.aliased",
|
|
823
|
+
report.patterns.imports.aliased.length
|
|
824
|
+
);
|
|
825
|
+
increment(patternMap, "usage.jsx", report.patterns.usage.jsx.length);
|
|
826
|
+
increment(
|
|
827
|
+
patternMap,
|
|
828
|
+
"usage.variables",
|
|
829
|
+
report.patterns.usage.variables.length
|
|
830
|
+
);
|
|
831
|
+
increment(
|
|
832
|
+
patternMap,
|
|
833
|
+
"usage.destructuring",
|
|
834
|
+
report.patterns.usage.destructuring.length
|
|
835
|
+
);
|
|
836
|
+
increment(
|
|
837
|
+
patternMap,
|
|
838
|
+
"usage.conditional",
|
|
839
|
+
report.patterns.usage.conditional.length
|
|
840
|
+
);
|
|
841
|
+
increment(patternMap, "usage.arrays", report.patterns.usage.arrays.length);
|
|
842
|
+
increment(patternMap, "usage.objects", report.patterns.usage.objects.length);
|
|
843
|
+
increment(patternMap, "advanced.lazy", report.patterns.advanced.lazy.length);
|
|
844
|
+
increment(
|
|
845
|
+
patternMap,
|
|
846
|
+
"advanced.dynamic",
|
|
847
|
+
report.patterns.advanced.dynamic.length
|
|
848
|
+
);
|
|
849
|
+
increment(patternMap, "advanced.hoc", report.patterns.advanced.hoc.length);
|
|
850
|
+
increment(patternMap, "advanced.memo", report.patterns.advanced.memo.length);
|
|
851
|
+
increment(
|
|
852
|
+
patternMap,
|
|
853
|
+
"advanced.forwardRef",
|
|
854
|
+
report.patterns.advanced.forwardRef.length
|
|
855
|
+
);
|
|
856
|
+
increment(
|
|
857
|
+
patternMap,
|
|
858
|
+
"advanced.portal",
|
|
859
|
+
report.patterns.advanced.portal.length
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
function increment(map, key, value) {
|
|
863
|
+
map.set(key, (map.get(key) || 0) + value);
|
|
864
|
+
}
|
|
865
|
+
function getPatternDisplayName(patternType) {
|
|
866
|
+
const displayNames = {
|
|
867
|
+
"imports.default": "Default Imports",
|
|
868
|
+
"imports.named": "Named Imports",
|
|
869
|
+
"imports.namespace": "Namespace Imports",
|
|
870
|
+
"imports.aliased": "Aliased Imports",
|
|
871
|
+
"usage.jsx": "JSX Usage",
|
|
872
|
+
"usage.variables": "Variable Assignments",
|
|
873
|
+
"usage.destructuring": "Destructuring",
|
|
874
|
+
"usage.conditional": "Conditional Usage",
|
|
875
|
+
"usage.arrays": "Array Mappings",
|
|
876
|
+
"usage.objects": "Object Mappings",
|
|
877
|
+
"advanced.lazy": "Lazy Loading",
|
|
878
|
+
"advanced.dynamic": "Dynamic Imports",
|
|
879
|
+
"advanced.hoc": "Higher-Order Components",
|
|
880
|
+
"advanced.memo": "Memoized Components",
|
|
881
|
+
"advanced.forwardRef": "Forward Refs",
|
|
882
|
+
"advanced.portal": "Portal Usage"
|
|
883
|
+
};
|
|
884
|
+
return displayNames[patternType] || patternType;
|
|
885
|
+
}
|
|
886
|
+
function printVerbose(filePath, report) {
|
|
887
|
+
const relativePath = path__default.default.relative(process.cwd(), filePath);
|
|
888
|
+
console.log(chalk__default.default.gray(`[VERBOSE] Analyzing: ${relativePath}`));
|
|
889
|
+
for (const jsx of report.patterns.usage.jsx) {
|
|
890
|
+
console.log(chalk__default.default.gray(`[VERBOSE] Found JSX Usage: <${jsx.component}>`));
|
|
891
|
+
}
|
|
892
|
+
for (const imp of report.patterns.imports.named) {
|
|
893
|
+
console.log(
|
|
894
|
+
chalk__default.default.gray(`[VERBOSE] Found import: ${imp.name} from ${imp.source}`)
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
for (const imp of report.patterns.imports.default) {
|
|
898
|
+
console.log(
|
|
899
|
+
chalk__default.default.gray(
|
|
900
|
+
`[VERBOSE] Found default import: ${imp.name} from ${imp.source}`
|
|
901
|
+
)
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
for (const imp of report.patterns.imports.namespace) {
|
|
905
|
+
console.log(
|
|
906
|
+
chalk__default.default.gray(
|
|
907
|
+
`[VERBOSE] Found namespace import: ${imp.name} from ${imp.source}`
|
|
908
|
+
)
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
for (const imp of report.patterns.imports.aliased) {
|
|
912
|
+
console.log(
|
|
913
|
+
chalk__default.default.gray(
|
|
914
|
+
`[VERBOSE] Found aliased import: ${imp.imported} as ${imp.local} from ${imp.source}`
|
|
915
|
+
)
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
for (const obj of report.patterns.usage.objects) {
|
|
919
|
+
for (const mapping of obj.mappings) {
|
|
735
920
|
console.log(
|
|
736
|
-
|
|
921
|
+
chalk__default.default.gray(
|
|
922
|
+
`[VERBOSE] Found Object mapping with Component: ${mapping.component}`
|
|
923
|
+
)
|
|
737
924
|
);
|
|
738
|
-
report.patterns.imports.aliased.forEach((imp) => {
|
|
739
|
-
console.log(
|
|
740
|
-
` - ${imp.alias} (originally ${imp.original}) from "${imp.source}"`
|
|
741
|
-
);
|
|
742
|
-
});
|
|
743
925
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
report.patterns.usage.jsx.forEach((usage) => {
|
|
747
|
-
console.log(
|
|
748
|
-
` Component: ${usage.component} (used ${usage.count} times)`
|
|
749
|
-
);
|
|
750
|
-
usage.usages.slice(0, 3).forEach((use, idx) => {
|
|
751
|
-
const props = use.props.length > 0 ? ` with ${use.props.length} props` : "";
|
|
752
|
-
console.log(
|
|
753
|
-
` ${idx + 1}. <${usage.component}${props}> (line ~${use.line})`
|
|
754
|
-
);
|
|
755
|
-
});
|
|
756
|
-
if (usage.usages.length > 3) {
|
|
757
|
-
console.log(` ... and ${usage.usages.length - 3} more`);
|
|
758
|
-
}
|
|
759
|
-
});
|
|
760
|
-
if (report.patterns.usage.variables.length > 0) {
|
|
761
|
-
console.log(`
|
|
762
|
-
\u{1F4DD} VARIABLE ASSIGNMENTS:`);
|
|
763
|
-
report.patterns.usage.variables.forEach((variable) => {
|
|
764
|
-
console.log(` ${variable.variable} = ${variable.assignment}`);
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
if (report.patterns.advanced.lazy.length > 0) {
|
|
768
|
-
console.log(`
|
|
769
|
-
\u{1F504} LAZY LOADING:`);
|
|
770
|
-
report.patterns.advanced.lazy.forEach((lazy) => {
|
|
771
|
-
console.log(` - Lazy import from "${lazy.source}"`);
|
|
772
|
-
});
|
|
773
|
-
}
|
|
774
|
-
if (report.patterns.advanced.dynamic.length > 0) {
|
|
775
|
-
console.log(`
|
|
776
|
-
\u26A1 DYNAMIC IMPORTS:`);
|
|
777
|
-
report.patterns.advanced.dynamic.forEach((dynamic) => {
|
|
778
|
-
console.log(` - Dynamic import from "${dynamic.source}"`);
|
|
779
|
-
});
|
|
780
|
-
}
|
|
781
|
-
if (report.patterns.usage.conditional.length > 0) {
|
|
782
|
-
console.log(`
|
|
783
|
-
\u{1F500} CONDITIONAL USAGE:`);
|
|
784
|
-
report.patterns.usage.conditional.forEach((cond) => {
|
|
785
|
-
console.log(
|
|
786
|
-
` - ${cond.consequent || "null"} ? ${cond.alternate || "null"}`
|
|
787
|
-
);
|
|
788
|
-
});
|
|
789
|
-
}
|
|
790
|
-
console.log(`
|
|
791
|
-
\u{1F9E9} COMPONENTS IDENTIFIED:`);
|
|
792
|
-
report.components.forEach((comp) => {
|
|
793
|
-
console.log(` - ${comp}`);
|
|
794
|
-
});
|
|
795
|
-
console.log("\n" + "=".repeat(80));
|
|
796
|
-
}
|
|
797
|
-
};
|
|
798
|
-
var FocusedUsageAnalyzer = class extends ReactComponentUsageAnalyzer {
|
|
799
|
-
constructor(libraryName = "@design-system/foundation") {
|
|
800
|
-
super(libraryName);
|
|
801
|
-
this.patternMap = /* @__PURE__ */ new Map();
|
|
802
|
-
this.componentFrequency = /* @__PURE__ */ new Map();
|
|
803
|
-
this.usageComplexity = /* @__PURE__ */ new Map();
|
|
804
|
-
}
|
|
805
|
-
analyzePatterns() {
|
|
806
|
-
const patterns = {
|
|
807
|
-
"Direct Import & Usage": {
|
|
808
|
-
weight: 1,
|
|
809
|
-
description: "Simple import and direct JSX usage",
|
|
810
|
-
examples: ['import Button from "lib"; <Button />']
|
|
811
|
-
},
|
|
812
|
-
"Named Import with Alias": {
|
|
813
|
-
weight: 2,
|
|
814
|
-
description: "Named import with renaming",
|
|
815
|
-
examples: ['import { Button as MyButton } from "lib"; <MyButton />']
|
|
816
|
-
},
|
|
817
|
-
"Namespace Import": {
|
|
818
|
-
weight: 2,
|
|
819
|
-
description: "Import entire namespace",
|
|
820
|
-
examples: ['import * as Lib from "lib"; <Lib.Button />']
|
|
821
|
-
},
|
|
822
|
-
"Variable Assignment": {
|
|
823
|
-
weight: 3,
|
|
824
|
-
description: "Assigning components to variables",
|
|
825
|
-
examples: ["const MyButton = Button; <MyButton />"]
|
|
826
|
-
},
|
|
827
|
-
"Conditional Assignment": {
|
|
828
|
-
weight: 4,
|
|
829
|
-
description: "Conditional component selection",
|
|
830
|
-
examples: ["const Comp = condition ? Button : Input; <Comp />"]
|
|
831
|
-
},
|
|
832
|
-
"Object Mapping": {
|
|
833
|
-
weight: 5,
|
|
834
|
-
description: "Components stored in objects",
|
|
835
|
-
examples: ["const map = {btn: Button}; <map.btn />"]
|
|
836
|
-
},
|
|
837
|
-
"Array Mapping": {
|
|
838
|
-
weight: 5,
|
|
839
|
-
description: "Components in arrays",
|
|
840
|
-
examples: ["[Button, Input].map(Comp => <Comp />)"]
|
|
841
|
-
},
|
|
842
|
-
"Dynamic Mapping": {
|
|
843
|
-
weight: 6,
|
|
844
|
-
description: "Runtime component selection",
|
|
845
|
-
examples: ["components[type]"]
|
|
846
|
-
},
|
|
847
|
-
"HOC Wrapping": {
|
|
848
|
-
weight: 7,
|
|
849
|
-
description: "Higher-order component patterns",
|
|
850
|
-
examples: ["withProps(Button)"]
|
|
851
|
-
},
|
|
852
|
-
"Lazy Loading": {
|
|
853
|
-
weight: 6,
|
|
854
|
-
description: "Lazy-loaded components",
|
|
855
|
-
examples: ['lazy(() => import("lib/Button"))']
|
|
856
|
-
},
|
|
857
|
-
"Dynamic Import": {
|
|
858
|
-
weight: 7,
|
|
859
|
-
description: "Runtime dynamic imports",
|
|
860
|
-
examples: ['await import("lib/Button")']
|
|
861
|
-
},
|
|
862
|
-
"Destructuring Usage": {
|
|
863
|
-
weight: 4,
|
|
864
|
-
description: "Destructured from objects",
|
|
865
|
-
examples: ["const {Button} = Foundation; <Button />"]
|
|
866
|
-
},
|
|
867
|
-
"Memoized Components": {
|
|
868
|
-
weight: 5,
|
|
869
|
-
description: "React.memo wrapped components",
|
|
870
|
-
examples: ["memo(Button)"]
|
|
871
|
-
},
|
|
872
|
-
"Forward Ref": {
|
|
873
|
-
weight: 6,
|
|
874
|
-
description: "forwardRef wrapped components",
|
|
875
|
-
examples: ["forwardRef((props, ref) => <Button ref={ref} />)"]
|
|
876
|
-
},
|
|
877
|
-
"Portal Usage": {
|
|
878
|
-
weight: 8,
|
|
879
|
-
description: "Components rendered in portals",
|
|
880
|
-
examples: ["createPortal(<Button />, document.body)"]
|
|
881
|
-
},
|
|
882
|
-
"Context Integration": {
|
|
883
|
-
weight: 7,
|
|
884
|
-
description: "Components from React context",
|
|
885
|
-
examples: ["const {Button} = useContext(ThemeContext)"]
|
|
886
|
-
}
|
|
887
|
-
};
|
|
888
|
-
return patterns;
|
|
889
|
-
}
|
|
890
|
-
classifyUsage(report) {
|
|
891
|
-
const patterns = this.analyzePatterns();
|
|
892
|
-
const foundPatterns = /* @__PURE__ */ new Map();
|
|
893
|
-
if (report.patterns.imports.default.length > 0) {
|
|
894
|
-
foundPatterns.set("Direct Import & Usage", {
|
|
895
|
-
count: report.patterns.imports.default.length,
|
|
896
|
-
complexity: 1,
|
|
897
|
-
examples: report.patterns.imports.default.slice(0, 3)
|
|
898
|
-
});
|
|
899
|
-
}
|
|
900
|
-
if (report.patterns.imports.aliased.length > 0) {
|
|
901
|
-
foundPatterns.set("Named Import with Alias", {
|
|
902
|
-
count: report.patterns.imports.aliased.length,
|
|
903
|
-
complexity: 2,
|
|
904
|
-
examples: report.patterns.imports.aliased.slice(0, 3)
|
|
905
|
-
});
|
|
906
|
-
}
|
|
907
|
-
if (report.patterns.imports.namespace.length > 0) {
|
|
908
|
-
foundPatterns.set("Namespace Import", {
|
|
909
|
-
count: report.patterns.imports.namespace.length,
|
|
910
|
-
complexity: 2,
|
|
911
|
-
examples: report.patterns.imports.namespace.slice(0, 3)
|
|
912
|
-
});
|
|
913
|
-
}
|
|
914
|
-
if (report.patterns.usage.variables.length > 0) {
|
|
915
|
-
foundPatterns.set("Variable Assignment", {
|
|
916
|
-
count: report.patterns.usage.variables.length,
|
|
917
|
-
complexity: 3,
|
|
918
|
-
examples: report.patterns.usage.variables.slice(0, 3)
|
|
919
|
-
});
|
|
920
|
-
}
|
|
921
|
-
if (report.patterns.usage.conditional.length > 0) {
|
|
922
|
-
foundPatterns.set("Conditional Assignment", {
|
|
923
|
-
count: report.patterns.usage.conditional.length,
|
|
924
|
-
complexity: 4,
|
|
925
|
-
examples: report.patterns.usage.conditional.slice(0, 3)
|
|
926
|
-
});
|
|
927
|
-
}
|
|
928
|
-
if (report.patterns.usage.objects.length > 0) {
|
|
929
|
-
foundPatterns.set("Object Mapping", {
|
|
930
|
-
count: report.patterns.usage.objects.length,
|
|
931
|
-
complexity: 5,
|
|
932
|
-
examples: report.patterns.usage.objects.slice(0, 3)
|
|
933
|
-
});
|
|
934
|
-
}
|
|
935
|
-
if (report.patterns.usage.arrays.length > 0) {
|
|
936
|
-
foundPatterns.set("Array Mapping", {
|
|
937
|
-
count: report.patterns.usage.arrays.length,
|
|
938
|
-
complexity: 5,
|
|
939
|
-
examples: report.patterns.usage.arrays.slice(0, 3)
|
|
940
|
-
});
|
|
941
|
-
}
|
|
942
|
-
if (report.patterns.advanced.lazy.length > 0) {
|
|
943
|
-
foundPatterns.set("Lazy Loading", {
|
|
944
|
-
count: report.patterns.advanced.lazy.length,
|
|
945
|
-
complexity: 6,
|
|
946
|
-
examples: report.patterns.advanced.lazy.slice(0, 3)
|
|
947
|
-
});
|
|
948
|
-
}
|
|
949
|
-
if (report.patterns.advanced.dynamic.length > 0) {
|
|
950
|
-
foundPatterns.set("Dynamic Import", {
|
|
951
|
-
count: report.patterns.advanced.dynamic.length,
|
|
952
|
-
complexity: 7,
|
|
953
|
-
examples: report.patterns.advanced.dynamic.slice(0, 3)
|
|
954
|
-
});
|
|
955
|
-
}
|
|
956
|
-
if (report.patterns.usage.destructuring.length > 0) {
|
|
957
|
-
foundPatterns.set("Destructuring Usage", {
|
|
958
|
-
count: report.patterns.usage.destructuring.length,
|
|
959
|
-
complexity: 4,
|
|
960
|
-
examples: report.patterns.usage.destructuring.slice(0, 3)
|
|
961
|
-
});
|
|
962
|
-
}
|
|
963
|
-
if (report.patterns.advanced.memo.length > 0) {
|
|
964
|
-
foundPatterns.set("Memoized Components", {
|
|
965
|
-
count: report.patterns.advanced.memo.length,
|
|
966
|
-
complexity: 5,
|
|
967
|
-
examples: report.patterns.advanced.memo.slice(0, 3)
|
|
968
|
-
});
|
|
969
|
-
}
|
|
970
|
-
if (report.patterns.advanced.forwardRef.length > 0) {
|
|
971
|
-
foundPatterns.set("Forward Ref", {
|
|
972
|
-
count: report.patterns.advanced.forwardRef.length,
|
|
973
|
-
complexity: 6,
|
|
974
|
-
examples: report.patterns.advanced.forwardRef.slice(0, 3)
|
|
975
|
-
});
|
|
976
|
-
}
|
|
977
|
-
if (report.patterns.advanced.portal.length > 0) {
|
|
978
|
-
foundPatterns.set("Portal Usage", {
|
|
979
|
-
count: report.patterns.advanced.portal.length,
|
|
980
|
-
complexity: 8,
|
|
981
|
-
examples: report.patterns.advanced.portal.slice(0, 3)
|
|
982
|
-
});
|
|
983
|
-
}
|
|
984
|
-
return { patterns, foundPatterns };
|
|
985
|
-
}
|
|
986
|
-
generateComplexityScore(foundPatterns) {
|
|
987
|
-
let totalScore = 0;
|
|
988
|
-
let maxPossibleScore = 0;
|
|
989
|
-
foundPatterns.forEach((data, patternName) => {
|
|
990
|
-
const weight = data.complexity;
|
|
991
|
-
const score = weight * data.count;
|
|
992
|
-
totalScore += score;
|
|
993
|
-
maxPossibleScore += weight * 10;
|
|
994
|
-
});
|
|
995
|
-
return {
|
|
996
|
-
score: totalScore,
|
|
997
|
-
maxPossible: maxPossibleScore,
|
|
998
|
-
percentage: Math.round(
|
|
999
|
-
totalScore / Math.max(maxPossibleScore, 1) * 100
|
|
1000
|
-
),
|
|
1001
|
-
level: this.getComplexityLevel(totalScore)
|
|
1002
|
-
};
|
|
1003
|
-
}
|
|
1004
|
-
getComplexityLevel(score) {
|
|
1005
|
-
if (score <= 10) return "Simple";
|
|
1006
|
-
if (score <= 30) return "Moderate";
|
|
1007
|
-
if (score <= 60) return "Complex";
|
|
1008
|
-
if (score <= 100) return "Very Complex";
|
|
1009
|
-
return "Extremely Complex";
|
|
1010
|
-
}
|
|
1011
|
-
generateRecommendations(foundPatterns, complexity) {
|
|
1012
|
-
const recommendations = [];
|
|
1013
|
-
if (foundPatterns.has("Dynamic Import") || foundPatterns.has("Portal Usage")) {
|
|
1014
|
-
recommendations.push({
|
|
1015
|
-
type: "Performance",
|
|
1016
|
-
priority: "High",
|
|
1017
|
-
message: "Consider code splitting strategies for dynamic imports",
|
|
1018
|
-
action: "Implement lazy loading boundaries"
|
|
1019
|
-
});
|
|
1020
|
-
}
|
|
1021
|
-
if (foundPatterns.has("Object Mapping") || foundPatterns.has("Array Mapping")) {
|
|
1022
|
-
recommendations.push({
|
|
1023
|
-
type: "Maintainability",
|
|
1024
|
-
priority: "Medium",
|
|
1025
|
-
message: "Component mappings can be hard to track",
|
|
1026
|
-
action: "Consider using TypeScript for better type safety"
|
|
1027
|
-
});
|
|
1028
|
-
}
|
|
1029
|
-
if (complexity.level === "Extremely Complex") {
|
|
1030
|
-
recommendations.push({
|
|
1031
|
-
type: "Architecture",
|
|
1032
|
-
priority: "High",
|
|
1033
|
-
message: "High complexity detected in component usage",
|
|
1034
|
-
action: "Consider refactoring to simpler patterns"
|
|
1035
|
-
});
|
|
1036
|
-
}
|
|
1037
|
-
if (foundPatterns.size > 8) {
|
|
1038
|
-
recommendations.push({
|
|
1039
|
-
type: "Consistency",
|
|
1040
|
-
priority: "Medium",
|
|
1041
|
-
message: "Many different usage patterns found",
|
|
1042
|
-
action: "Standardize on 2-3 primary patterns"
|
|
1043
|
-
});
|
|
1044
|
-
}
|
|
1045
|
-
return recommendations;
|
|
1046
|
-
}
|
|
1047
|
-
printFocusedReport(report) {
|
|
1048
|
-
const { patterns, foundPatterns } = this.classifyUsage(report);
|
|
1049
|
-
const complexity = this.generateComplexityScore(foundPatterns);
|
|
1050
|
-
const recommendations = this.generateRecommendations(
|
|
1051
|
-
foundPatterns,
|
|
1052
|
-
complexity
|
|
1053
|
-
);
|
|
1054
|
-
console.log("\n" + "\u{1F3AF}".repeat(40));
|
|
1055
|
-
console.log("\u{1F3AF} FOCUSED COMPONENT USAGE ANALYSIS");
|
|
1056
|
-
console.log("\u{1F3AF}".repeat(40));
|
|
1057
|
-
console.log(`
|
|
1058
|
-
\u{1F4CA} COMPLEXITY ANALYSIS:`);
|
|
926
|
+
}
|
|
927
|
+
for (const cond of report.patterns.usage.conditional) {
|
|
1059
928
|
console.log(
|
|
1060
|
-
`
|
|
929
|
+
chalk__default.default.gray(`[VERBOSE] Found Conditional usage: ${cond.consequent}`)
|
|
1061
930
|
);
|
|
931
|
+
}
|
|
932
|
+
for (const arr of report.patterns.usage.arrays) {
|
|
1062
933
|
console.log(
|
|
1063
|
-
|
|
934
|
+
chalk__default.default.gray(
|
|
935
|
+
`[VERBOSE] Found Array mapping with components: ${arr.components.join(", ")}`
|
|
936
|
+
)
|
|
1064
937
|
);
|
|
938
|
+
}
|
|
939
|
+
for (const variable of report.patterns.usage.variables) {
|
|
1065
940
|
console.log(
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
const sortedPatterns = Array.from(foundPatterns.entries()).sort(
|
|
1070
|
-
(a, b) => b[1].complexity - a[1].complexity
|
|
941
|
+
chalk__default.default.gray(
|
|
942
|
+
`[VERBOSE] Found Variable assignment: ${variable.variable} = ${variable.assignment}`
|
|
943
|
+
)
|
|
1071
944
|
);
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
console.log(
|
|
1075
|
-
`
|
|
1076
|
-
${this.getComplexityIcon(data.complexity)} ${patternName}:`
|
|
1077
|
-
);
|
|
1078
|
-
console.log(` Complexity: ${data.complexity}/10`);
|
|
1079
|
-
console.log(` Instances: ${data.count}`);
|
|
1080
|
-
console.log(` Description: ${patternInfo.description}`);
|
|
1081
|
-
if (data.examples.length > 0) {
|
|
1082
|
-
console.log(
|
|
1083
|
-
` Examples: ${JSON.stringify(data.examples[0], null, " ").slice(0, 100)}...`
|
|
1084
|
-
);
|
|
1085
|
-
}
|
|
1086
|
-
});
|
|
1087
|
-
console.log(`
|
|
1088
|
-
\u{1F4C8} COMPONENT FREQUENCY:`);
|
|
1089
|
-
report.patterns.usage.jsx.forEach((usage) => {
|
|
1090
|
-
console.log(` ${usage.component}: ${usage.count} uses`);
|
|
1091
|
-
});
|
|
1092
|
-
if (recommendations.length > 0) {
|
|
1093
|
-
console.log(`
|
|
1094
|
-
\u{1F4A1} RECOMMENDATIONS:`);
|
|
1095
|
-
recommendations.forEach((rec, idx) => {
|
|
1096
|
-
console.log(` ${idx + 1}. [${rec.priority}] ${rec.type}:`);
|
|
1097
|
-
console.log(` Issue: ${rec.message}`);
|
|
1098
|
-
console.log(` Action: ${rec.action}`);
|
|
1099
|
-
});
|
|
1100
|
-
}
|
|
1101
|
-
console.log(`
|
|
1102
|
-
\u{1F4CB} PATTERN COVERAGE:`);
|
|
1103
|
-
Object.keys(patterns).forEach((patternName) => {
|
|
1104
|
-
const found = foundPatterns.has(patternName);
|
|
1105
|
-
console.log(` ${found ? "\u2705" : "\u274C"} ${patternName}`);
|
|
1106
|
-
});
|
|
1107
|
-
console.log(`
|
|
1108
|
-
\u{1F3AF} USAGE DENSITY:`);
|
|
1109
|
-
const density = foundPatterns.size / Object.keys(patterns).length * 100;
|
|
1110
|
-
console.log(` Pattern Coverage: ${Math.round(density)}%`);
|
|
945
|
+
}
|
|
946
|
+
for (const destructure of report.patterns.usage.destructuring) {
|
|
1111
947
|
console.log(
|
|
1112
|
-
|
|
948
|
+
chalk__default.default.gray(
|
|
949
|
+
`[VERBOSE] Found Destructuring: ${destructure.property} from ${destructure.source}`
|
|
950
|
+
)
|
|
1113
951
|
);
|
|
1114
|
-
return { foundPatterns, complexity, recommendations };
|
|
1115
|
-
}
|
|
1116
|
-
getComplexityIcon(complexity) {
|
|
1117
|
-
if (complexity <= 2) return "\u{1F7E2}";
|
|
1118
|
-
if (complexity <= 4) return "\u{1F7E1}";
|
|
1119
|
-
if (complexity <= 6) return "\u{1F7E0}";
|
|
1120
|
-
return "\u{1F534}";
|
|
1121
|
-
}
|
|
1122
|
-
getUsageIntensity(score) {
|
|
1123
|
-
if (score <= 10) return "Light";
|
|
1124
|
-
if (score <= 30) return "Moderate";
|
|
1125
|
-
if (score <= 60) return "Heavy";
|
|
1126
|
-
return "Intensive";
|
|
1127
|
-
}
|
|
1128
|
-
analyzeMultipleFiles(filePatterns) {
|
|
1129
|
-
const allReports = [];
|
|
1130
|
-
const combinedAnalysis = {
|
|
1131
|
-
totalFiles: 0,
|
|
1132
|
-
totalComponents: /* @__PURE__ */ new Set(),
|
|
1133
|
-
patternFrequency: /* @__PURE__ */ new Map(),
|
|
1134
|
-
complexityDistribution: []
|
|
1135
|
-
};
|
|
1136
|
-
filePatterns.forEach((pattern) => {
|
|
1137
|
-
const files = this.findMatchingFiles(pattern);
|
|
1138
|
-
files.forEach((file) => {
|
|
1139
|
-
console.log(`
|
|
1140
|
-
\u{1F4C1} Analyzing: ${file}`);
|
|
1141
|
-
const report = this.analyzeFile(file);
|
|
1142
|
-
if (report) {
|
|
1143
|
-
allReports.push({ file, report });
|
|
1144
|
-
combinedAnalysis.totalFiles++;
|
|
1145
|
-
report.components.forEach(
|
|
1146
|
-
(comp) => combinedAnalysis.totalComponents.add(comp)
|
|
1147
|
-
);
|
|
1148
|
-
const { foundPatterns, complexity } = this.classifyUsage(report);
|
|
1149
|
-
combinedAnalysis.complexityDistribution.push({
|
|
1150
|
-
file,
|
|
1151
|
-
score: complexity.score,
|
|
1152
|
-
level: complexity.level
|
|
1153
|
-
});
|
|
1154
|
-
foundPatterns.forEach((data, pattern2) => {
|
|
1155
|
-
const current = combinedAnalysis.patternFrequency.get(pattern2) || 0;
|
|
1156
|
-
combinedAnalysis.patternFrequency.set(pattern2, current + 1);
|
|
1157
|
-
});
|
|
1158
|
-
}
|
|
1159
|
-
});
|
|
1160
|
-
});
|
|
1161
|
-
return { allReports, combinedAnalysis };
|
|
1162
|
-
}
|
|
1163
|
-
findMatchingFiles(pattern) {
|
|
1164
|
-
const glob5 = __require("glob");
|
|
1165
|
-
try {
|
|
1166
|
-
return glob5.sync(pattern);
|
|
1167
|
-
} catch (e) {
|
|
1168
|
-
console.warn(`Warning: Could not process pattern ${pattern}`);
|
|
1169
|
-
return [];
|
|
1170
|
-
}
|
|
1171
952
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
const allFiles = await glob.glob(pattern, {
|
|
1175
|
-
ignore: ignorePatterns || [
|
|
1176
|
-
"**/node_modules/**",
|
|
1177
|
-
"**/dist/**",
|
|
1178
|
-
"**/build/**"
|
|
1179
|
-
],
|
|
1180
|
-
nodir: true,
|
|
1181
|
-
absolute: true,
|
|
1182
|
-
// Only match React/JS/TS files
|
|
1183
|
-
matchBase: true
|
|
1184
|
-
});
|
|
1185
|
-
const reactFiles = allFiles.filter((file) => {
|
|
1186
|
-
var _a;
|
|
1187
|
-
const ext = (_a = file.split(".").pop()) == null ? void 0 : _a.toLowerCase();
|
|
1188
|
-
return ["tsx", "jsx", "ts", "js"].includes(ext || "");
|
|
1189
|
-
});
|
|
1190
|
-
return maxFiles ? reactFiles.slice(0, maxFiles) : reactFiles;
|
|
1191
|
-
}
|
|
1192
|
-
function generateReportFilename(commandType, extension = "json") {
|
|
1193
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1194
|
-
return `${commandType}-report-${timestamp}.${extension}`;
|
|
1195
|
-
}
|
|
1196
|
-
function addReportMetadata(data, commandType, additionalMetadata = {}) {
|
|
1197
|
-
return {
|
|
1198
|
-
...data,
|
|
1199
|
-
metadata: {
|
|
1200
|
-
...data.metadata,
|
|
1201
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1202
|
-
commandType,
|
|
1203
|
-
...additionalMetadata
|
|
1204
|
-
}
|
|
1205
|
-
};
|
|
1206
|
-
}
|
|
1207
|
-
function saveReport(options) {
|
|
1208
|
-
const { data, commandType, outputPath, format = "both" } = options;
|
|
1209
|
-
if (format === "console") {
|
|
1210
|
-
return "";
|
|
1211
|
-
}
|
|
1212
|
-
const filename = outputPath || generateReportFilename(commandType);
|
|
1213
|
-
const reportsDir = path__default.default.join(process.cwd(), "reports-outputs");
|
|
1214
|
-
if (!fs5__default.default.existsSync(reportsDir)) {
|
|
1215
|
-
fs5__default.default.mkdirSync(reportsDir, { recursive: true });
|
|
1216
|
-
}
|
|
1217
|
-
const finalPath = path__default.default.isAbsolute(filename) ? filename : path__default.default.join(reportsDir, filename);
|
|
1218
|
-
const reportWithMetadata = addReportMetadata(data, commandType);
|
|
1219
|
-
fs5__default.default.writeFileSync(finalPath, JSON.stringify(reportWithMetadata, null, 2));
|
|
1220
|
-
console.log(chalk8__default.default.blue(`
|
|
1221
|
-
\u{1F4C4} JSON report saved to: ${finalPath}`));
|
|
1222
|
-
return finalPath;
|
|
1223
|
-
}
|
|
1224
|
-
function getRankEmoji(rank) {
|
|
1225
|
-
switch (rank) {
|
|
1226
|
-
case 1:
|
|
1227
|
-
return "\u{1F947}";
|
|
1228
|
-
case 2:
|
|
1229
|
-
return "\u{1F948}";
|
|
1230
|
-
case 3:
|
|
1231
|
-
return "\u{1F949}";
|
|
1232
|
-
default:
|
|
1233
|
-
return "\u{1F4CD}";
|
|
953
|
+
for (const lazy of report.patterns.advanced.lazy) {
|
|
954
|
+
console.log(chalk__default.default.gray(`[VERBOSE] Found Lazy import: ${lazy.source}`));
|
|
1234
955
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
async function analyzeCommand(pattern, options) {
|
|
1239
|
-
const spinner = ora6__default.default("Finding files to analyze...").start();
|
|
1240
|
-
try {
|
|
1241
|
-
const files = await findFiles(pattern, options.ignore, options.maxFiles);
|
|
1242
|
-
if (files.length === 0) {
|
|
1243
|
-
spinner.fail(chalk8__default.default.red("No files found matching pattern: " + pattern));
|
|
1244
|
-
return;
|
|
1245
|
-
}
|
|
1246
|
-
spinner.succeed(chalk8__default.default.green(`Found ${files.length} files to analyze`));
|
|
1247
|
-
spinner.start("Analyzing files...");
|
|
1248
|
-
const analyzer = options.complexity ? new FocusedUsageAnalyzer(options.library) : new ReactComponentUsageAnalyzer(options.library);
|
|
1249
|
-
const aggregatedReport = {
|
|
1250
|
-
metadata: {
|
|
1251
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1252
|
-
commandType: "analyze",
|
|
1253
|
-
library: options.library,
|
|
1254
|
-
pattern,
|
|
1255
|
-
filesAnalyzed: 0,
|
|
1256
|
-
filesWithErrors: 0,
|
|
1257
|
-
totalFiles: files.length
|
|
1258
|
-
},
|
|
1259
|
-
files: [],
|
|
1260
|
-
aggregated: {
|
|
1261
|
-
allComponents: /* @__PURE__ */ new Set(),
|
|
1262
|
-
totalImports: 0,
|
|
1263
|
-
totalUsagePatterns: 0,
|
|
1264
|
-
patternFrequency: {},
|
|
1265
|
-
componentFrequency: {},
|
|
1266
|
-
fileComplexity: [],
|
|
1267
|
-
errors: []
|
|
1268
|
-
}
|
|
1269
|
-
};
|
|
1270
|
-
for (let i = 0; i < files.length; i++) {
|
|
1271
|
-
const file = files[i];
|
|
1272
|
-
spinner.text = `Analyzing files... (${i + 1}/${files.length}) ${path__default.default.basename(file)}`;
|
|
1273
|
-
try {
|
|
1274
|
-
const report = analyzer.analyzeFile(file);
|
|
1275
|
-
if (report) {
|
|
1276
|
-
aggregatedReport.metadata.filesAnalyzed++;
|
|
1277
|
-
const fileResult = {
|
|
1278
|
-
path: file,
|
|
1279
|
-
components: report.components,
|
|
1280
|
-
summary: report.summary,
|
|
1281
|
-
patterns: report.patterns
|
|
1282
|
-
};
|
|
1283
|
-
if (options.complexity) {
|
|
1284
|
-
const analysis = analyzer.classifyUsage(report);
|
|
1285
|
-
const complexity = analyzer.generateComplexityScore(
|
|
1286
|
-
analysis.foundPatterns
|
|
1287
|
-
);
|
|
1288
|
-
fileResult.complexity = complexity;
|
|
1289
|
-
aggregatedReport.aggregated.fileComplexity.push({
|
|
1290
|
-
file,
|
|
1291
|
-
score: complexity.score,
|
|
1292
|
-
level: complexity.level
|
|
1293
|
-
});
|
|
1294
|
-
}
|
|
1295
|
-
aggregatedReport.files.push(fileResult);
|
|
1296
|
-
report.components.forEach(
|
|
1297
|
-
(comp) => aggregatedReport.aggregated.allComponents.add(comp)
|
|
1298
|
-
);
|
|
1299
|
-
aggregatedReport.aggregated.totalImports += report.summary.totalImports;
|
|
1300
|
-
aggregatedReport.aggregated.totalUsagePatterns += report.summary.totalUsagePatterns;
|
|
1301
|
-
report.patterns.usage.jsx.forEach((usage) => {
|
|
1302
|
-
aggregatedReport.aggregated.componentFrequency[usage.component] = (aggregatedReport.aggregated.componentFrequency[usage.component] || 0) + usage.count;
|
|
1303
|
-
});
|
|
1304
|
-
Object.keys(report.patterns).forEach((category) => {
|
|
1305
|
-
Object.keys(report.patterns[category]).forEach((patternType) => {
|
|
1306
|
-
const pattern2 = report.patterns[category][patternType];
|
|
1307
|
-
const count = Array.isArray(pattern2) ? pattern2.length : 0;
|
|
1308
|
-
if (count > 0) {
|
|
1309
|
-
const key = `${category}.${patternType}`;
|
|
1310
|
-
aggregatedReport.aggregated.patternFrequency[key] = (aggregatedReport.aggregated.patternFrequency[key] || 0) + count;
|
|
1311
|
-
}
|
|
1312
|
-
});
|
|
1313
|
-
});
|
|
1314
|
-
}
|
|
1315
|
-
} catch (error) {
|
|
1316
|
-
aggregatedReport.metadata.filesWithErrors++;
|
|
1317
|
-
aggregatedReport.aggregated.errors.push({
|
|
1318
|
-
file,
|
|
1319
|
-
error: error.message
|
|
1320
|
-
});
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
aggregatedReport.aggregated.allComponents = Array.from(
|
|
1324
|
-
aggregatedReport.aggregated.allComponents
|
|
956
|
+
for (const dynamic of report.patterns.advanced.dynamic) {
|
|
957
|
+
console.log(
|
|
958
|
+
chalk__default.default.gray(`[VERBOSE] Found Dynamic import: ${dynamic.source}`)
|
|
1325
959
|
);
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
)
|
|
960
|
+
}
|
|
961
|
+
for (const hoc of report.patterns.advanced.hoc) {
|
|
962
|
+
console.log(
|
|
963
|
+
chalk__default.default.gray(`[VERBOSE] Found HOC: ${hoc.function}(${hoc.component})`)
|
|
1330
964
|
);
|
|
1331
|
-
if (options.format === "json" || options.format === "both") {
|
|
1332
|
-
saveReport({
|
|
1333
|
-
data: aggregatedReport,
|
|
1334
|
-
commandType: "analyze",
|
|
1335
|
-
outputPath: options.output,
|
|
1336
|
-
format: options.format
|
|
1337
|
-
});
|
|
1338
|
-
}
|
|
1339
|
-
if (options.format === "console" || options.format === "both") {
|
|
1340
|
-
printAggregatedReport(aggregatedReport, options);
|
|
1341
|
-
}
|
|
1342
|
-
} catch (error) {
|
|
1343
|
-
spinner.fail(chalk8__default.default.red("Analysis failed: " + error.message));
|
|
1344
|
-
console.error(error);
|
|
1345
965
|
}
|
|
1346
|
-
|
|
1347
|
-
function printAggregatedReport(report, options) {
|
|
1348
|
-
console.log("\n" + chalk8__default.default.bold.blue("\u2550".repeat(80)));
|
|
1349
|
-
console.log(chalk8__default.default.bold.blue(" \u{1F4CA} AGGREGATED ANALYSIS REPORT"));
|
|
1350
|
-
console.log(chalk8__default.default.bold.blue("\u2550".repeat(80)));
|
|
1351
|
-
console.log(chalk8__default.default.bold("\n\u{1F4C8} SUMMARY:"));
|
|
1352
|
-
console.log(` Library: ${chalk8__default.default.cyan(report.metadata.library)}`);
|
|
1353
|
-
console.log(
|
|
1354
|
-
` Files Analyzed: ${chalk8__default.default.green(report.metadata.filesAnalyzed)} / ${report.metadata.totalFiles}`
|
|
1355
|
-
);
|
|
1356
|
-
if (report.metadata.filesWithErrors > 0) {
|
|
966
|
+
for (const memo of report.patterns.advanced.memo) {
|
|
1357
967
|
console.log(
|
|
1358
|
-
`
|
|
968
|
+
chalk__default.default.gray(`[VERBOSE] Found Memoized component: ${memo.component}`)
|
|
1359
969
|
);
|
|
1360
970
|
}
|
|
971
|
+
if (report.patterns.advanced.forwardRef.length > 0) {
|
|
972
|
+
console.log(chalk__default.default.gray(`[VERBOSE] Found Forward Ref usage`));
|
|
973
|
+
}
|
|
974
|
+
if (report.patterns.advanced.portal.length > 0) {
|
|
975
|
+
console.log(chalk__default.default.gray(`[VERBOSE] Found Portal usage`));
|
|
976
|
+
}
|
|
977
|
+
console.log(chalk__default.default.gray(`[VERBOSE] ${"\u2500".repeat(80)}`));
|
|
978
|
+
}
|
|
979
|
+
function printSummary(aggregated, elapsedTimeSeconds) {
|
|
1361
980
|
console.log(
|
|
1362
|
-
|
|
981
|
+
chalk__default.default.green(
|
|
982
|
+
`[SUMMARY] Analysis completed successfully in ${elapsedTimeSeconds.toFixed(1)}s`
|
|
983
|
+
)
|
|
1363
984
|
);
|
|
1364
985
|
console.log(
|
|
1365
|
-
|
|
986
|
+
chalk__default.default.green(
|
|
987
|
+
`[SUMMARY] Files analyzed: ${aggregated.filesAnalyzed.toLocaleString()}`
|
|
988
|
+
)
|
|
1366
989
|
);
|
|
1367
990
|
console.log(
|
|
1368
|
-
|
|
991
|
+
chalk__default.default.green(
|
|
992
|
+
`[SUMMARY] Total imports: ${aggregated.totalImports.toLocaleString()}`
|
|
993
|
+
)
|
|
1369
994
|
);
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
});
|
|
1385
|
-
if (report.aggregated.fileComplexity.length > 0) {
|
|
1386
|
-
console.log(chalk8__default.default.bold("\n\u{1F4CA} COMPLEXITY ANALYSIS:"));
|
|
1387
|
-
const avgComplexity = report.aggregated.fileComplexity.reduce(
|
|
1388
|
-
(sum, f) => sum + f.score,
|
|
1389
|
-
0
|
|
1390
|
-
) / report.aggregated.fileComplexity.length;
|
|
995
|
+
console.log(
|
|
996
|
+
chalk__default.default.green(
|
|
997
|
+
`[SUMMARY] Total components: ${aggregated.totalComponents.toLocaleString()}`
|
|
998
|
+
)
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
function printDetails(aggregated) {
|
|
1002
|
+
console.log(
|
|
1003
|
+
chalk__default.default.cyan(
|
|
1004
|
+
`[DETAILS] Total usage patterns: ${aggregated.totalUsagePatterns.toLocaleString()}`
|
|
1005
|
+
)
|
|
1006
|
+
);
|
|
1007
|
+
for (const pattern of aggregated.patternCounts) {
|
|
1008
|
+
if (pattern.count > 0) {
|
|
1391
1009
|
console.log(
|
|
1392
|
-
|
|
1010
|
+
chalk__default.default.cyan(
|
|
1011
|
+
`[DETAILS] ${pattern.displayName}: ${pattern.count.toLocaleString()}`
|
|
1012
|
+
)
|
|
1393
1013
|
);
|
|
1394
|
-
const mostComplex = report.aggregated.fileComplexity.sort((a, b) => b.score - a.score).slice(0, 5);
|
|
1395
|
-
console.log(chalk8__default.default.bold("\n Most Complex Files:"));
|
|
1396
|
-
mostComplex.forEach((file, index) => {
|
|
1397
|
-
console.log(
|
|
1398
|
-
` ${index + 1}. ${path__default.default.basename(file.file)}: ${chalk8__default.default.yellow(file.score)} (${file.level})`
|
|
1399
|
-
);
|
|
1400
|
-
});
|
|
1401
1014
|
}
|
|
1402
1015
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1016
|
+
}
|
|
1017
|
+
function renderBarChart(data, options = {}) {
|
|
1018
|
+
const {
|
|
1019
|
+
maxWidth = 50,
|
|
1020
|
+
showValues = true,
|
|
1021
|
+
barChar = "\u2588",
|
|
1022
|
+
emptyChar = "\u2591"
|
|
1023
|
+
} = options;
|
|
1024
|
+
if (data.length === 0) {
|
|
1025
|
+
console.log(chalk__default.default.gray(" No data to display"));
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
const maxValue = Math.max(...data.map((d) => d.value));
|
|
1029
|
+
if (maxValue === 0) {
|
|
1030
|
+
console.log(chalk__default.default.gray(" All values are zero"));
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
const maxLabelLength = Math.max(...data.map((d) => d.label.length));
|
|
1034
|
+
for (const item of data) {
|
|
1035
|
+
const percentage = item.value / maxValue;
|
|
1036
|
+
const barLength = Math.round(percentage * maxWidth);
|
|
1037
|
+
const emptyLength = maxWidth - barLength;
|
|
1038
|
+
const paddedLabel = item.label.padEnd(maxLabelLength, " ");
|
|
1039
|
+
const bar = chalk__default.default.green(barChar.repeat(barLength)) + chalk__default.default.gray(emptyChar.repeat(emptyLength));
|
|
1040
|
+
const valueStr = showValues ? ` ${item.value.toLocaleString()}` : "";
|
|
1041
|
+
console.log(`${paddedLabel} ${bar}${valueStr}
|
|
1042
|
+
`);
|
|
1415
1043
|
}
|
|
1416
|
-
console.log("\n" + chalk8__default.default.bold.blue("\u2550".repeat(80)) + "\n");
|
|
1417
1044
|
}
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
metadata: {
|
|
1429
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1430
|
-
commandType: "compare",
|
|
1431
|
-
pattern,
|
|
1432
|
-
filesAnalyzed: files.length,
|
|
1433
|
-
libraries: options.libraries
|
|
1434
|
-
},
|
|
1435
|
-
libraries: []
|
|
1436
|
-
};
|
|
1437
|
-
for (const library of options.libraries) {
|
|
1438
|
-
spinner.start(`Analyzing ${library}...`);
|
|
1439
|
-
const analyzer = new FocusedUsageAnalyzer(library);
|
|
1440
|
-
let componentsFound = 0;
|
|
1441
|
-
let usagePatterns = 0;
|
|
1442
|
-
const components = /* @__PURE__ */ new Set();
|
|
1443
|
-
for (const file of files) {
|
|
1444
|
-
try {
|
|
1445
|
-
const report = analyzer.analyzeFile(file);
|
|
1446
|
-
if (report) {
|
|
1447
|
-
componentsFound += report.summary.totalComponents;
|
|
1448
|
-
usagePatterns += report.summary.totalUsagePatterns;
|
|
1449
|
-
report.components.forEach((comp) => components.add(comp));
|
|
1450
|
-
}
|
|
1451
|
-
} catch (error) {
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
const libraryResult = {
|
|
1455
|
-
name: library,
|
|
1456
|
-
componentsFound: components.size,
|
|
1457
|
-
totalUsagePatterns: usagePatterns,
|
|
1458
|
-
topComponents: Array.from(components).slice(0, 10)
|
|
1459
|
-
};
|
|
1460
|
-
comparisonResults.libraries.push(libraryResult);
|
|
1461
|
-
spinner.succeed(
|
|
1462
|
-
chalk8__default.default.green(`${library}: ${components.size} components found`)
|
|
1463
|
-
);
|
|
1464
|
-
}
|
|
1465
|
-
comparisonResults.libraries.sort(
|
|
1466
|
-
(a, b) => b.componentsFound - a.componentsFound
|
|
1467
|
-
);
|
|
1468
|
-
if (options.format === "json" || options.format === "both") {
|
|
1469
|
-
saveReport({
|
|
1470
|
-
data: comparisonResults,
|
|
1471
|
-
commandType: "compare",
|
|
1472
|
-
outputPath: options.output,
|
|
1473
|
-
format: options.format
|
|
1474
|
-
});
|
|
1475
|
-
}
|
|
1476
|
-
if (options.format === "console" || options.format === "both") {
|
|
1477
|
-
printComparisonReport(comparisonResults);
|
|
1478
|
-
}
|
|
1479
|
-
} catch (error) {
|
|
1480
|
-
spinner.fail(chalk8__default.default.red("Comparison failed: " + error.message));
|
|
1481
|
-
console.error(error);
|
|
1045
|
+
|
|
1046
|
+
// src/utils/print-top-components.ts
|
|
1047
|
+
function printTopComponents(aggregated, mode, topN = 10) {
|
|
1048
|
+
const topComponents = aggregated.topComponents.slice(0, topN);
|
|
1049
|
+
if (mode === "log") {
|
|
1050
|
+
printTopComponentsLog(topComponents);
|
|
1051
|
+
} else if (mode === "table") {
|
|
1052
|
+
printTopComponentsTable(topComponents);
|
|
1053
|
+
} else if (mode === "chart") {
|
|
1054
|
+
printTopComponentsChart(topComponents);
|
|
1482
1055
|
}
|
|
1483
1056
|
}
|
|
1484
|
-
function
|
|
1485
|
-
console.log(
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1057
|
+
function printTopComponentsLog(components) {
|
|
1058
|
+
console.log(chalk__default.default.yellow.bold(`
|
|
1059
|
+
\u{1F3C6} Top Components
|
|
1060
|
+
`));
|
|
1061
|
+
if (components.length === 0) {
|
|
1062
|
+
console.log(chalk__default.default.gray(" No components found"));
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
components.forEach((comp, idx) => {
|
|
1066
|
+
const rank = idx + 1;
|
|
1067
|
+
const emoji = rank === 1 ? "\u{1F947}" : rank === 2 ? "\u{1F948}" : rank === 3 ? "\u{1F949}" : " ";
|
|
1068
|
+
const sourceStr = comp.source !== "unknown" ? ` from ${comp.source}` : "";
|
|
1492
1069
|
console.log(
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
console.log("\n" + chalk8__default.default.bold.blue("\u2550".repeat(60)) + "\n");
|
|
1497
|
-
}
|
|
1498
|
-
function parseGitHubUrl(url) {
|
|
1499
|
-
if (/^[\w-]+\/[\w-]+$/.test(url)) {
|
|
1500
|
-
const [owner, repo] = url.split("/");
|
|
1501
|
-
return {
|
|
1502
|
-
owner,
|
|
1503
|
-
repo: repo.replace(".git", ""),
|
|
1504
|
-
url: `https://github.com/${owner}/${repo}`,
|
|
1505
|
-
shorthand: url
|
|
1506
|
-
};
|
|
1507
|
-
}
|
|
1508
|
-
const httpsMatch = url.match(/github\.com\/([^/]+)\/([^/.]+)/);
|
|
1509
|
-
if (httpsMatch) {
|
|
1510
|
-
return {
|
|
1511
|
-
owner: httpsMatch[1],
|
|
1512
|
-
repo: httpsMatch[2],
|
|
1513
|
-
url,
|
|
1514
|
-
shorthand: `${httpsMatch[1]}/${httpsMatch[2]}`
|
|
1515
|
-
};
|
|
1516
|
-
}
|
|
1517
|
-
throw new Error(
|
|
1518
|
-
`Invalid GitHub URL: ${url}. Expected format: https://github.com/owner/repo or owner/repo`
|
|
1519
|
-
);
|
|
1520
|
-
}
|
|
1521
|
-
function createTempDir() {
|
|
1522
|
-
return new Promise((resolve, reject) => {
|
|
1523
|
-
tmp__default.default.dir(
|
|
1524
|
-
{ unsafeCleanup: true, prefix: "react-analyzer-" },
|
|
1525
|
-
(err, dirPath, cleanup) => {
|
|
1526
|
-
if (err) reject(err);
|
|
1527
|
-
resolve({ path: dirPath, cleanup });
|
|
1528
|
-
}
|
|
1070
|
+
chalk__default.default.yellow(
|
|
1071
|
+
`[TOP-COMPONENTS] ${emoji} ${rank}. ${comp.name}${sourceStr}: ${comp.count} uses`
|
|
1072
|
+
)
|
|
1529
1073
|
);
|
|
1530
1074
|
});
|
|
1531
1075
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
];
|
|
1544
|
-
try {
|
|
1545
|
-
await git.clone(repoInfo.url, localPath, cloneOptions);
|
|
1546
|
-
return {
|
|
1547
|
-
...repoInfo,
|
|
1548
|
-
localPath,
|
|
1549
|
-
branch,
|
|
1550
|
-
success: true
|
|
1551
|
-
};
|
|
1552
|
-
} catch (error) {
|
|
1553
|
-
if (error instanceof Error && error.message.includes("not found") && branch === "main") {
|
|
1554
|
-
try {
|
|
1555
|
-
const fallbackOptions = [
|
|
1556
|
-
"--depth",
|
|
1557
|
-
"1",
|
|
1558
|
-
"--branch",
|
|
1559
|
-
"master",
|
|
1560
|
-
"--single-branch"
|
|
1561
|
-
];
|
|
1562
|
-
await git.clone(repoInfo.url, localPath, fallbackOptions);
|
|
1563
|
-
return {
|
|
1564
|
-
...repoInfo,
|
|
1565
|
-
localPath,
|
|
1566
|
-
branch: "master",
|
|
1567
|
-
success: true
|
|
1568
|
-
};
|
|
1569
|
-
} catch (retryError) {
|
|
1570
|
-
throw retryError;
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
throw error;
|
|
1574
|
-
}
|
|
1575
|
-
}
|
|
1576
|
-
async function cloneRepositories(repoUrls, targetDir, options = {}) {
|
|
1577
|
-
const spinner = ora6__default.default("Cloning repositories...").start();
|
|
1578
|
-
const clonedRepos = [];
|
|
1579
|
-
const errors = [];
|
|
1580
|
-
for (const repoUrl of repoUrls) {
|
|
1581
|
-
try {
|
|
1582
|
-
spinner.text = `Cloning ${repoUrl}...`;
|
|
1583
|
-
const repoInfo = await cloneRepository(repoUrl, targetDir, options);
|
|
1584
|
-
clonedRepos.push(repoInfo);
|
|
1585
|
-
spinner.succeed(
|
|
1586
|
-
chalk8__default.default.green(
|
|
1587
|
-
`\u2713 Cloned ${repoInfo.shorthand} (${repoInfo.branch} branch)`
|
|
1588
|
-
)
|
|
1589
|
-
);
|
|
1590
|
-
spinner.start();
|
|
1591
|
-
} catch (error) {
|
|
1592
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1593
|
-
errors.push({
|
|
1594
|
-
url: repoUrl,
|
|
1595
|
-
error: message
|
|
1596
|
-
});
|
|
1597
|
-
spinner.fail(chalk8__default.default.red(`\u2717 Failed to clone ${repoUrl}: ${message}`));
|
|
1598
|
-
spinner.start();
|
|
1076
|
+
function printTopComponentsTable(components) {
|
|
1077
|
+
console.log(chalk__default.default.yellow(`[TOP-COMPONENTS] TABLE`));
|
|
1078
|
+
if (components.length === 0) {
|
|
1079
|
+
console.log(chalk__default.default.gray(" No components found"));
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
const table = new Table__default.default({
|
|
1083
|
+
head: ["Rank", "Component", "Source", "Count"],
|
|
1084
|
+
style: {
|
|
1085
|
+
head: ["cyan"],
|
|
1086
|
+
border: ["gray"]
|
|
1599
1087
|
}
|
|
1600
|
-
}
|
|
1601
|
-
spinner.stop();
|
|
1602
|
-
if (clonedRepos.length === 0) {
|
|
1603
|
-
throw new Error("No repositories were successfully cloned");
|
|
1604
|
-
}
|
|
1605
|
-
console.log(
|
|
1606
|
-
chalk8__default.default.green(
|
|
1607
|
-
`
|
|
1608
|
-
Successfully cloned ${clonedRepos.length} of ${repoUrls.length} repositories
|
|
1609
|
-
`
|
|
1610
|
-
)
|
|
1611
|
-
);
|
|
1612
|
-
return { clonedRepos, errors };
|
|
1613
|
-
}
|
|
1614
|
-
async function findFilesInRepo(repo, pattern = "**/*.{tsx,jsx,ts,js}") {
|
|
1615
|
-
if (!repo.localPath) {
|
|
1616
|
-
throw new Error("Repository localPath is not defined");
|
|
1617
|
-
}
|
|
1618
|
-
const normalizedPath = repo.localPath.replace(/\\/g, "/");
|
|
1619
|
-
const searchPattern = `${normalizedPath}/${pattern}`;
|
|
1620
|
-
const files = await glob.glob(searchPattern, {
|
|
1621
|
-
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**"],
|
|
1622
|
-
nodir: true,
|
|
1623
|
-
windowsPathsNoEscape: true
|
|
1624
1088
|
});
|
|
1625
|
-
|
|
1089
|
+
components.forEach((comp, idx) => {
|
|
1090
|
+
const rank = idx + 1;
|
|
1091
|
+
const emoji = rank === 1 ? "\u{1F947}" : rank === 2 ? "\u{1F948}" : rank === 3 ? "\u{1F949}" : `${rank}.`;
|
|
1092
|
+
table.push([emoji, comp.name, comp.source, comp.count.toString()]);
|
|
1093
|
+
});
|
|
1094
|
+
console.log(table.toString());
|
|
1626
1095
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1096
|
+
function printTopComponentsChart(components) {
|
|
1097
|
+
console.log(chalk__default.default.yellow(`[TOP-COMPONENTS] CHART`));
|
|
1098
|
+
if (components.length === 0) {
|
|
1099
|
+
console.log(chalk__default.default.gray(" No components found"));
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
const data = components.map((comp) => ({
|
|
1103
|
+
label: comp.name,
|
|
1104
|
+
value: comp.count
|
|
1105
|
+
}));
|
|
1106
|
+
renderBarChart(data, { maxWidth: 50 });
|
|
1638
1107
|
}
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
}
|
|
1646
|
-
const normalizedPath = repoPath.replace(/\\/g, "/");
|
|
1647
|
-
const files = await glob.glob(`${normalizedPath}/**/*.{tsx,jsx,ts,js}`, {
|
|
1648
|
-
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"],
|
|
1649
|
-
nodir: true,
|
|
1650
|
-
windowsPathsNoEscape: true
|
|
1651
|
-
});
|
|
1652
|
-
const fileTypes = {
|
|
1653
|
-
tsx: 0,
|
|
1654
|
-
jsx: 0,
|
|
1655
|
-
ts: 0,
|
|
1656
|
-
js: 0
|
|
1657
|
-
};
|
|
1658
|
-
files.forEach((file) => {
|
|
1659
|
-
const ext = path__default.default.extname(file).slice(1);
|
|
1660
|
-
if (fileTypes.hasOwnProperty(ext)) {
|
|
1661
|
-
fileTypes[ext]++;
|
|
1662
|
-
}
|
|
1663
|
-
});
|
|
1664
|
-
return {
|
|
1665
|
-
packageJson,
|
|
1666
|
-
name: (packageJson == null ? void 0 : packageJson.name) || "unknown",
|
|
1667
|
-
version: (packageJson == null ? void 0 : packageJson.version) || "unknown",
|
|
1668
|
-
dependencies: (packageJson == null ? void 0 : packageJson.dependencies) || {},
|
|
1669
|
-
devDependencies: (packageJson == null ? void 0 : packageJson.devDependencies) || {},
|
|
1670
|
-
totalFiles: files.length,
|
|
1671
|
-
fileTypes
|
|
1672
|
-
};
|
|
1673
|
-
} catch (error) {
|
|
1674
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1675
|
-
return {
|
|
1676
|
-
error: message,
|
|
1677
|
-
name: "unknown",
|
|
1678
|
-
version: "unknown",
|
|
1679
|
-
dependencies: {},
|
|
1680
|
-
devDependencies: {},
|
|
1681
|
-
totalFiles: 0,
|
|
1682
|
-
fileTypes: { tsx: 0, jsx: 0, ts: 0, js: 0 }
|
|
1683
|
-
};
|
|
1108
|
+
function printComponentsUsage(aggregated, mode) {
|
|
1109
|
+
const components = aggregated.topComponents;
|
|
1110
|
+
if (mode === "table") {
|
|
1111
|
+
printComponentsUsageTable(components);
|
|
1112
|
+
} else if (mode === "chart") {
|
|
1113
|
+
printComponentsUsageChart(components);
|
|
1684
1114
|
}
|
|
1685
1115
|
}
|
|
1686
|
-
function
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
repoSummaries: []
|
|
1694
|
-
};
|
|
1695
|
-
const totalComponentsSet = /* @__PURE__ */ new Set();
|
|
1696
|
-
const totalImportsSet = /* @__PURE__ */ new Set();
|
|
1697
|
-
Object.entries(analysisResults.repositories).forEach(
|
|
1698
|
-
([repoName, repoData]) => {
|
|
1699
|
-
repoData.analysis.components.forEach(
|
|
1700
|
-
(comp) => totalComponentsSet.add(comp)
|
|
1701
|
-
);
|
|
1702
|
-
Object.keys(repoData.analysis.imports).forEach(
|
|
1703
|
-
(imp) => totalImportsSet.add(imp)
|
|
1704
|
-
);
|
|
1705
|
-
combined.componentsByRepo[repoName] = repoData.analysis.components;
|
|
1706
|
-
Object.entries(repoData.analysis.componentUsage).forEach(
|
|
1707
|
-
([comp, count]) => {
|
|
1708
|
-
combined.componentFrequency[comp] = (combined.componentFrequency[comp] || 0) + count;
|
|
1709
|
-
}
|
|
1710
|
-
);
|
|
1711
|
-
Object.entries(repoData.analysis.imports).forEach(([imp, data]) => {
|
|
1712
|
-
combined.importFrequency[imp] = (combined.importFrequency[imp] || 0) + data.count;
|
|
1713
|
-
});
|
|
1714
|
-
combined.repoSummaries.push({
|
|
1715
|
-
name: repoName,
|
|
1716
|
-
components: repoData.analysis.components.length,
|
|
1717
|
-
files: repoData.files.length,
|
|
1718
|
-
errors: repoData.analysis.errors.length,
|
|
1719
|
-
topComponents: Object.entries(repoData.analysis.componentUsage).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([comp, count]) => ({ component: comp, uses: count }))
|
|
1720
|
-
});
|
|
1721
|
-
}
|
|
1722
|
-
);
|
|
1723
|
-
combined.totalComponents = Array.from(totalComponentsSet);
|
|
1724
|
-
combined.totalImports = Array.from(totalImportsSet);
|
|
1725
|
-
return combined;
|
|
1726
|
-
}
|
|
1727
|
-
function cleanupTempDir(cleanup, tmpDir, keepRepos = false) {
|
|
1728
|
-
if (!keepRepos && cleanup) {
|
|
1729
|
-
try {
|
|
1730
|
-
cleanup();
|
|
1731
|
-
console.log(chalk8__default.default.gray("\n\u{1F9F9} Cleaned up temporary directory"));
|
|
1732
|
-
} catch (error) {
|
|
1733
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1734
|
-
console.warn(chalk8__default.default.yellow(`Warning: Failed to cleanup: ${message}`));
|
|
1735
|
-
}
|
|
1736
|
-
} else if (keepRepos && tmpDir) {
|
|
1737
|
-
console.log(chalk8__default.default.blue(`
|
|
1738
|
-
\u{1F4C1} Repositories kept in: ${tmpDir}`));
|
|
1116
|
+
function printComponentsUsageTable(components) {
|
|
1117
|
+
console.log(chalk__default.default.magenta.bold(`
|
|
1118
|
+
\u269B\uFE0F Components Usage
|
|
1119
|
+
`));
|
|
1120
|
+
if (components.length === 0) {
|
|
1121
|
+
console.log(chalk__default.default.gray(" No components found"));
|
|
1122
|
+
return;
|
|
1739
1123
|
}
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
const versions = {};
|
|
1746
|
-
if (lockData.packages) {
|
|
1747
|
-
Object.entries(lockData.packages).forEach(
|
|
1748
|
-
([pkgPath, pkgData]) => {
|
|
1749
|
-
if (!pkgPath || pkgPath === "") return;
|
|
1750
|
-
const pkgName = pkgPath.replace(/^node_modules\//, "");
|
|
1751
|
-
if (pkgData.version) {
|
|
1752
|
-
versions[pkgName] = pkgData.version;
|
|
1753
|
-
}
|
|
1754
|
-
}
|
|
1755
|
-
);
|
|
1756
|
-
}
|
|
1757
|
-
if (lockData.dependencies && Object.keys(versions).length === 0) {
|
|
1758
|
-
let extractVersions = function(deps, prefix = "") {
|
|
1759
|
-
Object.entries(deps).forEach(([name, data]) => {
|
|
1760
|
-
const fullName = prefix ? `${prefix}/${name}` : name;
|
|
1761
|
-
if (data.version) {
|
|
1762
|
-
versions[fullName] = data.version;
|
|
1763
|
-
}
|
|
1764
|
-
if (data.dependencies) {
|
|
1765
|
-
extractVersions(data.dependencies, fullName);
|
|
1766
|
-
}
|
|
1767
|
-
});
|
|
1768
|
-
};
|
|
1769
|
-
extractVersions(lockData.dependencies);
|
|
1124
|
+
const table = new Table__default.default({
|
|
1125
|
+
head: ["Component", "Source", "Version", "Count"],
|
|
1126
|
+
style: {
|
|
1127
|
+
head: ["cyan"],
|
|
1128
|
+
border: ["gray"]
|
|
1770
1129
|
}
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
}
|
|
1130
|
+
});
|
|
1131
|
+
components.forEach((comp) => {
|
|
1132
|
+
table.push([comp.name, comp.source, "0.0.0", comp.count.toString()]);
|
|
1133
|
+
});
|
|
1134
|
+
console.log(table.toString());
|
|
1777
1135
|
}
|
|
1778
|
-
function
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
if (match) {
|
|
1792
|
-
pkgName = match[1];
|
|
1793
|
-
}
|
|
1794
|
-
} else {
|
|
1795
|
-
const match = key.match(/^([^@]+)@/);
|
|
1796
|
-
if (match) {
|
|
1797
|
-
pkgName = match[1];
|
|
1798
|
-
}
|
|
1799
|
-
}
|
|
1800
|
-
if (value.version && (!versions[pkgName] || value.version)) {
|
|
1801
|
-
versions[pkgName] = value.version;
|
|
1802
|
-
}
|
|
1803
|
-
});
|
|
1804
|
-
return versions;
|
|
1805
|
-
} catch (error) {
|
|
1806
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1807
|
-
console.warn(`Warning: Could not parse yarn.lock: ${message}`);
|
|
1808
|
-
return {};
|
|
1809
|
-
}
|
|
1136
|
+
function printComponentsUsageChart(components) {
|
|
1137
|
+
console.log(chalk__default.default.magenta.bold(`
|
|
1138
|
+
\u269B\uFE0F Components Usage
|
|
1139
|
+
`));
|
|
1140
|
+
if (components.length === 0) {
|
|
1141
|
+
console.log(chalk__default.default.gray(" No components found"));
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
const data = components.map((comp) => ({
|
|
1145
|
+
label: comp.name,
|
|
1146
|
+
value: comp.count
|
|
1147
|
+
}));
|
|
1148
|
+
renderBarChart(data, { maxWidth: 50 });
|
|
1810
1149
|
}
|
|
1811
|
-
function
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
const rootImporter = lockData.importers["."];
|
|
1818
|
-
if (rootImporter) {
|
|
1819
|
-
if (rootImporter.dependencies) {
|
|
1820
|
-
Object.entries(rootImporter.dependencies).forEach(
|
|
1821
|
-
([name, data]) => {
|
|
1822
|
-
if (typeof data === "object" && data.version) {
|
|
1823
|
-
versions[name] = data.version;
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
);
|
|
1827
|
-
}
|
|
1828
|
-
if (rootImporter.devDependencies) {
|
|
1829
|
-
Object.entries(rootImporter.devDependencies).forEach(
|
|
1830
|
-
([name, data]) => {
|
|
1831
|
-
if (typeof data === "object" && data.version) {
|
|
1832
|
-
versions[name] = data.version;
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
);
|
|
1836
|
-
}
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
|
-
if (lockData.packages && Object.keys(versions).length === 0) {
|
|
1840
|
-
Object.keys(lockData.packages).forEach((key) => {
|
|
1841
|
-
const match = key.match(/\/(.+?)\/(\d+\.\d+\.\d+.*?)(?:_|$)/);
|
|
1842
|
-
if (match) {
|
|
1843
|
-
const [, pkgName, version] = match;
|
|
1844
|
-
versions[pkgName] = version;
|
|
1845
|
-
}
|
|
1846
|
-
});
|
|
1847
|
-
}
|
|
1848
|
-
if (lockData.dependencies && Object.keys(versions).length === 0) {
|
|
1849
|
-
Object.entries(lockData.dependencies).forEach(
|
|
1850
|
-
([name, versionSpec]) => {
|
|
1851
|
-
if (typeof versionSpec === "string" && !versionSpec.startsWith("link:")) {
|
|
1852
|
-
versions[name] = versionSpec;
|
|
1853
|
-
} else if (typeof versionSpec === "object" && versionSpec.version) {
|
|
1854
|
-
versions[name] = versionSpec.version;
|
|
1855
|
-
}
|
|
1856
|
-
}
|
|
1857
|
-
);
|
|
1858
|
-
}
|
|
1859
|
-
return versions;
|
|
1860
|
-
} catch (error) {
|
|
1861
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1862
|
-
console.warn(`Warning: Could not parse pnpm-lock.yaml: ${message}`);
|
|
1863
|
-
return {};
|
|
1150
|
+
function printPatterns(aggregated, mode) {
|
|
1151
|
+
const patterns = aggregated.patternCounts.filter((p) => p.count > 0);
|
|
1152
|
+
if (mode === "table") {
|
|
1153
|
+
printPatternsTable(patterns);
|
|
1154
|
+
} else if (mode === "chart") {
|
|
1155
|
+
printPatternsChart(patterns);
|
|
1864
1156
|
}
|
|
1865
1157
|
}
|
|
1866
|
-
function
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
{ name: "yarn.lock", parser: parseYarnLock, type: "yarn" },
|
|
1874
|
-
{ name: "pnpm-lock.yaml", parser: parsePnpmLock, type: "pnpm" }
|
|
1875
|
-
];
|
|
1876
|
-
for (const { name, parser, type } of lockfiles) {
|
|
1877
|
-
const lockfilePath = path__default.default.join(projectPath, name);
|
|
1878
|
-
if (fs5__default.default.existsSync(lockfilePath)) {
|
|
1879
|
-
const versions = parser(lockfilePath);
|
|
1880
|
-
return {
|
|
1881
|
-
versions,
|
|
1882
|
-
lockfileType: type,
|
|
1883
|
-
lockfilePath,
|
|
1884
|
-
found: true
|
|
1885
|
-
};
|
|
1886
|
-
}
|
|
1158
|
+
function printPatternsTable(patterns) {
|
|
1159
|
+
console.log(chalk__default.default.blue.bold(`
|
|
1160
|
+
\u{1F50D} Code Patterns
|
|
1161
|
+
`));
|
|
1162
|
+
if (patterns.length === 0) {
|
|
1163
|
+
console.log(chalk__default.default.gray(" No patterns found"));
|
|
1164
|
+
return;
|
|
1887
1165
|
}
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
}
|
|
1895
|
-
|
|
1896
|
-
|
|
1166
|
+
const table = new Table__default.default({
|
|
1167
|
+
head: ["Pattern", "Count"],
|
|
1168
|
+
style: {
|
|
1169
|
+
head: ["cyan"],
|
|
1170
|
+
border: ["gray"]
|
|
1171
|
+
}
|
|
1172
|
+
});
|
|
1173
|
+
patterns.forEach((pattern) => {
|
|
1174
|
+
table.push([pattern.displayName, pattern.count.toString()]);
|
|
1175
|
+
});
|
|
1176
|
+
console.log(table.toString());
|
|
1897
1177
|
}
|
|
1898
|
-
function
|
|
1899
|
-
|
|
1900
|
-
|
|
1178
|
+
function printPatternsChart(patterns) {
|
|
1179
|
+
console.log(chalk__default.default.blue(`[PATTERNS] CHART`));
|
|
1180
|
+
if (patterns.length === 0) {
|
|
1181
|
+
console.log(chalk__default.default.gray(" No patterns found"));
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
const data = patterns.map((pattern) => ({
|
|
1185
|
+
label: pattern.displayName,
|
|
1186
|
+
value: pattern.count
|
|
1187
|
+
}));
|
|
1188
|
+
renderBarChart(data, { maxWidth: 50 });
|
|
1901
1189
|
}
|
|
1902
1190
|
|
|
1903
|
-
// src/
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
pattern
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
const repoStats = {};
|
|
1930
|
-
const lockfileData = {};
|
|
1931
|
-
for (const repo of clonedRepos) {
|
|
1932
|
-
if (!repo.localPath) continue;
|
|
1933
|
-
const stats = await getRepoStats(repo.localPath);
|
|
1934
|
-
repoStats[repo.shorthand] = stats;
|
|
1935
|
-
const lockfileInfo = findAndParseLockfile(repo.localPath);
|
|
1936
|
-
lockfileData[repo.shorthand] = lockfileInfo;
|
|
1937
|
-
console.log(
|
|
1938
|
-
` ${chalk8__default.default.cyan(repo.shorthand)}: ${stats.name}@${stats.version} (${stats.totalFiles} files)`
|
|
1939
|
-
);
|
|
1940
|
-
if (lockfileInfo.found) {
|
|
1941
|
-
console.log(
|
|
1942
|
-
chalk8__default.default.gray(
|
|
1943
|
-
` Lockfile: ${lockfileInfo.lockfileType} (${Object.keys(lockfileInfo.versions).length} packages)`
|
|
1944
|
-
)
|
|
1945
|
-
);
|
|
1946
|
-
}
|
|
1947
|
-
}
|
|
1948
|
-
console.log(chalk8__default.default.bold("\n\u{1F52C} Analyzing repositories...\n"));
|
|
1949
|
-
const results = {};
|
|
1950
|
-
const spinner = ora6__default.default("Analyzing...").start();
|
|
1951
|
-
for (const [repoName, repoData] of Object.entries(filesByRepo)) {
|
|
1952
|
-
spinner.text = `Analyzing ${repoName}...`;
|
|
1953
|
-
const repoResults = {
|
|
1954
|
-
files: repoData.files,
|
|
1955
|
-
stats: repoStats[repoName],
|
|
1956
|
-
lockfile: lockfileData[repoName],
|
|
1957
|
-
analysis: {
|
|
1958
|
-
components: [],
|
|
1959
|
-
imports: {},
|
|
1960
|
-
componentUsage: {},
|
|
1961
|
-
errors: []
|
|
1962
|
-
}
|
|
1963
|
-
};
|
|
1964
|
-
const componentsSet = /* @__PURE__ */ new Set();
|
|
1965
|
-
const componentUsageTemp = /* @__PURE__ */ new Map();
|
|
1966
|
-
const importsTemp = /* @__PURE__ */ new Map();
|
|
1967
|
-
for (const file of repoData.files) {
|
|
1968
|
-
try {
|
|
1969
|
-
const report = analyzer.analyzeFile(file);
|
|
1970
|
-
if (report) {
|
|
1971
|
-
report.components.forEach((comp) => componentsSet.add(comp));
|
|
1972
|
-
report.patterns.usage.jsx.forEach((usage) => {
|
|
1973
|
-
const existing = componentUsageTemp.get(usage.component);
|
|
1974
|
-
if (!existing) {
|
|
1975
|
-
componentUsageTemp.set(usage.component, {
|
|
1976
|
-
count: usage.count,
|
|
1977
|
-
files: /* @__PURE__ */ new Set([file])
|
|
1978
|
-
});
|
|
1979
|
-
} else {
|
|
1980
|
-
existing.count += usage.count;
|
|
1981
|
-
existing.files.add(file);
|
|
1982
|
-
}
|
|
1983
|
-
});
|
|
1984
|
-
[
|
|
1985
|
-
...report.patterns.imports.default,
|
|
1986
|
-
...report.patterns.imports.named
|
|
1987
|
-
].forEach((imp) => {
|
|
1988
|
-
const name = imp.name || imp.local || "";
|
|
1989
|
-
const source = imp.source || imp.from;
|
|
1990
|
-
const existing = importsTemp.get(name);
|
|
1991
|
-
if (!existing) {
|
|
1992
|
-
importsTemp.set(name, {
|
|
1993
|
-
count: 1,
|
|
1994
|
-
files: /* @__PURE__ */ new Set([file]),
|
|
1995
|
-
source
|
|
1996
|
-
});
|
|
1997
|
-
} else {
|
|
1998
|
-
existing.count++;
|
|
1999
|
-
existing.files.add(file);
|
|
2000
|
-
}
|
|
2001
|
-
});
|
|
2002
|
-
}
|
|
2003
|
-
} catch (error) {
|
|
2004
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2005
|
-
repoResults.analysis.errors.push({
|
|
2006
|
-
file,
|
|
2007
|
-
error: message
|
|
2008
|
-
});
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2011
|
-
repoResults.analysis.components = Array.from(componentsSet);
|
|
2012
|
-
componentUsageTemp.forEach((value, key) => {
|
|
2013
|
-
repoResults.analysis.componentUsage[key] = {
|
|
2014
|
-
count: value.count,
|
|
2015
|
-
files: Array.from(value.files)
|
|
2016
|
-
};
|
|
2017
|
-
});
|
|
2018
|
-
importsTemp.forEach((value, key) => {
|
|
2019
|
-
repoResults.analysis.imports[key] = {
|
|
2020
|
-
count: value.count,
|
|
2021
|
-
files: Array.from(value.files),
|
|
2022
|
-
source: value.source
|
|
2023
|
-
};
|
|
2024
|
-
});
|
|
2025
|
-
results[repoName] = repoResults;
|
|
2026
|
-
spinner.succeed(
|
|
2027
|
-
chalk8__default.default.green(
|
|
2028
|
-
`\u2713 Analyzed ${repoName}: ${repoResults.analysis.components.length} components found`
|
|
2029
|
-
)
|
|
2030
|
-
);
|
|
2031
|
-
spinner.start();
|
|
2032
|
-
}
|
|
2033
|
-
spinner.stop();
|
|
2034
|
-
const analysisResults = {
|
|
2035
|
-
repositories: results,
|
|
2036
|
-
metadata: {
|
|
2037
|
-
analyzedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2038
|
-
totalRepositories: clonedRepos.length,
|
|
2039
|
-
failedClones: cloneErrors.length,
|
|
2040
|
-
pattern,
|
|
2041
|
-
branch,
|
|
2042
|
-
repoStats,
|
|
2043
|
-
lockfiles: lockfileData
|
|
2044
|
-
},
|
|
2045
|
-
cloneErrors
|
|
2046
|
-
};
|
|
2047
|
-
cleanupTempDir(cleanup, tmpDir, keepRepos);
|
|
2048
|
-
return analysisResults;
|
|
2049
|
-
} catch (error) {
|
|
2050
|
-
cleanupTempDir(cleanup, tmpDir, false);
|
|
2051
|
-
throw error;
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
function loadRepositoriesFromConfig(configPath) {
|
|
2055
|
-
try {
|
|
2056
|
-
const config = readJsonFile(configPath);
|
|
2057
|
-
return config.repositories || [];
|
|
2058
|
-
} catch (error) {
|
|
2059
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2060
|
-
throw new Error(`Failed to load config file: ${message}`);
|
|
2061
|
-
}
|
|
1191
|
+
// src/commands/scan.ts
|
|
1192
|
+
function registerScanCommand(program2) {
|
|
1193
|
+
program2.command("scan").description("Scan and analyze local files").argument(
|
|
1194
|
+
"[pattern]",
|
|
1195
|
+
"Glob pattern for files to analyze (defaults to current directory recursively)",
|
|
1196
|
+
"**/*.{tsx,jsx,ts,js}"
|
|
1197
|
+
).option(
|
|
1198
|
+
"--verbose",
|
|
1199
|
+
"Show detailed file-by-file analysis with every pattern found",
|
|
1200
|
+
false
|
|
1201
|
+
).option("--summary [mode]", "Show summary stats (log, false)", "log").option("--details", "Show detailed pattern counts").option(
|
|
1202
|
+
"--top-components [mode]",
|
|
1203
|
+
"Show top components (log, table, chart)",
|
|
1204
|
+
"log"
|
|
1205
|
+
).option(
|
|
1206
|
+
"--components-usage [mode]",
|
|
1207
|
+
"Show components table/chart (table, chart)",
|
|
1208
|
+
"table"
|
|
1209
|
+
).option(
|
|
1210
|
+
"--patterns [mode]",
|
|
1211
|
+
"Show patterns table/chart (table, chart)",
|
|
1212
|
+
"table"
|
|
1213
|
+
).action(async (pattern, options) => {
|
|
1214
|
+
const normalizedOptions = normalizeOptions(options);
|
|
1215
|
+
await executeScan(pattern, normalizedOptions);
|
|
1216
|
+
});
|
|
2062
1217
|
}
|
|
2063
|
-
function
|
|
2064
|
-
const combined = generateCombinedReport(analysisResults);
|
|
2065
|
-
const enhancedComponentFrequency = {};
|
|
2066
|
-
Object.entries(analysisResults.repositories).forEach(
|
|
2067
|
-
([repoName, repoData]) => {
|
|
2068
|
-
var _a;
|
|
2069
|
-
const lockfileData = repoData.lockfile || {};
|
|
2070
|
-
const version = (_a = lockfileData.versions) == null ? void 0 : _a[libraryName];
|
|
2071
|
-
Object.entries(repoData.analysis.componentUsage).forEach(
|
|
2072
|
-
([component, usage]) => {
|
|
2073
|
-
const key = version ? `${component} from ${libraryName}@${version}` : `${component} from ${libraryName}`;
|
|
2074
|
-
if (!enhancedComponentFrequency[key]) {
|
|
2075
|
-
enhancedComponentFrequency[key] = {
|
|
2076
|
-
component,
|
|
2077
|
-
library: libraryName,
|
|
2078
|
-
version: version || "unknown",
|
|
2079
|
-
count: 0,
|
|
2080
|
-
repos: []
|
|
2081
|
-
};
|
|
2082
|
-
}
|
|
2083
|
-
enhancedComponentFrequency[key].count += usage.count || 0;
|
|
2084
|
-
enhancedComponentFrequency[key].repos.push({
|
|
2085
|
-
name: repoName,
|
|
2086
|
-
count: usage.count || 0
|
|
2087
|
-
});
|
|
2088
|
-
}
|
|
2089
|
-
);
|
|
2090
|
-
}
|
|
2091
|
-
);
|
|
1218
|
+
function normalizeOptions(options) {
|
|
2092
1219
|
return {
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
enhancedComponentFrequency
|
|
2101
|
-
},
|
|
2102
|
-
repositories: analysisResults.repositories,
|
|
2103
|
-
cloneErrors: analysisResults.cloneErrors
|
|
1220
|
+
verbose: options.verbose || false,
|
|
1221
|
+
summary: options.summary === false || options.summary === "false" ? false : "log",
|
|
1222
|
+
details: options.details || false,
|
|
1223
|
+
topComponents: options.topComponents || "log",
|
|
1224
|
+
componentsUsage: options.componentsUsage || "table",
|
|
1225
|
+
patterns: options.patterns || "table",
|
|
1226
|
+
output: options.output
|
|
2104
1227
|
};
|
|
2105
1228
|
}
|
|
2106
|
-
function
|
|
2107
|
-
const
|
|
2108
|
-
|
|
2109
|
-
console.log(chalk8__default.default.bold.cyan(" \u{1F680} GITHUB REPOSITORIES ANALYSIS REPORT"));
|
|
2110
|
-
console.log(chalk8__default.default.bold.cyan("=".repeat(80) + "\n"));
|
|
2111
|
-
console.log(chalk8__default.default.bold("\u{1F4C8} SUMMARY:"));
|
|
2112
|
-
console.log(chalk8__default.default.gray(` Library: ${chalk8__default.default.cyan(metadata.library)}`));
|
|
2113
|
-
console.log(
|
|
2114
|
-
chalk8__default.default.gray(` Repositories Analyzed: ${metadata.repositories.length}`)
|
|
2115
|
-
);
|
|
2116
|
-
console.log(
|
|
2117
|
-
chalk8__default.default.gray(` Total Components Found: ${combined.totalComponents.length}`)
|
|
2118
|
-
);
|
|
2119
|
-
console.log(
|
|
2120
|
-
chalk8__default.default.gray(` Total Imports Found: ${combined.totalImports.length}`)
|
|
2121
|
-
);
|
|
2122
|
-
const topComponents = Object.entries(combined.componentFrequency).sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
2123
|
-
if (topComponents.length > 0) {
|
|
2124
|
-
console.log(chalk8__default.default.bold("\n\u{1F3C6} TOP COMPONENTS (Across All Repos):"));
|
|
2125
|
-
topComponents.forEach(([comp, count], idx) => {
|
|
2126
|
-
const rank = idx + 1;
|
|
2127
|
-
const emoji = rank === 1 ? "\u{1F947}" : rank === 2 ? "\u{1F948}" : rank === 3 ? "\u{1F949}" : " ";
|
|
2128
|
-
console.log(
|
|
2129
|
-
` ${emoji} ${rank}. ${chalk8__default.default.green(comp)}: ${chalk8__default.default.yellow(count)} uses`
|
|
2130
|
-
);
|
|
2131
|
-
});
|
|
2132
|
-
}
|
|
2133
|
-
if (combined.repoSummaries && combined.repoSummaries.length > 0) {
|
|
2134
|
-
console.log(chalk8__default.default.bold("\n\u{1F4E6} REPOSITORY SUMMARIES:\n"));
|
|
2135
|
-
combined.repoSummaries.forEach((summary, idx) => {
|
|
2136
|
-
console.log(` ${chalk8__default.default.bold(idx + 1 + ".")} ${chalk8__default.default.cyan(summary.name)}`);
|
|
2137
|
-
console.log(` Components: ${summary.components}`);
|
|
2138
|
-
console.log(` Files: ${summary.files}`);
|
|
2139
|
-
if (summary.topComponents && summary.topComponents.length > 0) {
|
|
2140
|
-
console.log(` Top Components:`);
|
|
2141
|
-
summary.topComponents.slice(0, 3).forEach((comp) => {
|
|
2142
|
-
console.log(` - ${comp.component}: ${comp.uses} uses`);
|
|
2143
|
-
});
|
|
2144
|
-
}
|
|
2145
|
-
console.log("");
|
|
2146
|
-
});
|
|
2147
|
-
}
|
|
2148
|
-
console.log(chalk8__default.default.bold("\u{1F50D} COMPONENT DISTRIBUTION:"));
|
|
2149
|
-
Object.entries(combined.componentsByRepo).forEach(([repo, components]) => {
|
|
2150
|
-
console.log(
|
|
2151
|
-
` ${chalk8__default.default.cyan(repo)}: ${components.length} unique components`
|
|
2152
|
-
);
|
|
2153
|
-
});
|
|
2154
|
-
console.log(chalk8__default.default.bold.cyan("\n" + "=".repeat(80)));
|
|
2155
|
-
console.log(chalk8__default.default.bold("\n\u{1F4A1} TIPS:"));
|
|
2156
|
-
console.log(" \u2022 Use --keep-repos to inspect cloned repositories locally");
|
|
2157
|
-
console.log(" \u2022 Use --branch <name> to analyze different branches");
|
|
2158
|
-
console.log(" \u2022 Use --pattern to customize which files to analyze");
|
|
2159
|
-
console.log(" \u2022 Use --config <file> to load repositories from JSON file");
|
|
2160
|
-
console.log(" \u2022 JSON report contains detailed per-repo analysis");
|
|
2161
|
-
console.log(chalk8__default.default.bold("\n\u{1F4DD} CONFIG FILE FORMAT:"));
|
|
2162
|
-
console.log(
|
|
2163
|
-
chalk8__default.default.gray(` {
|
|
2164
|
-
"repositories": [
|
|
2165
|
-
"owner/repo1",
|
|
2166
|
-
"owner/repo2",
|
|
2167
|
-
"https://github.com/owner/repo3"
|
|
2168
|
-
]
|
|
2169
|
-
}`)
|
|
2170
|
-
);
|
|
2171
|
-
console.log("");
|
|
2172
|
-
}
|
|
2173
|
-
|
|
2174
|
-
// src/commands/github.ts
|
|
2175
|
-
async function githubCommand(repos, options) {
|
|
2176
|
-
const spinner = ora6__default.default("Initializing GitHub analyzer...").start();
|
|
1229
|
+
async function executeScan(pattern, options) {
|
|
1230
|
+
const startTime = Date.now();
|
|
1231
|
+
const spinner = ora__default.default("Finding files...").start();
|
|
2177
1232
|
try {
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
try {
|
|
2182
|
-
repoList = loadRepositoriesFromConfig(options.config);
|
|
2183
|
-
spinner.succeed(
|
|
2184
|
-
chalk8__default.default.green(`Loaded ${repoList.length} repositories from config`)
|
|
2185
|
-
);
|
|
2186
|
-
spinner.start();
|
|
2187
|
-
} catch (error) {
|
|
2188
|
-
spinner.fail(chalk8__default.default.red(`Failed to load config file: ${error.message}`));
|
|
2189
|
-
process.exit(1);
|
|
2190
|
-
}
|
|
2191
|
-
}
|
|
2192
|
-
if (repoList.length === 0) {
|
|
2193
|
-
spinner.fail(
|
|
2194
|
-
chalk8__default.default.red(
|
|
2195
|
-
"No repositories specified. Use arguments or --config <file>"
|
|
2196
|
-
)
|
|
2197
|
-
);
|
|
2198
|
-
console.log(
|
|
2199
|
-
chalk8__default.default.yellow("\nExample: node cli.js github owner/repo1 owner/repo2")
|
|
2200
|
-
);
|
|
2201
|
-
console.log(chalk8__default.default.yellow("Or: node cli.js github --config repos.json"));
|
|
2202
|
-
process.exit(1);
|
|
2203
|
-
}
|
|
2204
|
-
spinner.succeed(chalk8__default.default.green("GitHub analyzer initialized"));
|
|
2205
|
-
const analyzer = options.complexity ? new FocusedUsageAnalyzer(options.library) : new ReactComponentUsageAnalyzer(options.library);
|
|
2206
|
-
const results = await analyzeGitHubRepositories(repoList, analyzer, {
|
|
2207
|
-
branch: options.branch,
|
|
2208
|
-
pattern: options.pattern,
|
|
2209
|
-
depth: parseInt(options.depth || "1"),
|
|
2210
|
-
keepRepos: options.keepRepos
|
|
1233
|
+
const files = await glob.glob(pattern, {
|
|
1234
|
+
ignore: ["node_modules/**", "dist/**", "build/**", ".git/**"],
|
|
1235
|
+
absolute: true
|
|
2211
1236
|
});
|
|
2212
|
-
const fullReport = createGitHubAnalysisReport(results, options.library);
|
|
2213
|
-
fullReport.metadata = {
|
|
2214
|
-
...fullReport.metadata,
|
|
2215
|
-
commandType: "github",
|
|
2216
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2217
|
-
};
|
|
2218
|
-
if (options.format === "json" || options.format === "both") {
|
|
2219
|
-
saveReport({
|
|
2220
|
-
data: fullReport,
|
|
2221
|
-
commandType: "github",
|
|
2222
|
-
outputPath: options.output,
|
|
2223
|
-
format: options.format
|
|
2224
|
-
});
|
|
2225
|
-
}
|
|
2226
|
-
if (options.format === "console" || options.format === "both") {
|
|
2227
|
-
formatGitHubReport(fullReport, options);
|
|
2228
|
-
}
|
|
2229
|
-
} catch (error) {
|
|
2230
|
-
spinner.fail(chalk8__default.default.red("GitHub analysis failed: " + error.message));
|
|
2231
|
-
console.error(error);
|
|
2232
|
-
process.exit(1);
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
|
|
2236
|
-
// src/cli.ts
|
|
2237
|
-
var program = new commander.Command();
|
|
2238
|
-
program.name("react-usage-analyzer").description("Analyze React component usage patterns in your codebase").version("1.0.0");
|
|
2239
|
-
program.command("analyze").description(
|
|
2240
|
-
"Analyze component usage patterns in files matching a glob pattern"
|
|
2241
|
-
).argument(
|
|
2242
|
-
"<pattern>",
|
|
2243
|
-
'Glob pattern for files to analyze (e.g., "src/**/*.tsx")'
|
|
2244
|
-
).option(
|
|
2245
|
-
"-l, --library <name>",
|
|
2246
|
-
"Library name to analyze (e.g., @mui/material)",
|
|
2247
|
-
"@design-system/foundation"
|
|
2248
|
-
).option(
|
|
2249
|
-
"-o, --output <file>",
|
|
2250
|
-
"Output file path for JSON report (defaults to timestamped filename)"
|
|
2251
|
-
).option(
|
|
2252
|
-
"-f, --format <type>",
|
|
2253
|
-
"Output format: json, console, or both",
|
|
2254
|
-
"both"
|
|
2255
|
-
).option("-c, --complexity", "Include complexity analysis", false).option(
|
|
2256
|
-
"-s, --summary-only",
|
|
2257
|
-
"Show only summary, not detailed patterns",
|
|
2258
|
-
false
|
|
2259
|
-
).option(
|
|
2260
|
-
"--ignore <patterns...>",
|
|
2261
|
-
'Glob patterns to ignore (e.g., "**/*.test.tsx")',
|
|
2262
|
-
[]
|
|
2263
|
-
).option("--max-files <number>", "Maximum number of files to analyze", "1000").action(async (pattern, options) => {
|
|
2264
|
-
await analyzeCommand(pattern, options);
|
|
2265
|
-
});
|
|
2266
|
-
program.command("compare").description("Compare usage patterns across multiple libraries").argument("<pattern>", "Glob pattern for files to analyze").option("-l, --libraries <names...>", "Library names to compare", [
|
|
2267
|
-
"@mui/material",
|
|
2268
|
-
"antd",
|
|
2269
|
-
"@chakra-ui/react"
|
|
2270
|
-
]).option(
|
|
2271
|
-
"-o, --output <file>",
|
|
2272
|
-
"Output file path for comparison report (defaults to timestamped filename)"
|
|
2273
|
-
).option(
|
|
2274
|
-
"-f, --format <type>",
|
|
2275
|
-
"Output format: json, console, or both",
|
|
2276
|
-
"both"
|
|
2277
|
-
).action(async (pattern, options) => {
|
|
2278
|
-
await compareCommand(pattern, options);
|
|
2279
|
-
});
|
|
2280
|
-
program.command("summary").description("Generate a quick summary of component usage").argument("<pattern>", "Glob pattern for files to analyze").option(
|
|
2281
|
-
"-l, --library <name>",
|
|
2282
|
-
"Library name to analyze",
|
|
2283
|
-
"@design-system/foundation"
|
|
2284
|
-
).option("--top <number>", "Number of top components to show", "10").action(async (pattern, options) => {
|
|
2285
|
-
await summaryCommand(pattern, options);
|
|
2286
|
-
});
|
|
2287
|
-
program.command("patterns").description("List all detected usage patterns").argument("<pattern>", "Glob pattern for files to analyze").option(
|
|
2288
|
-
"-l, --library <name>",
|
|
2289
|
-
"Library name to analyze",
|
|
2290
|
-
"@design-system/foundation"
|
|
2291
|
-
).option("--sort <by>", "Sort by: frequency, complexity, or name", "frequency").action(async (pattern, options) => {
|
|
2292
|
-
await patternsCommand(pattern, options);
|
|
2293
|
-
});
|
|
2294
|
-
program.command("stats").description("Show detailed statistics about component usage").argument("<pattern>", "Glob pattern for files to analyze").option(
|
|
2295
|
-
"-l, --library <name>",
|
|
2296
|
-
"Library name to analyze",
|
|
2297
|
-
"@design-system/foundation"
|
|
2298
|
-
).option("--chart", "Show ASCII charts", false).action(async (pattern, options) => {
|
|
2299
|
-
await statsCommand(pattern, options);
|
|
2300
|
-
});
|
|
2301
|
-
program.command("table").description("Show components and imports in table format").argument("<pattern>", "Glob pattern for files to analyze").option(
|
|
2302
|
-
"-l, --library <name>",
|
|
2303
|
-
"Library name to analyze",
|
|
2304
|
-
"@design-system/foundation"
|
|
2305
|
-
).option("-s, --sort <by>", "Sort by: uses, name, files, or props", "uses").option("-t, --top <number>", "Number of top items to show", "20").option("--props", "Show props analysis", false).action(async (pattern, options) => {
|
|
2306
|
-
await tableCommand(pattern, options);
|
|
2307
|
-
});
|
|
2308
|
-
program.command("github").description("Analyze GitHub repositories").argument(
|
|
2309
|
-
"[repos...]",
|
|
2310
|
-
"GitHub repository URLs or owner/repo format (or use --config)"
|
|
2311
|
-
).option(
|
|
2312
|
-
"-l, --library <name>",
|
|
2313
|
-
"Library name to analyze",
|
|
2314
|
-
"@design-system/foundation"
|
|
2315
|
-
).option("-b, --branch <name>", "Branch to analyze", "main").option(
|
|
2316
|
-
"-p, --pattern <glob>",
|
|
2317
|
-
"File pattern to analyze",
|
|
2318
|
-
"**/*.{tsx,jsx,ts,js}"
|
|
2319
|
-
).option(
|
|
2320
|
-
"-o, --output <file>",
|
|
2321
|
-
"Output JSON file (defaults to timestamped filename)"
|
|
2322
|
-
).option(
|
|
2323
|
-
"-f, --format <type>",
|
|
2324
|
-
"Output format: json, console, or both",
|
|
2325
|
-
"both"
|
|
2326
|
-
).option("--keep-repos", "Keep cloned repositories after analysis", false).option("--depth <number>", "Clone depth", "1").option("-c, --complexity", "Include complexity analysis", false).option("--config <file>", "Path to config file with repository list").action(async (repos, options) => {
|
|
2327
|
-
await githubCommand(repos, options);
|
|
2328
|
-
});
|
|
2329
|
-
async function summaryCommand(pattern, options) {
|
|
2330
|
-
const spinner = ora6__default.default("Generating summary...").start();
|
|
2331
|
-
try {
|
|
2332
|
-
const files = await findFiles2(pattern, [], 1e3);
|
|
2333
1237
|
if (files.length === 0) {
|
|
2334
|
-
spinner.fail(
|
|
1238
|
+
spinner.fail(chalk__default.default.red(`No files found matching pattern: ${pattern}`));
|
|
2335
1239
|
return;
|
|
2336
1240
|
}
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
for (
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
totalFiles++;
|
|
2345
|
-
report.patterns.usage.jsx.forEach((usage) => {
|
|
2346
|
-
if (!componentUsage[usage.component]) {
|
|
2347
|
-
componentUsage[usage.component] = { count: 0, files: /* @__PURE__ */ new Set() };
|
|
2348
|
-
}
|
|
2349
|
-
componentUsage[usage.component].count += usage.count;
|
|
2350
|
-
componentUsage[usage.component].files.add(file);
|
|
2351
|
-
});
|
|
2352
|
-
}
|
|
2353
|
-
} catch (error) {
|
|
1241
|
+
spinner.succeed(chalk__default.default.green(`Found ${files.length} files`));
|
|
1242
|
+
spinner.start("Analyzing files...");
|
|
1243
|
+
const reports = [];
|
|
1244
|
+
for (let i = 0; i < files.length; i++) {
|
|
1245
|
+
const file = files[i];
|
|
1246
|
+
if (!options.verbose) {
|
|
1247
|
+
spinner.text = `Analyzing files... (${i + 1}/${files.length})`;
|
|
2354
1248
|
}
|
|
2355
|
-
}
|
|
2356
|
-
spinner.succeed(chalk8__default.default.green("Summary generated"));
|
|
2357
|
-
console.log(chalk8__default.default.bold("\n\u{1F4CA} COMPONENT USAGE SUMMARY\n"));
|
|
2358
|
-
console.log(chalk8__default.default.gray(`Library: ${options.library}`));
|
|
2359
|
-
console.log(chalk8__default.default.gray(`Files analyzed: ${totalFiles}
|
|
2360
|
-
`));
|
|
2361
|
-
const topN = parseInt(options.top);
|
|
2362
|
-
const sorted = Object.entries(componentUsage).map(([comp, data]) => ({
|
|
2363
|
-
comp,
|
|
2364
|
-
count: data.count,
|
|
2365
|
-
files: data.files.size
|
|
2366
|
-
})).sort((a, b) => b.count - a.count).slice(0, topN);
|
|
2367
|
-
console.log(chalk8__default.default.bold(`Top ${topN} Components:`));
|
|
2368
|
-
sorted.forEach((item, index) => {
|
|
2369
|
-
const rank = index + 1;
|
|
2370
|
-
const emoji = rank === 1 ? "\u{1F947}" : rank === 2 ? "\u{1F948}" : rank === 3 ? "\u{1F949}" : " ";
|
|
2371
|
-
console.log(
|
|
2372
|
-
`${emoji} ${rank}. ${chalk8__default.default.cyan(item.comp)}: ${chalk8__default.default.yellow(item.count)} uses in ${chalk8__default.default.green(item.files)} files`
|
|
2373
|
-
);
|
|
2374
|
-
});
|
|
2375
|
-
} catch (error) {
|
|
2376
|
-
spinner.fail(chalk8__default.default.red("Summary failed: " + error.message));
|
|
2377
|
-
}
|
|
2378
|
-
}
|
|
2379
|
-
async function patternsCommand(pattern, options) {
|
|
2380
|
-
const spinner = ora6__default.default("Analyzing patterns...").start();
|
|
2381
|
-
try {
|
|
2382
|
-
const files = await findFiles2(pattern, [], 1e3);
|
|
2383
|
-
if (files.length === 0) {
|
|
2384
|
-
spinner.fail(chalk8__default.default.red("No files found"));
|
|
2385
|
-
return;
|
|
2386
|
-
}
|
|
2387
|
-
const analyzer = new FocusedUsageAnalyzer(options.library);
|
|
2388
|
-
const patternStats = {};
|
|
2389
|
-
for (const file of files) {
|
|
2390
1249
|
try {
|
|
2391
|
-
const report =
|
|
1250
|
+
const report = parseFile(file);
|
|
2392
1251
|
if (report) {
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
files: /* @__PURE__ */ new Set()
|
|
2400
|
-
};
|
|
2401
|
-
}
|
|
2402
|
-
patternStats[patternName].count += data.count;
|
|
2403
|
-
patternStats[patternName].files.add(file);
|
|
2404
|
-
});
|
|
1252
|
+
reports.push(report);
|
|
1253
|
+
if (options.verbose) {
|
|
1254
|
+
spinner.stop();
|
|
1255
|
+
printVerbose(file, report);
|
|
1256
|
+
spinner.start();
|
|
1257
|
+
}
|
|
2405
1258
|
}
|
|
2406
1259
|
} catch (error) {
|
|
1260
|
+
spinner.stop();
|
|
1261
|
+
console.error(chalk__default.default.red(`Error analyzing ${file}: ${error.message}`));
|
|
1262
|
+
spinner.start();
|
|
2407
1263
|
}
|
|
2408
1264
|
}
|
|
2409
|
-
spinner.succeed(
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
sorted = sorted.sort((a, b) => a[0].localeCompare(b[0]));
|
|
1265
|
+
spinner.succeed(
|
|
1266
|
+
chalk__default.default.green(`Analysis complete! Analyzed ${reports.length} files`)
|
|
1267
|
+
);
|
|
1268
|
+
const elapsedTime = (Date.now() - startTime) / 1e3;
|
|
1269
|
+
const aggregated = aggregateReports(reports);
|
|
1270
|
+
console.log("");
|
|
1271
|
+
if (options.summary) {
|
|
1272
|
+
printSummary(aggregated, elapsedTime);
|
|
2418
1273
|
}
|
|
2419
|
-
|
|
2420
|
-
const icon = getComplexityIcon(stats.complexity);
|
|
2421
|
-
console.log(`${icon} ${chalk8__default.default.bold(name)}`);
|
|
2422
|
-
console.log(` Complexity: ${stats.complexity}/10`);
|
|
2423
|
-
console.log(` Instances: ${chalk8__default.default.yellow(stats.count)}`);
|
|
2424
|
-
console.log(` Files: ${chalk8__default.default.green(stats.files.size)}`);
|
|
1274
|
+
if (options.details) {
|
|
2425
1275
|
console.log("");
|
|
2426
|
-
|
|
2427
|
-
} catch (error) {
|
|
2428
|
-
spinner.fail(chalk8__default.default.red("Pattern analysis failed: " + error.message));
|
|
2429
|
-
}
|
|
2430
|
-
}
|
|
2431
|
-
async function statsCommand(pattern, options) {
|
|
2432
|
-
const spinner = ora6__default.default("Generating statistics...").start();
|
|
2433
|
-
try {
|
|
2434
|
-
const files = await findFiles2(pattern, [], 1e3);
|
|
2435
|
-
if (files.length === 0) {
|
|
2436
|
-
spinner.fail(chalk8__default.default.red("No files found"));
|
|
2437
|
-
return;
|
|
2438
|
-
}
|
|
2439
|
-
const analyzer = new FocusedUsageAnalyzer(options.library);
|
|
2440
|
-
const stats = {
|
|
2441
|
-
totalFiles: files.length,
|
|
2442
|
-
analyzedFiles: 0,
|
|
2443
|
-
totalComponents: /* @__PURE__ */ new Set(),
|
|
2444
|
-
totalPatterns: 0,
|
|
2445
|
-
complexityDistribution: {
|
|
2446
|
-
Simple: 0,
|
|
2447
|
-
Moderate: 0,
|
|
2448
|
-
Complex: 0,
|
|
2449
|
-
"Very Complex": 0,
|
|
2450
|
-
"Extremely Complex": 0
|
|
2451
|
-
},
|
|
2452
|
-
avgComplexity: 0,
|
|
2453
|
-
topComponents: {},
|
|
2454
|
-
topPatterns: {}
|
|
2455
|
-
};
|
|
2456
|
-
let totalComplexityScore = 0;
|
|
2457
|
-
for (const file of files) {
|
|
2458
|
-
try {
|
|
2459
|
-
const report = analyzer.analyzeFile(file);
|
|
2460
|
-
if (report) {
|
|
2461
|
-
stats.analyzedFiles++;
|
|
2462
|
-
report.components.forEach((comp) => stats.totalComponents.add(comp));
|
|
2463
|
-
stats.totalPatterns += report.summary.totalUsagePatterns;
|
|
2464
|
-
const analysis = analyzer.classifyUsage(report);
|
|
2465
|
-
const complexity = analyzer.generateComplexityScore(
|
|
2466
|
-
analysis.foundPatterns
|
|
2467
|
-
);
|
|
2468
|
-
stats.complexityDistribution[complexity.level]++;
|
|
2469
|
-
totalComplexityScore += complexity.score;
|
|
2470
|
-
report.patterns.usage.jsx.forEach((usage) => {
|
|
2471
|
-
stats.topComponents[usage.component] = (stats.topComponents[usage.component] || 0) + usage.count;
|
|
2472
|
-
});
|
|
2473
|
-
analysis.foundPatterns.forEach((data, patternName) => {
|
|
2474
|
-
stats.topPatterns[patternName] = (stats.topPatterns[patternName] || 0) + data.count;
|
|
2475
|
-
});
|
|
2476
|
-
}
|
|
2477
|
-
} catch (error) {
|
|
2478
|
-
}
|
|
1276
|
+
printDetails(aggregated);
|
|
2479
1277
|
}
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
console.log(chalk8__default.default.bold("\n\u{1F4C8} DETAILED STATISTICS\n"));
|
|
2484
|
-
console.log(chalk8__default.default.cyan("Overview:"));
|
|
2485
|
-
console.log(` Total Files: ${stats.totalFiles}`);
|
|
2486
|
-
console.log(` Analyzed Files: ${chalk8__default.default.green(stats.analyzedFiles)}`);
|
|
2487
|
-
console.log(` Unique Components: ${chalk8__default.default.yellow(stats.totalComponents)}`);
|
|
2488
|
-
console.log(` Total Patterns: ${chalk8__default.default.yellow(stats.totalPatterns)}`);
|
|
2489
|
-
console.log(
|
|
2490
|
-
` Average Complexity: ${chalk8__default.default.yellow(stats.avgComplexity.toFixed(2))}`
|
|
2491
|
-
);
|
|
2492
|
-
console.log(chalk8__default.default.cyan("\nComplexity Distribution:"));
|
|
2493
|
-
Object.entries(stats.complexityDistribution).forEach(([level, count]) => {
|
|
2494
|
-
if (count > 0) {
|
|
2495
|
-
const percentage = (count / stats.analyzedFiles * 100).toFixed(1);
|
|
2496
|
-
const bar = options.chart ? createBar(count, stats.analyzedFiles, 30) : "";
|
|
2497
|
-
console.log(
|
|
2498
|
-
` ${level.padEnd(20)} ${count.toString().padStart(4)} (${percentage}%) ${bar}`
|
|
2499
|
-
);
|
|
2500
|
-
}
|
|
2501
|
-
});
|
|
2502
|
-
console.log(chalk8__default.default.cyan("\nTop 5 Components:"));
|
|
2503
|
-
Object.entries(stats.topComponents).sort((a, b) => b[1] - a[1]).slice(0, 5).forEach(([comp, count], index) => {
|
|
2504
|
-
console.log(
|
|
2505
|
-
` ${index + 1}. ${comp.padEnd(30)} ${chalk8__default.default.yellow(count)} uses`
|
|
2506
|
-
);
|
|
2507
|
-
});
|
|
2508
|
-
console.log(chalk8__default.default.cyan("\nTop 5 Patterns:"));
|
|
2509
|
-
Object.entries(stats.topPatterns).sort((a, b) => b[1] - a[1]).slice(0, 5).forEach(([pattern2, count], index) => {
|
|
2510
|
-
console.log(
|
|
2511
|
-
` ${index + 1}. ${pattern2.padEnd(30)} ${chalk8__default.default.yellow(count)} instances`
|
|
2512
|
-
);
|
|
2513
|
-
});
|
|
2514
|
-
} catch (error) {
|
|
2515
|
-
spinner.fail(chalk8__default.default.red("Stats generation failed: " + error.message));
|
|
2516
|
-
}
|
|
2517
|
-
}
|
|
2518
|
-
async function tableCommand(pattern, options) {
|
|
2519
|
-
const spinner = ora6__default.default("Generating table...").start();
|
|
2520
|
-
try {
|
|
2521
|
-
const files = await findFiles2(pattern, [], 1e3);
|
|
2522
|
-
if (files.length === 0) {
|
|
2523
|
-
spinner.fail(chalk8__default.default.red("No files found"));
|
|
2524
|
-
return;
|
|
1278
|
+
if (options.topComponents) {
|
|
1279
|
+
console.log("");
|
|
1280
|
+
printTopComponents(aggregated, options.topComponents);
|
|
2525
1281
|
}
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
for (const file of files) {
|
|
2530
|
-
try {
|
|
2531
|
-
const report = analyzer.analyzeFile(file);
|
|
2532
|
-
if (report) {
|
|
2533
|
-
report.patterns.usage.jsx.forEach((usage) => {
|
|
2534
|
-
if (!componentData[usage.component]) {
|
|
2535
|
-
componentData[usage.component] = {
|
|
2536
|
-
uses: 0,
|
|
2537
|
-
files: /* @__PURE__ */ new Set(),
|
|
2538
|
-
props: /* @__PURE__ */ new Set(),
|
|
2539
|
-
spreadProps: 0,
|
|
2540
|
-
propsDetails: []
|
|
2541
|
-
};
|
|
2542
|
-
}
|
|
2543
|
-
componentData[usage.component].uses += usage.count;
|
|
2544
|
-
componentData[usage.component].files.add(file);
|
|
2545
|
-
usage.usages.forEach((u) => {
|
|
2546
|
-
if (u.propsAnalysis) {
|
|
2547
|
-
u.propsAnalysis.namedProps.forEach(
|
|
2548
|
-
(prop) => componentData[usage.component].props.add(prop)
|
|
2549
|
-
);
|
|
2550
|
-
if (u.propsAnalysis.hasSpread) {
|
|
2551
|
-
componentData[usage.component].spreadProps++;
|
|
2552
|
-
}
|
|
2553
|
-
}
|
|
2554
|
-
});
|
|
2555
|
-
});
|
|
2556
|
-
[
|
|
2557
|
-
...report.patterns.imports.default,
|
|
2558
|
-
...report.patterns.imports.named
|
|
2559
|
-
].forEach((imp) => {
|
|
2560
|
-
const name = imp.name || imp.local;
|
|
2561
|
-
if (!importData[name]) {
|
|
2562
|
-
importData[name] = {
|
|
2563
|
-
files: /* @__PURE__ */ new Set(),
|
|
2564
|
-
type: imp.imported ? "named" : "default"
|
|
2565
|
-
};
|
|
2566
|
-
}
|
|
2567
|
-
importData[name].files.add(file);
|
|
2568
|
-
});
|
|
2569
|
-
}
|
|
2570
|
-
} catch (error) {
|
|
2571
|
-
}
|
|
1282
|
+
if (options.componentsUsage) {
|
|
1283
|
+
console.log("");
|
|
1284
|
+
printComponentsUsage(aggregated, options.componentsUsage);
|
|
2572
1285
|
}
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
componentData[key].propsCount = componentData[key].props.size;
|
|
2577
|
-
componentData[key].propsList = Array.from(componentData[key].props);
|
|
2578
|
-
});
|
|
2579
|
-
Object.keys(importData).forEach((key) => {
|
|
2580
|
-
importData[key].filesCount = importData[key].files.size;
|
|
2581
|
-
});
|
|
2582
|
-
const sortedComponents = Object.entries(componentData).sort((a, b) => {
|
|
2583
|
-
switch (options.sort) {
|
|
2584
|
-
case "name":
|
|
2585
|
-
return a[0].localeCompare(b[0]);
|
|
2586
|
-
case "files":
|
|
2587
|
-
return b[1].filesCount - a[1].filesCount;
|
|
2588
|
-
case "props":
|
|
2589
|
-
return b[1].propsCount - a[1].propsCount;
|
|
2590
|
-
case "uses":
|
|
2591
|
-
default:
|
|
2592
|
-
return b[1].uses - a[1].uses;
|
|
2593
|
-
}
|
|
2594
|
-
}).slice(0, parseInt(options.top));
|
|
2595
|
-
const sortedImports = Object.entries(importData).sort((a, b) => {
|
|
2596
|
-
if (options.sort === "name") {
|
|
2597
|
-
return a[0].localeCompare(b[0]);
|
|
2598
|
-
}
|
|
2599
|
-
return b[1].filesCount - a[1].filesCount;
|
|
2600
|
-
}).slice(0, parseInt(options.top));
|
|
2601
|
-
console.log(chalk8__default.default.bold("\n\u{1F4CA} COMPONENT USAGE TABLE\n"));
|
|
2602
|
-
const componentTable = new Table2__default.default({
|
|
2603
|
-
head: [
|
|
2604
|
-
chalk8__default.default.cyan("Component"),
|
|
2605
|
-
chalk8__default.default.cyan("Uses"),
|
|
2606
|
-
chalk8__default.default.cyan("Files"),
|
|
2607
|
-
...options.props ? [chalk8__default.default.cyan("Props"), chalk8__default.default.cyan("Spread")] : []
|
|
2608
|
-
],
|
|
2609
|
-
colWidths: [30, 10, 10, ...options.props ? [40, 10] : []],
|
|
2610
|
-
style: { head: [], border: [] }
|
|
2611
|
-
});
|
|
2612
|
-
sortedComponents.forEach(([component, data]) => {
|
|
2613
|
-
const row = [
|
|
2614
|
-
component,
|
|
2615
|
-
chalk8__default.default.yellow(data.uses),
|
|
2616
|
-
chalk8__default.default.green(data.filesCount)
|
|
2617
|
-
];
|
|
2618
|
-
if (options.props) {
|
|
2619
|
-
const propsDisplay = data.propsCount > 0 ? data.propsList.slice(0, 5).join(", ") + (data.propsCount > 5 ? "..." : "") : "-";
|
|
2620
|
-
const spreadDisplay = data.spreadProps > 0 ? chalk8__default.default.red(`\u26A0 ${data.spreadProps}`) : chalk8__default.default.gray("0");
|
|
2621
|
-
row.push(propsDisplay, spreadDisplay);
|
|
2622
|
-
}
|
|
2623
|
-
componentTable.push(row);
|
|
2624
|
-
});
|
|
2625
|
-
console.log(componentTable.toString());
|
|
2626
|
-
console.log(chalk8__default.default.bold("\n\u{1F4E6} IMPORTS TABLE\n"));
|
|
2627
|
-
const importTable = new Table2__default.default({
|
|
2628
|
-
head: [chalk8__default.default.cyan("Import"), chalk8__default.default.cyan("Type"), chalk8__default.default.cyan("Files")],
|
|
2629
|
-
colWidths: [30, 15, 10],
|
|
2630
|
-
style: { head: [], border: [] }
|
|
2631
|
-
});
|
|
2632
|
-
sortedImports.forEach(([name, data]) => {
|
|
2633
|
-
importTable.push([
|
|
2634
|
-
name,
|
|
2635
|
-
data.type === "named" ? chalk8__default.default.blue("named") : chalk8__default.default.green("default"),
|
|
2636
|
-
chalk8__default.default.yellow(data.filesCount)
|
|
2637
|
-
]);
|
|
2638
|
-
});
|
|
2639
|
-
console.log(importTable.toString());
|
|
2640
|
-
if (options.props) {
|
|
2641
|
-
console.log(chalk8__default.default.bold("\n\u{1F527} PROPS ANALYSIS\n"));
|
|
2642
|
-
sortedComponents.forEach(([component, data]) => {
|
|
2643
|
-
if (data.propsCount > 0 || data.spreadProps > 0) {
|
|
2644
|
-
console.log(chalk8__default.default.cyan(`${component}:`));
|
|
2645
|
-
console.log(` Props: ${data.propsList.join(", ")}`);
|
|
2646
|
-
if (data.spreadProps > 0) {
|
|
2647
|
-
console.log(
|
|
2648
|
-
chalk8__default.default.yellow(
|
|
2649
|
-
` \u26A0 Warning: ${data.spreadProps} usage(s) with spread props (cannot analyze statically)`
|
|
2650
|
-
)
|
|
2651
|
-
);
|
|
2652
|
-
}
|
|
2653
|
-
console.log("");
|
|
2654
|
-
}
|
|
2655
|
-
});
|
|
1286
|
+
if (options.patterns) {
|
|
1287
|
+
console.log("");
|
|
1288
|
+
printPatterns(aggregated, options.patterns);
|
|
2656
1289
|
}
|
|
2657
|
-
console.log(chalk8__default.default.bold("\u{1F4C8} SUMMARY"));
|
|
2658
|
-
console.log(` Total Components: ${Object.keys(componentData).length}`);
|
|
2659
|
-
console.log(` Total Imports: ${Object.keys(importData).length}`);
|
|
2660
|
-
console.log(` Files Analyzed: ${files.length}`);
|
|
2661
|
-
console.log(` Sort: ${options.sort}`);
|
|
2662
1290
|
} catch (error) {
|
|
2663
|
-
spinner.fail(
|
|
1291
|
+
spinner.fail(chalk__default.default.red("Analysis failed: " + error.message));
|
|
1292
|
+
console.error(error);
|
|
1293
|
+
process.exit(1);
|
|
2664
1294
|
}
|
|
2665
1295
|
}
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
const ext = path__default.default.extname(file).toLowerCase();
|
|
2676
|
-
return [".tsx", ".jsx", ".ts", ".js"].includes(ext);
|
|
2677
|
-
});
|
|
2678
|
-
return reactFiles.slice(0, maxFiles);
|
|
2679
|
-
}
|
|
2680
|
-
function getComplexityIcon(complexity) {
|
|
2681
|
-
if (complexity <= 2) return chalk8__default.default.green("\u{1F7E2}");
|
|
2682
|
-
if (complexity <= 4) return chalk8__default.default.yellow("\u{1F7E1}");
|
|
2683
|
-
if (complexity <= 6) return chalk8__default.default.hex("#FFA500")("\u{1F7E0}");
|
|
2684
|
-
return chalk8__default.default.red("\u{1F534}");
|
|
2685
|
-
}
|
|
2686
|
-
function createBar(value, max, width) {
|
|
2687
|
-
const filled = Math.round(value / max * width);
|
|
2688
|
-
const empty = width - filled;
|
|
2689
|
-
return chalk8__default.default.green("\u2588".repeat(filled)) + chalk8__default.default.gray("\u2591".repeat(empty));
|
|
2690
|
-
}
|
|
1296
|
+
|
|
1297
|
+
// package.json
|
|
1298
|
+
var package_default = {
|
|
1299
|
+
version: "1.0.0-beta.1"};
|
|
1300
|
+
|
|
1301
|
+
// src/cli.ts
|
|
1302
|
+
var program = new commander.Command();
|
|
1303
|
+
program.name("hermex").description("Analyze React component usage patterns in your codebase").version(package_default.version);
|
|
1304
|
+
registerScanCommand(program);
|
|
2691
1305
|
program.parse(process.argv);
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
}
|
|
1306
|
+
|
|
1307
|
+
exports.program = program;
|
|
2695
1308
|
//# sourceMappingURL=cli.js.map
|
|
2696
1309
|
//# sourceMappingURL=cli.js.map
|