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,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
|