test-genie-mcp 2.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 +219 -0
- package/dist/analyzers/astAnalyzer.d.ts +108 -0
- package/dist/analyzers/astAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/astAnalyzer.js +800 -0
- package/dist/analyzers/astAnalyzer.js.map +1 -0
- package/dist/analyzers/performanceAnalyzer.d.ts +90 -0
- package/dist/analyzers/performanceAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/performanceAnalyzer.js +647 -0
- package/dist/analyzers/performanceAnalyzer.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +683 -0
- package/dist/index.js.map +1 -0
- package/dist/platforms/android/index.d.ts +90 -0
- package/dist/platforms/android/index.d.ts.map +1 -0
- package/dist/platforms/android/index.js +519 -0
- package/dist/platforms/android/index.js.map +1 -0
- package/dist/platforms/flutter/index.d.ts +105 -0
- package/dist/platforms/flutter/index.d.ts.map +1 -0
- package/dist/platforms/flutter/index.js +533 -0
- package/dist/platforms/flutter/index.js.map +1 -0
- package/dist/platforms/ios/index.d.ts +79 -0
- package/dist/platforms/ios/index.d.ts.map +1 -0
- package/dist/platforms/ios/index.js +393 -0
- package/dist/platforms/ios/index.js.map +1 -0
- package/dist/platforms/react-native/index.d.ts +160 -0
- package/dist/platforms/react-native/index.d.ts.map +1 -0
- package/dist/platforms/react-native/index.js +602 -0
- package/dist/platforms/react-native/index.js.map +1 -0
- package/dist/platforms/web/index.d.ts +177 -0
- package/dist/platforms/web/index.d.ts.map +1 -0
- package/dist/platforms/web/index.js +637 -0
- package/dist/platforms/web/index.js.map +1 -0
- package/dist/storage/index.d.ts +35 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +351 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/tools/analysis/analyzeAppStructure.d.ts +9 -0
- package/dist/tools/analysis/analyzeAppStructure.d.ts.map +1 -0
- package/dist/tools/analysis/analyzeAppStructure.js +289 -0
- package/dist/tools/analysis/analyzeAppStructure.js.map +1 -0
- package/dist/tools/analysis/createTestPlan.d.ts +21 -0
- package/dist/tools/analysis/createTestPlan.d.ts.map +1 -0
- package/dist/tools/analysis/createTestPlan.js +214 -0
- package/dist/tools/analysis/createTestPlan.js.map +1 -0
- package/dist/tools/analysis/generateScenarios.d.ts +21 -0
- package/dist/tools/analysis/generateScenarios.d.ts.map +1 -0
- package/dist/tools/analysis/generateScenarios.js +567 -0
- package/dist/tools/analysis/generateScenarios.js.map +1 -0
- package/dist/tools/automation/cicdIntegration.d.ts +41 -0
- package/dist/tools/automation/cicdIntegration.d.ts.map +1 -0
- package/dist/tools/automation/cicdIntegration.js +825 -0
- package/dist/tools/automation/cicdIntegration.js.map +1 -0
- package/dist/tools/automation/generateReport.d.ts +16 -0
- package/dist/tools/automation/generateReport.d.ts.map +1 -0
- package/dist/tools/automation/generateReport.js +406 -0
- package/dist/tools/automation/generateReport.js.map +1 -0
- package/dist/tools/automation/runFullAutomation.d.ts +25 -0
- package/dist/tools/automation/runFullAutomation.d.ts.map +1 -0
- package/dist/tools/automation/runFullAutomation.js +296 -0
- package/dist/tools/automation/runFullAutomation.js.map +1 -0
- package/dist/tools/detection/detectLogicErrors.d.ts +18 -0
- package/dist/tools/detection/detectLogicErrors.d.ts.map +1 -0
- package/dist/tools/detection/detectLogicErrors.js +470 -0
- package/dist/tools/detection/detectLogicErrors.js.map +1 -0
- package/dist/tools/detection/detectMemoryLeaks.d.ts +23 -0
- package/dist/tools/detection/detectMemoryLeaks.d.ts.map +1 -0
- package/dist/tools/detection/detectMemoryLeaks.js +453 -0
- package/dist/tools/detection/detectMemoryLeaks.js.map +1 -0
- package/dist/tools/execution/runScenarioTest.d.ts +34 -0
- package/dist/tools/execution/runScenarioTest.d.ts.map +1 -0
- package/dist/tools/execution/runScenarioTest.js +334 -0
- package/dist/tools/execution/runScenarioTest.js.map +1 -0
- package/dist/tools/execution/runSimulation.d.ts +17 -0
- package/dist/tools/execution/runSimulation.d.ts.map +1 -0
- package/dist/tools/execution/runSimulation.js +250 -0
- package/dist/tools/execution/runSimulation.js.map +1 -0
- package/dist/tools/execution/runStressTest.d.ts +52 -0
- package/dist/tools/execution/runStressTest.d.ts.map +1 -0
- package/dist/tools/execution/runStressTest.js +176 -0
- package/dist/tools/execution/runStressTest.js.map +1 -0
- package/dist/tools/fixing/applyFix.d.ts +44 -0
- package/dist/tools/fixing/applyFix.d.ts.map +1 -0
- package/dist/tools/fixing/applyFix.js +399 -0
- package/dist/tools/fixing/applyFix.js.map +1 -0
- package/dist/tools/fixing/confirmFix.d.ts +31 -0
- package/dist/tools/fixing/confirmFix.d.ts.map +1 -0
- package/dist/tools/fixing/confirmFix.js +136 -0
- package/dist/tools/fixing/confirmFix.js.map +1 -0
- package/dist/tools/fixing/suggestFixes.d.ts +23 -0
- package/dist/tools/fixing/suggestFixes.d.ts.map +1 -0
- package/dist/tools/fixing/suggestFixes.js +349 -0
- package/dist/tools/fixing/suggestFixes.js.map +1 -0
- package/dist/types.d.ts +322 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/codeParser.d.ts +12 -0
- package/dist/utils/codeParser.d.ts.map +1 -0
- package/dist/utils/codeParser.js +487 -0
- package/dist/utils/codeParser.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================
|
|
3
|
+
// Performance Analyzer
|
|
4
|
+
// Static and runtime performance analysis
|
|
5
|
+
// ============================================
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.analyzePerformance = analyzePerformance;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const child_process_1 = require("child_process");
|
|
44
|
+
const util_1 = require("util");
|
|
45
|
+
const astAnalyzer_js_1 = require("./astAnalyzer.js");
|
|
46
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
47
|
+
// ============================================
|
|
48
|
+
// Main Analysis Function
|
|
49
|
+
// ============================================
|
|
50
|
+
async function analyzePerformance(projectPath, platform) {
|
|
51
|
+
const extensions = getExtensionsForPlatform(platform);
|
|
52
|
+
const files = getAllSourceFiles(projectPath, extensions);
|
|
53
|
+
const allIssues = [];
|
|
54
|
+
const rendering = await analyzeRendering(files, platform);
|
|
55
|
+
const computation = await analyzeComputation(files, platform);
|
|
56
|
+
const network = await analyzeNetwork(files);
|
|
57
|
+
const bundle = await analyzeBundle(projectPath, files, platform);
|
|
58
|
+
// Collect all issues
|
|
59
|
+
allIssues.push(...rendering.issues);
|
|
60
|
+
allIssues.push(...computation.issues);
|
|
61
|
+
allIssues.push(...network.issues);
|
|
62
|
+
allIssues.push(...bundle.issues);
|
|
63
|
+
// Calculate performance score
|
|
64
|
+
const criticalCount = allIssues.filter(i => i.type === 'critical').length;
|
|
65
|
+
const majorCount = allIssues.filter(i => i.type === 'major').length;
|
|
66
|
+
const minorCount = allIssues.filter(i => i.type === 'minor').length;
|
|
67
|
+
let score = 100;
|
|
68
|
+
score -= criticalCount * 15;
|
|
69
|
+
score -= majorCount * 5;
|
|
70
|
+
score -= minorCount * 1;
|
|
71
|
+
score = Math.max(0, Math.min(100, score));
|
|
72
|
+
// Generate recommendations
|
|
73
|
+
const recommendations = generateRecommendations(rendering, computation, network, bundle);
|
|
74
|
+
return {
|
|
75
|
+
projectPath,
|
|
76
|
+
platform,
|
|
77
|
+
analyzedAt: new Date().toISOString(),
|
|
78
|
+
summary: {
|
|
79
|
+
criticalIssues: criticalCount,
|
|
80
|
+
majorIssues: majorCount,
|
|
81
|
+
minorIssues: minorCount,
|
|
82
|
+
performanceScore: score,
|
|
83
|
+
},
|
|
84
|
+
rendering,
|
|
85
|
+
computation,
|
|
86
|
+
network,
|
|
87
|
+
bundle,
|
|
88
|
+
recommendations,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// ============================================
|
|
92
|
+
// Rendering Analysis
|
|
93
|
+
// ============================================
|
|
94
|
+
async function analyzeRendering(files, platform) {
|
|
95
|
+
const issues = [];
|
|
96
|
+
const expensiveComponents = [];
|
|
97
|
+
const memoizationOpportunities = [];
|
|
98
|
+
for (const file of files) {
|
|
99
|
+
try {
|
|
100
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
101
|
+
// Check for inline object creation in JSX
|
|
102
|
+
if (content.includes('style={{')) {
|
|
103
|
+
const matches = content.matchAll(/style=\{\{[^}]+\}\}/g);
|
|
104
|
+
for (const match of matches) {
|
|
105
|
+
issues.push({
|
|
106
|
+
type: 'major',
|
|
107
|
+
category: 'rendering',
|
|
108
|
+
title: 'Inline style object in render',
|
|
109
|
+
description: 'Creating style objects inline causes new object creation on every render',
|
|
110
|
+
file,
|
|
111
|
+
line: getLineNumber(content, match.index || 0),
|
|
112
|
+
impact: 'Unnecessary re-renders and garbage collection',
|
|
113
|
+
suggestion: 'Move style object outside component or use useMemo',
|
|
114
|
+
estimatedGain: '10-30% render performance improvement',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Check for inline arrow functions in JSX
|
|
119
|
+
const inlineArrowRegex = /on\w+=\{\s*\(\s*\)\s*=>/g;
|
|
120
|
+
if (inlineArrowRegex.test(content)) {
|
|
121
|
+
issues.push({
|
|
122
|
+
type: 'major',
|
|
123
|
+
category: 'rendering',
|
|
124
|
+
title: 'Inline arrow function in render',
|
|
125
|
+
description: 'Inline arrow functions create new function instances on every render',
|
|
126
|
+
file,
|
|
127
|
+
impact: 'May cause child component re-renders',
|
|
128
|
+
suggestion: 'Use useCallback to memoize event handlers',
|
|
129
|
+
estimatedGain: 'Prevents unnecessary child re-renders',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
// Check for missing React.memo on large components
|
|
133
|
+
if (platform === 'react-native' || platform === 'web') {
|
|
134
|
+
const componentMatch = content.match(/(?:export\s+)?(?:const|function)\s+(\w+)[^{]*(?:=>|{)[\s\S]{500,}/);
|
|
135
|
+
if (componentMatch && !content.includes('React.memo') && !content.includes('memo(')) {
|
|
136
|
+
const name = componentMatch[1] || 'Component';
|
|
137
|
+
expensiveComponents.push({
|
|
138
|
+
name,
|
|
139
|
+
file,
|
|
140
|
+
reason: 'Large component without memoization',
|
|
141
|
+
});
|
|
142
|
+
memoizationOpportunities.push({
|
|
143
|
+
component: name,
|
|
144
|
+
file,
|
|
145
|
+
suggestion: `Wrap ${name} with React.memo() to prevent unnecessary re-renders`,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Check for expensive operations in render
|
|
150
|
+
const expensivePatterns = [
|
|
151
|
+
{ pattern: /\.filter\s*\(/, name: 'Array.filter' },
|
|
152
|
+
{ pattern: /\.map\s*\(/, name: 'Array.map' },
|
|
153
|
+
{ pattern: /\.reduce\s*\(/, name: 'Array.reduce' },
|
|
154
|
+
{ pattern: /JSON\.stringify/, name: 'JSON.stringify' },
|
|
155
|
+
{ pattern: /JSON\.parse/, name: 'JSON.parse' },
|
|
156
|
+
{ pattern: /new Date\s*\(/, name: 'Date creation' },
|
|
157
|
+
];
|
|
158
|
+
for (const { pattern, name } of expensivePatterns) {
|
|
159
|
+
// Check if inside render/return
|
|
160
|
+
const returnIndex = content.indexOf('return');
|
|
161
|
+
if (returnIndex > 0) {
|
|
162
|
+
const afterReturn = content.substring(returnIndex);
|
|
163
|
+
if (pattern.test(afterReturn)) {
|
|
164
|
+
issues.push({
|
|
165
|
+
type: 'minor',
|
|
166
|
+
category: 'rendering',
|
|
167
|
+
title: `${name} in render path`,
|
|
168
|
+
description: `${name} called during render may impact performance`,
|
|
169
|
+
file,
|
|
170
|
+
impact: 'Computation on every render',
|
|
171
|
+
suggestion: `Move ${name} to useMemo or calculate before render`,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Check for missing key prop in lists
|
|
177
|
+
if (content.includes('.map') && !content.includes('key=')) {
|
|
178
|
+
issues.push({
|
|
179
|
+
type: 'major',
|
|
180
|
+
category: 'rendering',
|
|
181
|
+
title: 'Missing key prop in list rendering',
|
|
182
|
+
description: 'List items without key prop cause inefficient reconciliation',
|
|
183
|
+
file,
|
|
184
|
+
impact: 'React cannot efficiently update list items',
|
|
185
|
+
suggestion: 'Add unique key prop to each list item',
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
// Flutter-specific checks
|
|
189
|
+
if (platform === 'flutter') {
|
|
190
|
+
// Check for rebuild on every setState
|
|
191
|
+
if (content.includes('setState') && !content.includes('const ')) {
|
|
192
|
+
issues.push({
|
|
193
|
+
type: 'minor',
|
|
194
|
+
category: 'rendering',
|
|
195
|
+
title: 'setState may cause full widget rebuild',
|
|
196
|
+
description: 'Consider using const constructors to prevent rebuilds',
|
|
197
|
+
file,
|
|
198
|
+
impact: 'Unnecessary widget rebuilds',
|
|
199
|
+
suggestion: 'Use const constructors where possible',
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
// Check for missing const in widget constructors
|
|
203
|
+
const widgetRegex = /return\s+(?!const\s+)\w+\s*\(/g;
|
|
204
|
+
if (widgetRegex.test(content)) {
|
|
205
|
+
issues.push({
|
|
206
|
+
type: 'minor',
|
|
207
|
+
category: 'rendering',
|
|
208
|
+
title: 'Widget without const constructor',
|
|
209
|
+
description: 'Non-const widgets are rebuilt on every parent rebuild',
|
|
210
|
+
file,
|
|
211
|
+
impact: 'Unnecessary widget rebuilds',
|
|
212
|
+
suggestion: 'Use const constructors for stateless widgets',
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// Skip files that can't be read
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return { issues, expensiveComponents, memoizationOpportunities };
|
|
222
|
+
}
|
|
223
|
+
// ============================================
|
|
224
|
+
// Computation Analysis
|
|
225
|
+
// ============================================
|
|
226
|
+
async function analyzeComputation(files, platform) {
|
|
227
|
+
const issues = [];
|
|
228
|
+
const heavyFunctions = [];
|
|
229
|
+
const optimizationOpportunities = [];
|
|
230
|
+
for (const file of files) {
|
|
231
|
+
try {
|
|
232
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
233
|
+
const analysis = (0, astAnalyzer_js_1.analyzeTypeScript)(file, content);
|
|
234
|
+
// Check for high complexity functions
|
|
235
|
+
for (const func of analysis.functions) {
|
|
236
|
+
if (func.complexity > 10) {
|
|
237
|
+
heavyFunctions.push({
|
|
238
|
+
name: func.name,
|
|
239
|
+
file,
|
|
240
|
+
complexity: func.complexity,
|
|
241
|
+
suggestion: 'Consider breaking into smaller functions',
|
|
242
|
+
});
|
|
243
|
+
if (func.complexity > 20) {
|
|
244
|
+
issues.push({
|
|
245
|
+
type: 'major',
|
|
246
|
+
category: 'computation',
|
|
247
|
+
title: `High complexity function: ${func.name}`,
|
|
248
|
+
description: `Cyclomatic complexity of ${func.complexity} exceeds recommended threshold`,
|
|
249
|
+
file,
|
|
250
|
+
line: func.line,
|
|
251
|
+
impact: 'Hard to maintain and may indicate performance issues',
|
|
252
|
+
suggestion: 'Refactor into smaller, more focused functions',
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// Check for nested loops
|
|
258
|
+
const nestedLoopRegex = /for\s*\([^)]+\)[^{]*\{[^}]*for\s*\([^)]+\)/gs;
|
|
259
|
+
if (nestedLoopRegex.test(content)) {
|
|
260
|
+
issues.push({
|
|
261
|
+
type: 'major',
|
|
262
|
+
category: 'computation',
|
|
263
|
+
title: 'Nested loops detected',
|
|
264
|
+
description: 'Nested loops can cause O(n²) or worse complexity',
|
|
265
|
+
file,
|
|
266
|
+
impact: 'Performance degrades rapidly with data size',
|
|
267
|
+
suggestion: 'Consider using Map/Set for lookups or restructuring algorithm',
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
// Check for synchronous blocking operations
|
|
271
|
+
const blockingPatterns = [
|
|
272
|
+
{ pattern: /fs\.readFileSync/, name: 'Synchronous file read' },
|
|
273
|
+
{ pattern: /fs\.writeFileSync/, name: 'Synchronous file write' },
|
|
274
|
+
{ pattern: /JSON\.parse\s*\([^)]*readFileSync/, name: 'Sync file read + JSON parse' },
|
|
275
|
+
];
|
|
276
|
+
for (const { pattern, name } of blockingPatterns) {
|
|
277
|
+
if (pattern.test(content)) {
|
|
278
|
+
issues.push({
|
|
279
|
+
type: 'major',
|
|
280
|
+
category: 'computation',
|
|
281
|
+
title: name,
|
|
282
|
+
description: 'Synchronous operation blocks the event loop',
|
|
283
|
+
file,
|
|
284
|
+
impact: 'UI freezing and poor responsiveness',
|
|
285
|
+
suggestion: 'Use async alternatives (readFile, writeFile)',
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// Check for expensive regex in loops
|
|
290
|
+
const regexInLoopRegex = /(?:for|while)\s*\([^)]+\)[^{]*\{[^}]*(?:\.match|\.replace|\.test)\s*\(/gs;
|
|
291
|
+
if (regexInLoopRegex.test(content)) {
|
|
292
|
+
optimizationOpportunities.push({
|
|
293
|
+
location: file,
|
|
294
|
+
type: 'regex',
|
|
295
|
+
suggestion: 'Move regex pattern outside loop to avoid recompilation',
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
// Check for unnecessary array spread
|
|
299
|
+
if (content.includes('[...') && content.includes('.push(')) {
|
|
300
|
+
optimizationOpportunities.push({
|
|
301
|
+
location: file,
|
|
302
|
+
type: 'array',
|
|
303
|
+
suggestion: 'Consider using push() instead of spread for building arrays',
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// Skip files that can't be read
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return { issues, heavyFunctions, optimizationOpportunities };
|
|
312
|
+
}
|
|
313
|
+
// ============================================
|
|
314
|
+
// Network Analysis
|
|
315
|
+
// ============================================
|
|
316
|
+
async function analyzeNetwork(files) {
|
|
317
|
+
const issues = [];
|
|
318
|
+
const apiCalls = [];
|
|
319
|
+
const suggestions = [];
|
|
320
|
+
for (const file of files) {
|
|
321
|
+
try {
|
|
322
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
323
|
+
// Find fetch/axios calls
|
|
324
|
+
const fetchRegex = /(?:fetch|axios\.(?:get|post|put|delete|patch))\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
325
|
+
let match;
|
|
326
|
+
while ((match = fetchRegex.exec(content)) !== null) {
|
|
327
|
+
const endpoint = match[1] || '';
|
|
328
|
+
const line = getLineNumber(content, match.index);
|
|
329
|
+
// Check for error handling
|
|
330
|
+
const surroundingCode = content.substring(Math.max(0, match.index - 200), match.index + 500);
|
|
331
|
+
const hasErrorHandling = surroundingCode.includes('.catch') || surroundingCode.includes('try');
|
|
332
|
+
const hasRetry = surroundingCode.includes('retry') || surroundingCode.includes('Retry');
|
|
333
|
+
const hasCaching = surroundingCode.includes('cache') || surroundingCode.includes('Cache');
|
|
334
|
+
apiCalls.push({
|
|
335
|
+
endpoint,
|
|
336
|
+
file,
|
|
337
|
+
line,
|
|
338
|
+
hasErrorHandling,
|
|
339
|
+
hasRetry,
|
|
340
|
+
hasCaching,
|
|
341
|
+
});
|
|
342
|
+
if (!hasErrorHandling) {
|
|
343
|
+
issues.push({
|
|
344
|
+
type: 'major',
|
|
345
|
+
category: 'network',
|
|
346
|
+
title: 'API call without error handling',
|
|
347
|
+
description: `fetch/axios call to ${endpoint} has no error handling`,
|
|
348
|
+
file,
|
|
349
|
+
line,
|
|
350
|
+
impact: 'Unhandled network errors may crash the app',
|
|
351
|
+
suggestion: 'Add .catch() or try/catch for error handling',
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// Check for API calls in useEffect without abort controller
|
|
356
|
+
if (content.includes('useEffect') && (content.includes('fetch') || content.includes('axios'))) {
|
|
357
|
+
if (!content.includes('AbortController') && !content.includes('abortController')) {
|
|
358
|
+
issues.push({
|
|
359
|
+
type: 'minor',
|
|
360
|
+
category: 'network',
|
|
361
|
+
title: 'API call in useEffect without AbortController',
|
|
362
|
+
description: 'API calls may complete after component unmounts',
|
|
363
|
+
file,
|
|
364
|
+
impact: 'Memory leaks and state updates on unmounted components',
|
|
365
|
+
suggestion: 'Use AbortController to cancel requests on unmount',
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// Check for waterfall requests
|
|
370
|
+
const awaitFetchCount = (content.match(/await\s+(?:fetch|axios)/g) || []).length;
|
|
371
|
+
if (awaitFetchCount > 2) {
|
|
372
|
+
const hasPromiseAll = content.includes('Promise.all');
|
|
373
|
+
if (!hasPromiseAll) {
|
|
374
|
+
issues.push({
|
|
375
|
+
type: 'major',
|
|
376
|
+
category: 'network',
|
|
377
|
+
title: 'Sequential API calls detected',
|
|
378
|
+
description: `${awaitFetchCount} sequential await calls may cause waterfall requests`,
|
|
379
|
+
file,
|
|
380
|
+
impact: 'Increased loading time due to sequential requests',
|
|
381
|
+
suggestion: 'Use Promise.all() for independent requests',
|
|
382
|
+
estimatedGain: 'Reduce loading time by parallelizing requests',
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
// Skip files that can't be read
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// Generate suggestions based on findings
|
|
392
|
+
if (apiCalls.some(a => !a.hasCaching)) {
|
|
393
|
+
suggestions.push('Consider implementing response caching for frequently called endpoints');
|
|
394
|
+
}
|
|
395
|
+
if (apiCalls.some(a => !a.hasRetry)) {
|
|
396
|
+
suggestions.push('Add retry logic for critical API endpoints');
|
|
397
|
+
}
|
|
398
|
+
if (apiCalls.length > 10) {
|
|
399
|
+
suggestions.push('Consider implementing request batching to reduce network overhead');
|
|
400
|
+
}
|
|
401
|
+
return { issues, apiCalls, suggestions };
|
|
402
|
+
}
|
|
403
|
+
// ============================================
|
|
404
|
+
// Bundle Analysis
|
|
405
|
+
// ============================================
|
|
406
|
+
async function analyzeBundle(projectPath, files, platform) {
|
|
407
|
+
const issues = [];
|
|
408
|
+
const largeImports = [];
|
|
409
|
+
const unusedExports = [];
|
|
410
|
+
const treeShakingOpportunities = [];
|
|
411
|
+
// Known large libraries
|
|
412
|
+
const largeLibraries = {
|
|
413
|
+
'moment': { size: '~300KB', alternative: 'date-fns or dayjs' },
|
|
414
|
+
'lodash': { size: '~70KB', alternative: 'lodash-es with tree-shaking' },
|
|
415
|
+
'rxjs': { size: '~50KB', alternative: 'Import only needed operators' },
|
|
416
|
+
'chart.js': { size: '~200KB', alternative: 'react-chartjs-2 with lazy loading' },
|
|
417
|
+
'd3': { size: '~250KB', alternative: 'Import only needed d3 modules' },
|
|
418
|
+
'firebase': { size: '~300KB', alternative: 'Use modular Firebase SDK' },
|
|
419
|
+
'@material-ui': { size: '~300KB', alternative: 'Import components individually' },
|
|
420
|
+
'antd': { size: '~500KB', alternative: 'Use babel-plugin-import' },
|
|
421
|
+
};
|
|
422
|
+
const allImports = new Map();
|
|
423
|
+
const allExports = new Map();
|
|
424
|
+
for (const file of files) {
|
|
425
|
+
try {
|
|
426
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
427
|
+
// Check for large library imports
|
|
428
|
+
const importRegex = /import\s+(?:\*\s+as\s+\w+|\w+|\{[^}]+\})\s+from\s+['"]([^'"]+)['"]/g;
|
|
429
|
+
let match;
|
|
430
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
431
|
+
const module = match[1] || '';
|
|
432
|
+
const baseName = module.split('/')[0] || module;
|
|
433
|
+
// Track imports
|
|
434
|
+
if (!allImports.has(baseName)) {
|
|
435
|
+
allImports.set(baseName, []);
|
|
436
|
+
}
|
|
437
|
+
allImports.get(baseName).push(file);
|
|
438
|
+
// Check for known large libraries
|
|
439
|
+
if (largeLibraries[baseName]) {
|
|
440
|
+
const lib = largeLibraries[baseName];
|
|
441
|
+
if (!largeImports.some(i => i.module === baseName)) {
|
|
442
|
+
largeImports.push({
|
|
443
|
+
module: baseName,
|
|
444
|
+
file,
|
|
445
|
+
estimatedSize: lib?.size,
|
|
446
|
+
alternative: lib?.alternative,
|
|
447
|
+
});
|
|
448
|
+
issues.push({
|
|
449
|
+
type: 'minor',
|
|
450
|
+
category: 'bundle',
|
|
451
|
+
title: `Large library import: ${baseName}`,
|
|
452
|
+
description: `${baseName} adds approximately ${lib?.size} to bundle`,
|
|
453
|
+
file,
|
|
454
|
+
impact: 'Increased bundle size and load time',
|
|
455
|
+
suggestion: lib?.alternative || 'Consider alternatives or lazy loading',
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// Check for full library imports that should be tree-shaken
|
|
460
|
+
if (content.includes(`import ${baseName} from`) || content.includes(`import * as`)) {
|
|
461
|
+
if (['lodash', 'rxjs', 'd3', 'ramda'].includes(baseName)) {
|
|
462
|
+
treeShakingOpportunities.push(`${baseName} in ${file}: Import only used functions instead of entire library`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
// Track exports
|
|
467
|
+
const exportRegex = /export\s+(?:const|let|var|function|class)\s+(\w+)/g;
|
|
468
|
+
while ((match = exportRegex.exec(content)) !== null) {
|
|
469
|
+
const name = match[1] || '';
|
|
470
|
+
if (!allExports.has(file)) {
|
|
471
|
+
allExports.set(file, []);
|
|
472
|
+
}
|
|
473
|
+
allExports.get(file).push(name);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
catch {
|
|
477
|
+
// Skip files that can't be read
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
// Check for unused exports (basic check)
|
|
481
|
+
for (const [exportFile, exports] of allExports.entries()) {
|
|
482
|
+
for (const exp of exports) {
|
|
483
|
+
let isUsed = false;
|
|
484
|
+
for (const file of files) {
|
|
485
|
+
if (file === exportFile)
|
|
486
|
+
continue;
|
|
487
|
+
try {
|
|
488
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
489
|
+
if (content.includes(exp)) {
|
|
490
|
+
isUsed = true;
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
catch {
|
|
495
|
+
// Skip
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (!isUsed && exp !== 'default') {
|
|
499
|
+
unusedExports.push(`${exp} in ${exportFile}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
// Check package.json for bundle-affecting issues
|
|
504
|
+
try {
|
|
505
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
506
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
507
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
508
|
+
// Check for dev dependencies that might be bundled
|
|
509
|
+
const devDeps = Object.keys(packageJson.devDependencies || {});
|
|
510
|
+
const prodDeps = Object.keys(packageJson.dependencies || {});
|
|
511
|
+
for (const [module, importFiles] of allImports.entries()) {
|
|
512
|
+
if (devDeps.includes(module) && !prodDeps.includes(module)) {
|
|
513
|
+
issues.push({
|
|
514
|
+
type: 'minor',
|
|
515
|
+
category: 'bundle',
|
|
516
|
+
title: `Dev dependency imported in production code`,
|
|
517
|
+
description: `${module} is a devDependency but imported in ${importFiles.join(', ')}`,
|
|
518
|
+
file: importFiles[0] || '',
|
|
519
|
+
impact: 'May cause build issues or unnecessary bundle size',
|
|
520
|
+
suggestion: 'Move to dependencies or remove import',
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
catch {
|
|
527
|
+
// Skip package.json analysis
|
|
528
|
+
}
|
|
529
|
+
return { issues, largeImports, unusedExports, treeShakingOpportunities };
|
|
530
|
+
}
|
|
531
|
+
// ============================================
|
|
532
|
+
// Recommendations Generator
|
|
533
|
+
// ============================================
|
|
534
|
+
function generateRecommendations(rendering, computation, network, bundle) {
|
|
535
|
+
const recommendations = [];
|
|
536
|
+
// Rendering recommendations
|
|
537
|
+
if (rendering.memoizationOpportunities.length > 0) {
|
|
538
|
+
recommendations.push({
|
|
539
|
+
priority: 'high',
|
|
540
|
+
category: 'Rendering',
|
|
541
|
+
description: `Add React.memo to ${rendering.memoizationOpportunities.length} components to prevent unnecessary re-renders`,
|
|
542
|
+
estimatedImpact: '10-50% render performance improvement',
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
if (rendering.issues.filter(i => i.title.includes('inline')).length > 3) {
|
|
546
|
+
recommendations.push({
|
|
547
|
+
priority: 'medium',
|
|
548
|
+
category: 'Rendering',
|
|
549
|
+
description: 'Refactor inline functions and objects to prevent re-creation on each render',
|
|
550
|
+
estimatedImpact: 'Reduced garbage collection and smoother UI',
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
// Computation recommendations
|
|
554
|
+
if (computation.heavyFunctions.length > 0) {
|
|
555
|
+
recommendations.push({
|
|
556
|
+
priority: 'medium',
|
|
557
|
+
category: 'Code Quality',
|
|
558
|
+
description: `Refactor ${computation.heavyFunctions.length} high-complexity functions for better maintainability`,
|
|
559
|
+
estimatedImpact: 'Improved code quality and potential performance gains',
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
// Network recommendations
|
|
563
|
+
if (network.issues.filter(i => i.title.includes('Sequential')).length > 0) {
|
|
564
|
+
recommendations.push({
|
|
565
|
+
priority: 'high',
|
|
566
|
+
category: 'Network',
|
|
567
|
+
description: 'Parallelize sequential API calls using Promise.all',
|
|
568
|
+
estimatedImpact: 'Significant reduction in loading time',
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
if (network.apiCalls.filter(a => !a.hasErrorHandling).length > 0) {
|
|
572
|
+
recommendations.push({
|
|
573
|
+
priority: 'high',
|
|
574
|
+
category: 'Network',
|
|
575
|
+
description: 'Add error handling to all API calls',
|
|
576
|
+
estimatedImpact: 'Improved app stability and user experience',
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
// Bundle recommendations
|
|
580
|
+
if (bundle.largeImports.length > 0) {
|
|
581
|
+
recommendations.push({
|
|
582
|
+
priority: 'medium',
|
|
583
|
+
category: 'Bundle Size',
|
|
584
|
+
description: `Optimize ${bundle.largeImports.length} large library imports using tree-shaking or alternatives`,
|
|
585
|
+
estimatedImpact: 'Reduced bundle size and faster load times',
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
if (bundle.treeShakingOpportunities.length > 0) {
|
|
589
|
+
recommendations.push({
|
|
590
|
+
priority: 'low',
|
|
591
|
+
category: 'Bundle Size',
|
|
592
|
+
description: 'Enable tree-shaking for lodash, rxjs, and other libraries',
|
|
593
|
+
estimatedImpact: '10-30% bundle size reduction',
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
return recommendations;
|
|
597
|
+
}
|
|
598
|
+
// ============================================
|
|
599
|
+
// Helper Functions
|
|
600
|
+
// ============================================
|
|
601
|
+
function getExtensionsForPlatform(platform) {
|
|
602
|
+
switch (platform) {
|
|
603
|
+
case 'ios':
|
|
604
|
+
return ['.swift', '.m', '.mm'];
|
|
605
|
+
case 'android':
|
|
606
|
+
return ['.kt', '.java'];
|
|
607
|
+
case 'flutter':
|
|
608
|
+
return ['.dart'];
|
|
609
|
+
case 'react-native':
|
|
610
|
+
case 'web':
|
|
611
|
+
default:
|
|
612
|
+
return ['.ts', '.tsx', '.js', '.jsx'];
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
function getAllSourceFiles(dir, extensions) {
|
|
616
|
+
const results = [];
|
|
617
|
+
const skipDirs = ['node_modules', '.git', 'build', 'dist', 'Pods', '.gradle', 'DerivedData', '__tests__', 'test'];
|
|
618
|
+
function walk(currentDir) {
|
|
619
|
+
try {
|
|
620
|
+
const items = fs.readdirSync(currentDir);
|
|
621
|
+
for (const item of items) {
|
|
622
|
+
if (skipDirs.includes(item))
|
|
623
|
+
continue;
|
|
624
|
+
const fullPath = path.join(currentDir, item);
|
|
625
|
+
const stat = fs.statSync(fullPath);
|
|
626
|
+
if (stat.isDirectory()) {
|
|
627
|
+
walk(fullPath);
|
|
628
|
+
}
|
|
629
|
+
else if (extensions.some(ext => item.endsWith(ext))) {
|
|
630
|
+
results.push(fullPath);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
catch {
|
|
635
|
+
// Ignore errors
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
walk(dir);
|
|
639
|
+
return results;
|
|
640
|
+
}
|
|
641
|
+
function getLineNumber(content, index) {
|
|
642
|
+
return content.substring(0, index).split('\n').length;
|
|
643
|
+
}
|
|
644
|
+
exports.default = {
|
|
645
|
+
analyzePerformance,
|
|
646
|
+
};
|
|
647
|
+
//# sourceMappingURL=performanceAnalyzer.js.map
|