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.
Files changed (102) hide show
  1. package/README.md +219 -0
  2. package/dist/analyzers/astAnalyzer.d.ts +108 -0
  3. package/dist/analyzers/astAnalyzer.d.ts.map +1 -0
  4. package/dist/analyzers/astAnalyzer.js +800 -0
  5. package/dist/analyzers/astAnalyzer.js.map +1 -0
  6. package/dist/analyzers/performanceAnalyzer.d.ts +90 -0
  7. package/dist/analyzers/performanceAnalyzer.d.ts.map +1 -0
  8. package/dist/analyzers/performanceAnalyzer.js +647 -0
  9. package/dist/analyzers/performanceAnalyzer.js.map +1 -0
  10. package/dist/index.d.ts +3 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +683 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/platforms/android/index.d.ts +90 -0
  15. package/dist/platforms/android/index.d.ts.map +1 -0
  16. package/dist/platforms/android/index.js +519 -0
  17. package/dist/platforms/android/index.js.map +1 -0
  18. package/dist/platforms/flutter/index.d.ts +105 -0
  19. package/dist/platforms/flutter/index.d.ts.map +1 -0
  20. package/dist/platforms/flutter/index.js +533 -0
  21. package/dist/platforms/flutter/index.js.map +1 -0
  22. package/dist/platforms/ios/index.d.ts +79 -0
  23. package/dist/platforms/ios/index.d.ts.map +1 -0
  24. package/dist/platforms/ios/index.js +393 -0
  25. package/dist/platforms/ios/index.js.map +1 -0
  26. package/dist/platforms/react-native/index.d.ts +160 -0
  27. package/dist/platforms/react-native/index.d.ts.map +1 -0
  28. package/dist/platforms/react-native/index.js +602 -0
  29. package/dist/platforms/react-native/index.js.map +1 -0
  30. package/dist/platforms/web/index.d.ts +177 -0
  31. package/dist/platforms/web/index.d.ts.map +1 -0
  32. package/dist/platforms/web/index.js +637 -0
  33. package/dist/platforms/web/index.js.map +1 -0
  34. package/dist/storage/index.d.ts +35 -0
  35. package/dist/storage/index.d.ts.map +1 -0
  36. package/dist/storage/index.js +351 -0
  37. package/dist/storage/index.js.map +1 -0
  38. package/dist/tools/analysis/analyzeAppStructure.d.ts +9 -0
  39. package/dist/tools/analysis/analyzeAppStructure.d.ts.map +1 -0
  40. package/dist/tools/analysis/analyzeAppStructure.js +289 -0
  41. package/dist/tools/analysis/analyzeAppStructure.js.map +1 -0
  42. package/dist/tools/analysis/createTestPlan.d.ts +21 -0
  43. package/dist/tools/analysis/createTestPlan.d.ts.map +1 -0
  44. package/dist/tools/analysis/createTestPlan.js +214 -0
  45. package/dist/tools/analysis/createTestPlan.js.map +1 -0
  46. package/dist/tools/analysis/generateScenarios.d.ts +21 -0
  47. package/dist/tools/analysis/generateScenarios.d.ts.map +1 -0
  48. package/dist/tools/analysis/generateScenarios.js +567 -0
  49. package/dist/tools/analysis/generateScenarios.js.map +1 -0
  50. package/dist/tools/automation/cicdIntegration.d.ts +41 -0
  51. package/dist/tools/automation/cicdIntegration.d.ts.map +1 -0
  52. package/dist/tools/automation/cicdIntegration.js +825 -0
  53. package/dist/tools/automation/cicdIntegration.js.map +1 -0
  54. package/dist/tools/automation/generateReport.d.ts +16 -0
  55. package/dist/tools/automation/generateReport.d.ts.map +1 -0
  56. package/dist/tools/automation/generateReport.js +406 -0
  57. package/dist/tools/automation/generateReport.js.map +1 -0
  58. package/dist/tools/automation/runFullAutomation.d.ts +25 -0
  59. package/dist/tools/automation/runFullAutomation.d.ts.map +1 -0
  60. package/dist/tools/automation/runFullAutomation.js +296 -0
  61. package/dist/tools/automation/runFullAutomation.js.map +1 -0
  62. package/dist/tools/detection/detectLogicErrors.d.ts +18 -0
  63. package/dist/tools/detection/detectLogicErrors.d.ts.map +1 -0
  64. package/dist/tools/detection/detectLogicErrors.js +470 -0
  65. package/dist/tools/detection/detectLogicErrors.js.map +1 -0
  66. package/dist/tools/detection/detectMemoryLeaks.d.ts +23 -0
  67. package/dist/tools/detection/detectMemoryLeaks.d.ts.map +1 -0
  68. package/dist/tools/detection/detectMemoryLeaks.js +453 -0
  69. package/dist/tools/detection/detectMemoryLeaks.js.map +1 -0
  70. package/dist/tools/execution/runScenarioTest.d.ts +34 -0
  71. package/dist/tools/execution/runScenarioTest.d.ts.map +1 -0
  72. package/dist/tools/execution/runScenarioTest.js +334 -0
  73. package/dist/tools/execution/runScenarioTest.js.map +1 -0
  74. package/dist/tools/execution/runSimulation.d.ts +17 -0
  75. package/dist/tools/execution/runSimulation.d.ts.map +1 -0
  76. package/dist/tools/execution/runSimulation.js +250 -0
  77. package/dist/tools/execution/runSimulation.js.map +1 -0
  78. package/dist/tools/execution/runStressTest.d.ts +52 -0
  79. package/dist/tools/execution/runStressTest.d.ts.map +1 -0
  80. package/dist/tools/execution/runStressTest.js +176 -0
  81. package/dist/tools/execution/runStressTest.js.map +1 -0
  82. package/dist/tools/fixing/applyFix.d.ts +44 -0
  83. package/dist/tools/fixing/applyFix.d.ts.map +1 -0
  84. package/dist/tools/fixing/applyFix.js +399 -0
  85. package/dist/tools/fixing/applyFix.js.map +1 -0
  86. package/dist/tools/fixing/confirmFix.d.ts +31 -0
  87. package/dist/tools/fixing/confirmFix.d.ts.map +1 -0
  88. package/dist/tools/fixing/confirmFix.js +136 -0
  89. package/dist/tools/fixing/confirmFix.js.map +1 -0
  90. package/dist/tools/fixing/suggestFixes.d.ts +23 -0
  91. package/dist/tools/fixing/suggestFixes.d.ts.map +1 -0
  92. package/dist/tools/fixing/suggestFixes.js +349 -0
  93. package/dist/tools/fixing/suggestFixes.js.map +1 -0
  94. package/dist/types.d.ts +322 -0
  95. package/dist/types.d.ts.map +1 -0
  96. package/dist/types.js +6 -0
  97. package/dist/types.js.map +1 -0
  98. package/dist/utils/codeParser.d.ts +12 -0
  99. package/dist/utils/codeParser.d.ts.map +1 -0
  100. package/dist/utils/codeParser.js +487 -0
  101. package/dist/utils/codeParser.js.map +1 -0
  102. package/package.json +68 -0
@@ -0,0 +1,800 @@
1
+ "use strict";
2
+ // ============================================
3
+ // Enhanced AST Analyzer
4
+ // Deep code analysis using ts-morph
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.analyzeTypeScript = analyzeTypeScript;
41
+ exports.analyzeProject = analyzeProject;
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ // ============================================
45
+ // AST Parser (Using regex patterns for compatibility)
46
+ // In production, would use ts-morph or @babel/parser
47
+ // ============================================
48
+ function analyzeTypeScript(filePath, content) {
49
+ const functions = extractFunctions(content, filePath);
50
+ const classes = extractClasses(content, filePath);
51
+ const imports = extractImports(content);
52
+ const exports = extractExports(content);
53
+ const hooks = extractReactHooks(content, filePath);
54
+ const components = extractReactComponents(content, filePath);
55
+ const apis = extractApiCalls(content);
56
+ const issues = analyzeCodeIssues(content, filePath);
57
+ const metrics = calculateMetrics(content, functions, classes);
58
+ const complexities = functions.map(f => f.complexity);
59
+ const complexity = {
60
+ average: complexities.length > 0 ? complexities.reduce((a, b) => a + b, 0) / complexities.length : 0,
61
+ max: complexities.length > 0 ? Math.max(...complexities) : 0,
62
+ total: complexities.reduce((a, b) => a + b, 0),
63
+ };
64
+ return {
65
+ functions,
66
+ classes,
67
+ imports,
68
+ exports,
69
+ hooks,
70
+ components,
71
+ apis,
72
+ complexity,
73
+ issues,
74
+ metrics,
75
+ };
76
+ }
77
+ // ============================================
78
+ // Function Extraction
79
+ // ============================================
80
+ function extractFunctions(content, filePath) {
81
+ const functions = [];
82
+ const lines = content.split('\n');
83
+ // Arrow functions
84
+ const arrowFuncRegex = /(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(async\s*)?\(([^)]*)\)(?:\s*:\s*([^=>]+))?\s*=>/g;
85
+ let match;
86
+ while ((match = arrowFuncRegex.exec(content)) !== null) {
87
+ const name = match[1] || 'anonymous';
88
+ const isAsync = !!match[2];
89
+ const params = parseParameters(match[3] || '');
90
+ const returnType = match[4]?.trim() || 'void';
91
+ const line = getLineNumber(content, match.index);
92
+ const body = extractFunctionBody(content, match.index);
93
+ functions.push({
94
+ name,
95
+ path: filePath,
96
+ line,
97
+ parameters: params,
98
+ returnType,
99
+ async: isAsync,
100
+ complexity: calculateCyclomaticComplexity(body),
101
+ linesOfCode: body.split('\n').length,
102
+ dependencies: extractDependencies(body),
103
+ calls: extractFunctionCalls(body),
104
+ isExported: content.substring(Math.max(0, match.index - 10), match.index).includes('export'),
105
+ });
106
+ }
107
+ // Regular functions
108
+ const funcRegex = /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{]+))?\s*\{/g;
109
+ while ((match = funcRegex.exec(content)) !== null) {
110
+ const name = match[1] || 'anonymous';
111
+ const isAsync = content.substring(Math.max(0, match.index - 10), match.index).includes('async');
112
+ const params = parseParameters(match[2] || '');
113
+ const returnType = match[3]?.trim() || 'void';
114
+ const line = getLineNumber(content, match.index);
115
+ const body = extractFunctionBody(content, match.index);
116
+ functions.push({
117
+ name,
118
+ path: filePath,
119
+ line,
120
+ parameters: params,
121
+ returnType,
122
+ async: isAsync,
123
+ complexity: calculateCyclomaticComplexity(body),
124
+ linesOfCode: body.split('\n').length,
125
+ dependencies: extractDependencies(body),
126
+ calls: extractFunctionCalls(body),
127
+ isExported: content.substring(Math.max(0, match.index - 10), match.index).includes('export'),
128
+ });
129
+ }
130
+ return functions;
131
+ }
132
+ function parseParameters(paramString) {
133
+ if (!paramString.trim())
134
+ return [];
135
+ const params = [];
136
+ const paramParts = paramString.split(',');
137
+ for (const part of paramParts) {
138
+ const match = part.trim().match(/(\w+)(\?)?(?::\s*(.+))?/);
139
+ if (match) {
140
+ params.push({
141
+ name: match[1] || 'param',
142
+ type: match[3]?.trim() || 'any',
143
+ optional: !!match[2],
144
+ });
145
+ }
146
+ }
147
+ return params;
148
+ }
149
+ function extractFunctionBody(content, startIndex) {
150
+ let braceCount = 0;
151
+ let started = false;
152
+ let bodyStart = startIndex;
153
+ let bodyEnd = startIndex;
154
+ for (let i = startIndex; i < content.length; i++) {
155
+ const char = content[i];
156
+ if (char === '{' || char === '(') {
157
+ if (!started) {
158
+ bodyStart = i;
159
+ started = true;
160
+ }
161
+ braceCount++;
162
+ }
163
+ else if (char === '}' || char === ')') {
164
+ braceCount--;
165
+ if (started && braceCount === 0) {
166
+ bodyEnd = i + 1;
167
+ break;
168
+ }
169
+ }
170
+ }
171
+ return content.substring(bodyStart, bodyEnd);
172
+ }
173
+ function calculateCyclomaticComplexity(body) {
174
+ let complexity = 1;
175
+ // Count decision points
176
+ const decisionPatterns = [
177
+ /\bif\s*\(/g,
178
+ /\belse\s+if\s*\(/g,
179
+ /\bfor\s*\(/g,
180
+ /\bwhile\s*\(/g,
181
+ /\bcase\s+/g,
182
+ /\bcatch\s*\(/g,
183
+ /\?\s*[^:]+\s*:/g, // Ternary
184
+ /\|\|/g,
185
+ /&&/g,
186
+ ];
187
+ for (const pattern of decisionPatterns) {
188
+ const matches = body.match(pattern);
189
+ if (matches) {
190
+ complexity += matches.length;
191
+ }
192
+ }
193
+ return complexity;
194
+ }
195
+ function extractDependencies(body) {
196
+ const deps = new Set();
197
+ // Extract variable references
198
+ const refRegex = /\b(?:this\.|props\.|state\.)(\w+)/g;
199
+ let match;
200
+ while ((match = refRegex.exec(body)) !== null) {
201
+ deps.add(match[1] || '');
202
+ }
203
+ return Array.from(deps);
204
+ }
205
+ function extractFunctionCalls(body) {
206
+ const calls = new Set();
207
+ const callRegex = /\b(\w+)\s*\(/g;
208
+ let match;
209
+ while ((match = callRegex.exec(body)) !== null) {
210
+ const name = match[1] || '';
211
+ // Exclude keywords and common constructs
212
+ if (!['if', 'for', 'while', 'switch', 'catch', 'function', 'return', 'new'].includes(name)) {
213
+ calls.add(name);
214
+ }
215
+ }
216
+ return Array.from(calls);
217
+ }
218
+ // ============================================
219
+ // Class Extraction
220
+ // ============================================
221
+ function extractClasses(content, filePath) {
222
+ const classes = [];
223
+ const classRegex = /(?:export\s+)?(?:abstract\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([^{]+))?\s*\{/g;
224
+ let match;
225
+ while ((match = classRegex.exec(content)) !== null) {
226
+ const name = match[1] || 'Anonymous';
227
+ const extendsClass = match[2];
228
+ const implementsStr = match[3];
229
+ const line = getLineNumber(content, match.index);
230
+ const classBody = extractFunctionBody(content, match.index);
231
+ const properties = extractClassProperties(classBody);
232
+ const methods = extractClassMethods(classBody, filePath, line);
233
+ classes.push({
234
+ name,
235
+ path: filePath,
236
+ line,
237
+ extends: extendsClass,
238
+ implements: implementsStr ? implementsStr.split(',').map(s => s.trim()) : [],
239
+ properties,
240
+ methods,
241
+ isExported: content.substring(Math.max(0, match.index - 10), match.index).includes('export'),
242
+ });
243
+ }
244
+ return classes;
245
+ }
246
+ function extractClassProperties(classBody) {
247
+ const properties = [];
248
+ const propRegex = /(private|protected|public)?\s*(static)?\s*(\w+)(?:\?)?(?::\s*([^;=]+))?(?:\s*=)?/g;
249
+ let match;
250
+ while ((match = propRegex.exec(classBody)) !== null) {
251
+ const name = match[3] || '';
252
+ // Skip methods
253
+ if (classBody.substring(match.index, match.index + 100).includes('('))
254
+ continue;
255
+ properties.push({
256
+ name,
257
+ type: match[4]?.trim() || 'any',
258
+ visibility: match[1] || 'public',
259
+ static: !!match[2],
260
+ });
261
+ }
262
+ return properties;
263
+ }
264
+ function extractClassMethods(classBody, filePath, classLine) {
265
+ const methods = [];
266
+ const methodRegex = /(private|protected|public)?\s*(static)?\s*(async)?\s*(\w+)\s*\(([^)]*)\)(?:\s*:\s*([^{]+))?\s*\{/g;
267
+ let match;
268
+ while ((match = methodRegex.exec(classBody)) !== null) {
269
+ const name = match[4] || '';
270
+ if (['constructor', 'if', 'for', 'while'].includes(name))
271
+ continue;
272
+ const params = parseParameters(match[5] || '');
273
+ const returnType = match[6]?.trim() || 'void';
274
+ const methodBody = extractFunctionBody(classBody, match.index);
275
+ methods.push({
276
+ name,
277
+ path: filePath,
278
+ line: classLine + getLineNumber(classBody, match.index),
279
+ parameters: params,
280
+ returnType,
281
+ async: !!match[3],
282
+ complexity: calculateCyclomaticComplexity(methodBody),
283
+ linesOfCode: methodBody.split('\n').length,
284
+ dependencies: extractDependencies(methodBody),
285
+ calls: extractFunctionCalls(methodBody),
286
+ isExported: false,
287
+ });
288
+ }
289
+ return methods;
290
+ }
291
+ // ============================================
292
+ // Import/Export Extraction
293
+ // ============================================
294
+ function extractImports(content) {
295
+ const imports = [];
296
+ // Named imports
297
+ const namedImportRegex = /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g;
298
+ let match;
299
+ while ((match = namedImportRegex.exec(content)) !== null) {
300
+ const importsStr = match[1] || '';
301
+ const importItems = importsStr.split(',').map(s => {
302
+ const parts = s.trim().split(/\s+as\s+/);
303
+ return {
304
+ name: parts[0]?.trim() || '',
305
+ alias: parts[1]?.trim(),
306
+ };
307
+ });
308
+ imports.push({
309
+ module: match[2] || '',
310
+ imports: importItems,
311
+ isDefault: false,
312
+ isNamespace: false,
313
+ line: getLineNumber(content, match.index),
314
+ });
315
+ }
316
+ // Default imports
317
+ const defaultImportRegex = /import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g;
318
+ while ((match = defaultImportRegex.exec(content)) !== null) {
319
+ imports.push({
320
+ module: match[2] || '',
321
+ imports: [{ name: match[1] || '' }],
322
+ isDefault: true,
323
+ isNamespace: false,
324
+ line: getLineNumber(content, match.index),
325
+ });
326
+ }
327
+ // Namespace imports
328
+ const namespaceImportRegex = /import\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g;
329
+ while ((match = namespaceImportRegex.exec(content)) !== null) {
330
+ imports.push({
331
+ module: match[2] || '',
332
+ imports: [{ name: match[1] || '' }],
333
+ isDefault: false,
334
+ isNamespace: true,
335
+ line: getLineNumber(content, match.index),
336
+ });
337
+ }
338
+ return imports;
339
+ }
340
+ function extractExports(content) {
341
+ const exports = new Set();
342
+ // Named exports
343
+ const namedExportRegex = /export\s+(?:const|let|var|function|class|interface|type|enum)\s+(\w+)/g;
344
+ let match;
345
+ while ((match = namedExportRegex.exec(content)) !== null) {
346
+ exports.add(match[1] || '');
347
+ }
348
+ // Export list
349
+ const exportListRegex = /export\s*\{([^}]+)\}/g;
350
+ while ((match = exportListRegex.exec(content)) !== null) {
351
+ const items = match[1]?.split(',') || [];
352
+ for (const item of items) {
353
+ const name = item.trim().split(/\s+as\s+/)[0]?.trim() || '';
354
+ if (name)
355
+ exports.add(name);
356
+ }
357
+ }
358
+ // Default export
359
+ if (content.includes('export default')) {
360
+ exports.add('default');
361
+ }
362
+ return Array.from(exports);
363
+ }
364
+ // ============================================
365
+ // React Hooks Extraction
366
+ // ============================================
367
+ function extractReactHooks(content, filePath) {
368
+ const hooks = [];
369
+ // useState
370
+ const useStateRegex = /const\s*\[(\w+),\s*\w+\]\s*=\s*useState(?:<[^>]+>)?\s*\(([^)]*)\)/g;
371
+ let match;
372
+ while ((match = useStateRegex.exec(content)) !== null) {
373
+ hooks.push({
374
+ type: 'useState',
375
+ name: match[1] || 'state',
376
+ line: getLineNumber(content, match.index),
377
+ initialValue: match[2]?.trim(),
378
+ });
379
+ }
380
+ // useEffect
381
+ const useEffectRegex = /useEffect\s*\(\s*\(\s*\)\s*=>\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}(?:,\s*\[([^\]]*)\])?\s*\)/gs;
382
+ while ((match = useEffectRegex.exec(content)) !== null) {
383
+ const body = match[1] || '';
384
+ const deps = match[2]?.split(',').map(s => s.trim()).filter(Boolean) || [];
385
+ hooks.push({
386
+ type: 'useEffect',
387
+ name: 'effect',
388
+ line: getLineNumber(content, match.index),
389
+ dependencies: deps,
390
+ hasCleanup: body.includes('return'),
391
+ });
392
+ }
393
+ // useCallback
394
+ const useCallbackRegex = /const\s+(\w+)\s*=\s*useCallback\s*\([^,]+,\s*\[([^\]]*)\]\s*\)/g;
395
+ while ((match = useCallbackRegex.exec(content)) !== null) {
396
+ hooks.push({
397
+ type: 'useCallback',
398
+ name: match[1] || 'callback',
399
+ line: getLineNumber(content, match.index),
400
+ dependencies: match[2]?.split(',').map(s => s.trim()).filter(Boolean) || [],
401
+ });
402
+ }
403
+ // useMemo
404
+ const useMemoRegex = /const\s+(\w+)\s*=\s*useMemo\s*\([^,]+,\s*\[([^\]]*)\]\s*\)/g;
405
+ while ((match = useMemoRegex.exec(content)) !== null) {
406
+ hooks.push({
407
+ type: 'useMemo',
408
+ name: match[1] || 'memo',
409
+ line: getLineNumber(content, match.index),
410
+ dependencies: match[2]?.split(',').map(s => s.trim()).filter(Boolean) || [],
411
+ });
412
+ }
413
+ // useRef
414
+ const useRefRegex = /const\s+(\w+)\s*=\s*useRef(?:<[^>]+>)?\s*\(([^)]*)\)/g;
415
+ while ((match = useRefRegex.exec(content)) !== null) {
416
+ hooks.push({
417
+ type: 'useRef',
418
+ name: match[1] || 'ref',
419
+ line: getLineNumber(content, match.index),
420
+ initialValue: match[2]?.trim(),
421
+ });
422
+ }
423
+ // Custom hooks
424
+ const customHookRegex = /const\s+(?:\{[^}]+\}|\w+)\s*=\s*(use\w+)\s*\(/g;
425
+ while ((match = customHookRegex.exec(content)) !== null) {
426
+ const hookName = match[1] || '';
427
+ if (!['useState', 'useEffect', 'useCallback', 'useMemo', 'useRef', 'useContext'].includes(hookName)) {
428
+ hooks.push({
429
+ type: 'custom',
430
+ name: hookName,
431
+ line: getLineNumber(content, match.index),
432
+ });
433
+ }
434
+ }
435
+ return hooks;
436
+ }
437
+ // ============================================
438
+ // React Component Extraction
439
+ // ============================================
440
+ function extractReactComponents(content, filePath) {
441
+ const components = [];
442
+ // Functional components
443
+ const funcCompRegex = /(?:export\s+)?(?:const|function)\s+(\w+)\s*(?::\s*(?:React\.)?FC(?:<[^>]+>)?)?[^{]*(?:=>|{)/g;
444
+ let match;
445
+ while ((match = funcCompRegex.exec(content)) !== null) {
446
+ const name = match[1] || '';
447
+ const line = getLineNumber(content, match.index);
448
+ // Check if it returns JSX
449
+ const body = extractFunctionBody(content, match.index);
450
+ if (!body.includes('return') && !body.includes('<'))
451
+ continue;
452
+ if (!body.includes('<') || !/return[^<]*</.test(body)) {
453
+ if (!content.substring(match.index, match.index + 200).includes('=>'))
454
+ continue;
455
+ }
456
+ // Extract props
457
+ const propsMatch = content.substring(match.index, match.index + 500).match(/\(\s*(?:\{([^}]+)\}|(\w+))\s*(?::\s*(\w+Props))?\s*\)/);
458
+ const props = [];
459
+ if (propsMatch && propsMatch[1]) {
460
+ const propsStr = propsMatch[1];
461
+ const propItems = propsStr.split(',');
462
+ for (const item of propItems) {
463
+ const propMatch = item.trim().match(/(\w+)(?:\s*=\s*[^,]+)?/);
464
+ if (propMatch) {
465
+ props.push({
466
+ name: propMatch[1] || '',
467
+ type: 'any',
468
+ required: !item.includes('='),
469
+ });
470
+ }
471
+ }
472
+ }
473
+ // Extract state
474
+ const state = [];
475
+ const stateRegex = /const\s*\[(\w+),\s*set\w+\]\s*=\s*useState(?:<([^>]+)>)?\s*\(([^)]*)\)/g;
476
+ let stateMatch;
477
+ while ((stateMatch = stateRegex.exec(body)) !== null) {
478
+ state.push({
479
+ name: stateMatch[1] || '',
480
+ type: stateMatch[2]?.trim() || 'unknown',
481
+ initialValue: stateMatch[3]?.trim(),
482
+ });
483
+ }
484
+ // Extract lifecycle (useEffect)
485
+ const lifecycle = [];
486
+ const effectRegex = /useEffect\s*\(\s*\(\s*\)\s*=>\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/gs;
487
+ let effectMatch;
488
+ while ((effectMatch = effectRegex.exec(body)) !== null) {
489
+ const effectBody = effectMatch[1] || '';
490
+ const subscriptions = [];
491
+ if (effectBody.includes('subscribe'))
492
+ subscriptions.push('subscription');
493
+ if (effectBody.includes('addEventListener'))
494
+ subscriptions.push('eventListener');
495
+ if (effectBody.includes('setInterval'))
496
+ subscriptions.push('interval');
497
+ if (effectBody.includes('setTimeout'))
498
+ subscriptions.push('timeout');
499
+ lifecycle.push({
500
+ method: 'useEffect',
501
+ hasCleanup: effectBody.includes('return'),
502
+ subscriptions,
503
+ });
504
+ }
505
+ // Extract dependencies
506
+ const dependencies = [];
507
+ const importRegex = /import\s+.*\s+from\s+['"]([^'"]+)['"]/g;
508
+ let importMatch;
509
+ while ((importMatch = importRegex.exec(content)) !== null) {
510
+ dependencies.push(importMatch[1] || '');
511
+ }
512
+ components.push({
513
+ name,
514
+ path: filePath,
515
+ type: 'component',
516
+ props,
517
+ state,
518
+ lifecycle,
519
+ dependencies,
520
+ });
521
+ }
522
+ return components;
523
+ }
524
+ // ============================================
525
+ // API Call Extraction
526
+ // ============================================
527
+ function extractApiCalls(content) {
528
+ const apis = [];
529
+ // Fetch
530
+ const fetchRegex = /fetch\s*\(\s*['"`]([^'"`]+)['"`](?:,\s*\{([^}]+)\})?\s*\)/g;
531
+ let match;
532
+ while ((match = fetchRegex.exec(content)) !== null) {
533
+ const options = match[2] || '';
534
+ const methodMatch = options.match(/method:\s*['"](\w+)['"]/);
535
+ apis.push({
536
+ endpoint: match[1] || '',
537
+ method: methodMatch?.[1]?.toUpperCase() || 'GET',
538
+ path: match[1] || '',
539
+ errorHandling: content.includes('.catch') || content.includes('try'),
540
+ });
541
+ }
542
+ // Axios
543
+ const axiosRegex = /axios\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
544
+ while ((match = axiosRegex.exec(content)) !== null) {
545
+ apis.push({
546
+ endpoint: match[2] || '',
547
+ method: match[1]?.toUpperCase() || 'GET',
548
+ path: match[2] || '',
549
+ errorHandling: content.includes('.catch') || content.includes('try'),
550
+ });
551
+ }
552
+ return apis;
553
+ }
554
+ // ============================================
555
+ // Code Issue Detection
556
+ // ============================================
557
+ function analyzeCodeIssues(content, filePath) {
558
+ const issues = [];
559
+ // Memory issues
560
+ if (content.includes('addEventListener') && !content.includes('removeEventListener')) {
561
+ const line = getLineNumber(content, content.indexOf('addEventListener'));
562
+ issues.push({
563
+ type: 'warning',
564
+ category: 'memory',
565
+ message: 'Event listener added but not removed',
566
+ line,
567
+ file: filePath,
568
+ suggestion: 'Add removeEventListener in cleanup/unmount',
569
+ });
570
+ }
571
+ if (content.includes('setInterval') && !content.includes('clearInterval')) {
572
+ const line = getLineNumber(content, content.indexOf('setInterval'));
573
+ issues.push({
574
+ type: 'warning',
575
+ category: 'memory',
576
+ message: 'setInterval called but not cleared',
577
+ line,
578
+ file: filePath,
579
+ suggestion: 'Store interval ID and call clearInterval in cleanup',
580
+ });
581
+ }
582
+ // useEffect without cleanup
583
+ const effectRegex = /useEffect\s*\(\s*\(\s*\)\s*=>\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)(?:\},\s*\[[^\]]*\])?/gs;
584
+ let effectMatch;
585
+ while ((effectMatch = effectRegex.exec(content)) !== null) {
586
+ const body = effectMatch[1] || '';
587
+ if ((body.includes('subscribe') || body.includes('addEventListener') || body.includes('setInterval')) && !body.includes('return')) {
588
+ issues.push({
589
+ type: 'error',
590
+ category: 'memory',
591
+ message: 'useEffect with subscription/listener missing cleanup function',
592
+ line: getLineNumber(content, effectMatch.index),
593
+ file: filePath,
594
+ suggestion: 'Add return statement with cleanup logic',
595
+ });
596
+ }
597
+ }
598
+ // Performance issues
599
+ if (content.includes('console.log') || content.includes('console.debug')) {
600
+ const line = getLineNumber(content, content.indexOf('console.'));
601
+ issues.push({
602
+ type: 'info',
603
+ category: 'performance',
604
+ message: 'Console statement found - should be removed in production',
605
+ line,
606
+ file: filePath,
607
+ suggestion: 'Remove console statements or use a logging library',
608
+ });
609
+ }
610
+ // Object created in render
611
+ const objectInRenderRegex = /return\s*\([^)]*\{[^}]*:[^}]*\}[^)]*\)/gs;
612
+ if (objectInRenderRegex.test(content)) {
613
+ issues.push({
614
+ type: 'warning',
615
+ category: 'performance',
616
+ message: 'Possible object creation in render - may cause unnecessary re-renders',
617
+ line: 1,
618
+ file: filePath,
619
+ suggestion: 'Use useMemo to memoize objects passed as props',
620
+ });
621
+ }
622
+ // Security issues
623
+ if (content.includes('dangerouslySetInnerHTML')) {
624
+ const line = getLineNumber(content, content.indexOf('dangerouslySetInnerHTML'));
625
+ issues.push({
626
+ type: 'error',
627
+ category: 'security',
628
+ message: 'dangerouslySetInnerHTML usage detected - XSS risk',
629
+ line,
630
+ file: filePath,
631
+ suggestion: 'Sanitize HTML content or avoid using dangerouslySetInnerHTML',
632
+ });
633
+ }
634
+ if (content.includes('eval(')) {
635
+ const line = getLineNumber(content, content.indexOf('eval('));
636
+ issues.push({
637
+ type: 'error',
638
+ category: 'security',
639
+ message: 'eval() usage detected - security risk',
640
+ line,
641
+ file: filePath,
642
+ suggestion: 'Avoid using eval - use safer alternatives',
643
+ });
644
+ }
645
+ // Logic issues
646
+ const asyncWithoutAwaitRegex = /async\s+(?:function\s+\w+|\(\w*\)\s*=>|\w+\s*=\s*async)[^{]*\{([^}]+)\}/g;
647
+ let asyncMatch;
648
+ while ((asyncMatch = asyncWithoutAwaitRegex.exec(content)) !== null) {
649
+ const body = asyncMatch[1] || '';
650
+ if (!body.includes('await')) {
651
+ issues.push({
652
+ type: 'warning',
653
+ category: 'logic',
654
+ message: 'Async function without await - may be unnecessary',
655
+ line: getLineNumber(content, asyncMatch.index),
656
+ file: filePath,
657
+ suggestion: 'Either add await or remove async keyword',
658
+ });
659
+ }
660
+ }
661
+ return issues;
662
+ }
663
+ // ============================================
664
+ // Metrics Calculation
665
+ // ============================================
666
+ function calculateMetrics(content, functions, classes) {
667
+ const lines = content.split('\n');
668
+ let codeLines = 0;
669
+ let commentLines = 0;
670
+ let blankLines = 0;
671
+ let inMultiLineComment = false;
672
+ for (const line of lines) {
673
+ const trimmed = line.trim();
674
+ if (!trimmed) {
675
+ blankLines++;
676
+ }
677
+ else if (inMultiLineComment) {
678
+ commentLines++;
679
+ if (trimmed.includes('*/')) {
680
+ inMultiLineComment = false;
681
+ }
682
+ }
683
+ else if (trimmed.startsWith('//')) {
684
+ commentLines++;
685
+ }
686
+ else if (trimmed.startsWith('/*')) {
687
+ commentLines++;
688
+ if (!trimmed.includes('*/')) {
689
+ inMultiLineComment = true;
690
+ }
691
+ }
692
+ else {
693
+ codeLines++;
694
+ }
695
+ }
696
+ return {
697
+ totalLines: lines.length,
698
+ codeLines,
699
+ commentLines,
700
+ blankLines,
701
+ functionCount: functions.length,
702
+ classCount: classes.length,
703
+ };
704
+ }
705
+ // ============================================
706
+ // Helper Functions
707
+ // ============================================
708
+ function getLineNumber(content, index) {
709
+ return content.substring(0, index).split('\n').length;
710
+ }
711
+ // ============================================
712
+ // Full Project Analysis
713
+ // ============================================
714
+ async function analyzeProject(projectPath, platform) {
715
+ const extensions = getExtensionsForPlatform(platform);
716
+ const files = getAllSourceFiles(projectPath, extensions);
717
+ const results = [];
718
+ let totalFunctions = 0;
719
+ let totalClasses = 0;
720
+ let totalComponents = 0;
721
+ let totalComplexity = 0;
722
+ let complexityCount = 0;
723
+ const issueCount = { error: 0, warning: 0, info: 0 };
724
+ for (const filePath of files) {
725
+ try {
726
+ const content = fs.readFileSync(filePath, 'utf-8');
727
+ const analysis = analyzeTypeScript(filePath, content);
728
+ results.push({ path: filePath, analysis });
729
+ totalFunctions += analysis.functions.length;
730
+ totalClasses += analysis.classes.length;
731
+ totalComponents += analysis.components.length;
732
+ if (analysis.complexity.average > 0) {
733
+ totalComplexity += analysis.complexity.average;
734
+ complexityCount++;
735
+ }
736
+ for (const issue of analysis.issues) {
737
+ issueCount[issue.type]++;
738
+ }
739
+ }
740
+ catch {
741
+ // Skip files that can't be read
742
+ }
743
+ }
744
+ return {
745
+ files: results,
746
+ summary: {
747
+ totalFiles: files.length,
748
+ totalFunctions,
749
+ totalClasses,
750
+ totalComponents,
751
+ avgComplexity: complexityCount > 0 ? totalComplexity / complexityCount : 0,
752
+ issueCount,
753
+ },
754
+ };
755
+ }
756
+ function getExtensionsForPlatform(platform) {
757
+ switch (platform) {
758
+ case 'ios':
759
+ return ['.swift', '.m', '.mm'];
760
+ case 'android':
761
+ return ['.kt', '.java'];
762
+ case 'flutter':
763
+ return ['.dart'];
764
+ case 'react-native':
765
+ case 'web':
766
+ default:
767
+ return ['.ts', '.tsx', '.js', '.jsx'];
768
+ }
769
+ }
770
+ function getAllSourceFiles(dir, extensions) {
771
+ const results = [];
772
+ const skipDirs = ['node_modules', '.git', 'build', 'dist', 'Pods', '.gradle', 'DerivedData'];
773
+ function walk(currentDir) {
774
+ try {
775
+ const items = fs.readdirSync(currentDir);
776
+ for (const item of items) {
777
+ if (skipDirs.includes(item))
778
+ continue;
779
+ const fullPath = path.join(currentDir, item);
780
+ const stat = fs.statSync(fullPath);
781
+ if (stat.isDirectory()) {
782
+ walk(fullPath);
783
+ }
784
+ else if (extensions.some(ext => item.endsWith(ext))) {
785
+ results.push(fullPath);
786
+ }
787
+ }
788
+ }
789
+ catch {
790
+ // Ignore errors
791
+ }
792
+ }
793
+ walk(dir);
794
+ return results;
795
+ }
796
+ exports.default = {
797
+ analyzeTypeScript,
798
+ analyzeProject,
799
+ };
800
+ //# sourceMappingURL=astAnalyzer.js.map