codeguard-testgen 1.0.14 → 1.0.16
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 +157 -1034
- package/dist/ai.d.ts +8 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +332 -0
- package/dist/ai.js.map +1 -0
- package/dist/ast.d.ts +8 -0
- package/dist/ast.d.ts.map +1 -0
- package/dist/ast.js +988 -0
- package/dist/ast.js.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -0
- package/dist/config.js.map +1 -1
- package/dist/git.d.ts +18 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +208 -0
- package/dist/git.js.map +1 -0
- package/dist/globals.d.ts +24 -0
- package/dist/globals.d.ts.map +1 -0
- package/dist/globals.js +40 -0
- package/dist/globals.js.map +1 -0
- package/dist/index.d.ts +9 -54
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +85 -5434
- package/dist/index.js.map +1 -1
- package/dist/pathResolver.d.ts +12 -0
- package/dist/pathResolver.d.ts.map +1 -0
- package/dist/pathResolver.js +44 -0
- package/dist/pathResolver.js.map +1 -0
- package/dist/reviewer.d.ts +13 -0
- package/dist/reviewer.d.ts.map +1 -0
- package/dist/reviewer.js +402 -0
- package/dist/reviewer.js.map +1 -0
- package/dist/testGenerator.d.ts +24 -0
- package/dist/testGenerator.d.ts.map +1 -0
- package/dist/testGenerator.js +1107 -0
- package/dist/testGenerator.js.map +1 -0
- package/dist/toolDefinitions.d.ts +6 -0
- package/dist/toolDefinitions.d.ts.map +1 -0
- package/dist/toolDefinitions.js +370 -0
- package/dist/toolDefinitions.js.map +1 -0
- package/dist/toolHandlers.d.ts +76 -0
- package/dist/toolHandlers.d.ts.map +1 -0
- package/dist/toolHandlers.js +1430 -0
- package/dist/toolHandlers.js.map +1 -0
- package/dist/types.d.ts +74 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +1 -2
package/dist/ast.js
ADDED
|
@@ -0,0 +1,988 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseFileToAST = parseFileToAST;
|
|
4
|
+
exports.analyzeFileAST = analyzeFileAST;
|
|
5
|
+
exports.getFunctionAST = getFunctionAST;
|
|
6
|
+
exports.getImportsAST = getImportsAST;
|
|
7
|
+
exports.getTypeDefinitions = getTypeDefinitions;
|
|
8
|
+
exports.getFilePreamble = getFilePreamble;
|
|
9
|
+
exports.getClassMethods = getClassMethods;
|
|
10
|
+
const fsSync = require("fs");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
const babelParser = require("@babel/parser");
|
|
13
|
+
const traverse = require('@babel/traverse').default;
|
|
14
|
+
const globals_1 = require("./globals");
|
|
15
|
+
const pathResolver_1 = require("./pathResolver");
|
|
16
|
+
// AST Parsing utilities
|
|
17
|
+
function parseFileToAST(filePath, content) {
|
|
18
|
+
const ext = path.extname(filePath);
|
|
19
|
+
try {
|
|
20
|
+
return babelParser.parse(content, {
|
|
21
|
+
sourceType: 'module',
|
|
22
|
+
plugins: [
|
|
23
|
+
'typescript',
|
|
24
|
+
ext === '.tsx' || ext === '.jsx' ? 'jsx' : null,
|
|
25
|
+
'decorators-legacy',
|
|
26
|
+
'classProperties',
|
|
27
|
+
'objectRestSpread',
|
|
28
|
+
'asyncGenerators',
|
|
29
|
+
'dynamicImport',
|
|
30
|
+
'optionalChaining',
|
|
31
|
+
'nullishCoalescingOperator'
|
|
32
|
+
].filter(Boolean)
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw new Error(`Failed to parse ${filePath}: ${error.message}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function analyzeFileAST(filePath, functionName) {
|
|
40
|
+
try {
|
|
41
|
+
const content = fsSync.readFileSync(filePath, 'utf-8');
|
|
42
|
+
const ast = parseFileToAST(filePath, content);
|
|
43
|
+
if (!ast || !ast.program) {
|
|
44
|
+
throw new Error('Failed to parse AST: no program node found');
|
|
45
|
+
}
|
|
46
|
+
const analysis = {
|
|
47
|
+
functions: [],
|
|
48
|
+
classes: [],
|
|
49
|
+
exports: [],
|
|
50
|
+
imports: [],
|
|
51
|
+
types: [],
|
|
52
|
+
constants: [],
|
|
53
|
+
routeRegistrations: []
|
|
54
|
+
};
|
|
55
|
+
traverse(ast, {
|
|
56
|
+
// Function declarations
|
|
57
|
+
FunctionDeclaration(path) {
|
|
58
|
+
const node = path.node;
|
|
59
|
+
analysis.functions.push({
|
|
60
|
+
name: node.id?.name,
|
|
61
|
+
type: 'function',
|
|
62
|
+
async: node.async,
|
|
63
|
+
generator: node.generator,
|
|
64
|
+
params: node.params.map(p => extractParamInfo(p)),
|
|
65
|
+
returnType: node.returnType ? getTypeAnnotation(node.returnType) : null,
|
|
66
|
+
line: node.loc?.start.line,
|
|
67
|
+
exported: path.parent.type === 'ExportNamedDeclaration' ||
|
|
68
|
+
path.parent.type === 'ExportDefaultDeclaration'
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
// Arrow functions and function expressions
|
|
72
|
+
VariableDeclarator(path) {
|
|
73
|
+
const node = path.node;
|
|
74
|
+
if (node.init && (node.init.type === 'ArrowFunctionExpression' ||
|
|
75
|
+
node.init.type === 'FunctionExpression')) {
|
|
76
|
+
analysis.functions.push({
|
|
77
|
+
name: node.id.name,
|
|
78
|
+
type: node.init.type === 'ArrowFunctionExpression' ? 'arrow' : 'function',
|
|
79
|
+
async: node.init.async,
|
|
80
|
+
params: node.init.params.map(p => extractParamInfo(p)),
|
|
81
|
+
returnType: node.init.returnType ? getTypeAnnotation(node.init.returnType) : null,
|
|
82
|
+
line: node.loc?.start.line,
|
|
83
|
+
exported: path.parentPath.parent.type === 'ExportNamedDeclaration'
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
else if (node.init) {
|
|
87
|
+
// Constants
|
|
88
|
+
analysis.constants.push({
|
|
89
|
+
name: node.id.name,
|
|
90
|
+
type: node.id.typeAnnotation ? getTypeAnnotation(node.id.typeAnnotation) : null,
|
|
91
|
+
line: node.loc?.start.line
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
// Classes
|
|
96
|
+
ClassDeclaration(path) {
|
|
97
|
+
const node = path.node;
|
|
98
|
+
const methods = [];
|
|
99
|
+
if (node.body && node.body.body) {
|
|
100
|
+
node.body.body.forEach(member => {
|
|
101
|
+
if (member.type === 'ClassMethod' || member.type === 'MethodDefinition') {
|
|
102
|
+
methods.push({
|
|
103
|
+
name: member.key.name,
|
|
104
|
+
kind: member.kind,
|
|
105
|
+
static: member.static,
|
|
106
|
+
async: member.async,
|
|
107
|
+
params: member.params ? member.params.map(p => extractParamInfo(p)) : [],
|
|
108
|
+
returnType: member.returnType ? getTypeAnnotation(member.returnType) : null
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
analysis.classes.push({
|
|
114
|
+
name: node.id?.name,
|
|
115
|
+
methods,
|
|
116
|
+
superClass: node.superClass?.name,
|
|
117
|
+
line: node.loc?.start.line,
|
|
118
|
+
exported: path.parent.type === 'ExportNamedDeclaration' ||
|
|
119
|
+
path.parent.type === 'ExportDefaultDeclaration'
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
// Type definitions
|
|
123
|
+
TSTypeAliasDeclaration(path) {
|
|
124
|
+
analysis.types.push({
|
|
125
|
+
name: path.node.id.name,
|
|
126
|
+
kind: 'type',
|
|
127
|
+
line: path.node.loc?.start.line
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
TSInterfaceDeclaration(path) {
|
|
131
|
+
analysis.types.push({
|
|
132
|
+
name: path.node.id.name,
|
|
133
|
+
kind: 'interface',
|
|
134
|
+
line: path.node.loc?.start.line
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
// Exports
|
|
138
|
+
ExportNamedDeclaration(path) {
|
|
139
|
+
if (path.node.specifiers) {
|
|
140
|
+
path.node.specifiers.forEach(spec => {
|
|
141
|
+
analysis.exports.push({
|
|
142
|
+
name: spec.exported.name,
|
|
143
|
+
local: spec.local.name,
|
|
144
|
+
type: 'named'
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
ExportDefaultDeclaration(path) {
|
|
150
|
+
const declaration = path.node.declaration;
|
|
151
|
+
let name = 'default';
|
|
152
|
+
if (declaration.id) {
|
|
153
|
+
name = declaration.id.name;
|
|
154
|
+
}
|
|
155
|
+
else if (declaration.name) {
|
|
156
|
+
name = declaration.name;
|
|
157
|
+
}
|
|
158
|
+
analysis.exports.push({
|
|
159
|
+
name,
|
|
160
|
+
type: 'default'
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
// Detect route registrations
|
|
165
|
+
analysis.routeRegistrations = detectRouteRegistrations(content, analysis.imports);
|
|
166
|
+
// Filter to specific function if requested (token optimization)
|
|
167
|
+
if (functionName) {
|
|
168
|
+
const targetFunction = analysis.functions.find(f => f.name === functionName);
|
|
169
|
+
if (!targetFunction) {
|
|
170
|
+
const available = analysis.functions.map(f => f.name).slice(0, 10).join(', ');
|
|
171
|
+
const more = analysis.functions.length > 10 ? ` (and ${analysis.functions.length - 10} more)` : '';
|
|
172
|
+
return {
|
|
173
|
+
success: false,
|
|
174
|
+
error: `Function '${functionName}' not found in ${filePath}. Available functions: ${available}${more}`
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
let relatedClass = null;
|
|
178
|
+
for (const cls of analysis.classes) {
|
|
179
|
+
if (cls.methods?.some((m) => m.name === functionName)) {
|
|
180
|
+
relatedClass = cls;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
success: true,
|
|
186
|
+
analysis: {
|
|
187
|
+
functions: [targetFunction],
|
|
188
|
+
classes: relatedClass ? [relatedClass] : [],
|
|
189
|
+
exports: analysis.exports.filter(e => e.name === functionName || e.local === functionName),
|
|
190
|
+
imports: analysis.imports,
|
|
191
|
+
types: [],
|
|
192
|
+
constants: [],
|
|
193
|
+
routeRegistrations: analysis.routeRegistrations.filter(r => r.handler === functionName)
|
|
194
|
+
},
|
|
195
|
+
summary: {
|
|
196
|
+
functionCount: 1,
|
|
197
|
+
classCount: relatedClass ? 1 : 0,
|
|
198
|
+
exportCount: analysis.exports.filter(e => e.name === functionName || e.local === functionName).length,
|
|
199
|
+
typeCount: 0,
|
|
200
|
+
filtered: true,
|
|
201
|
+
targetFunction: functionName
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
success: true,
|
|
207
|
+
analysis,
|
|
208
|
+
summary: {
|
|
209
|
+
functionCount: analysis.functions.length,
|
|
210
|
+
classCount: analysis.classes.length,
|
|
211
|
+
exportCount: analysis.exports.length,
|
|
212
|
+
typeCount: analysis.types.length
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
return { success: false, error: error.message };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Fast route detection using regex patterns
|
|
221
|
+
function detectRouteRegistrations(code, imports) {
|
|
222
|
+
const registrations = [];
|
|
223
|
+
// Express: router.get('/users', getUsers) or app.post('/users', createUser)
|
|
224
|
+
const expressPattern = /\b(app|router)\.(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]\s*,\s*(\w+)/g;
|
|
225
|
+
for (const match of code.matchAll(expressPattern)) {
|
|
226
|
+
registrations.push({
|
|
227
|
+
framework: 'express',
|
|
228
|
+
method: match[2].toUpperCase(),
|
|
229
|
+
path: match[3],
|
|
230
|
+
handler: match[4],
|
|
231
|
+
handlerImportedFrom: findImportSource(match[4], imports),
|
|
232
|
+
line: getLineNumber(code, match.index || 0)
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
// Fastify: fastify.get('/users', getUsers)
|
|
236
|
+
const fastifyPattern = /\bfastify\.(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]\s*,\s*(\w+)/g;
|
|
237
|
+
for (const match of code.matchAll(fastifyPattern)) {
|
|
238
|
+
registrations.push({
|
|
239
|
+
framework: 'fastify',
|
|
240
|
+
method: match[1].toUpperCase(),
|
|
241
|
+
path: match[2],
|
|
242
|
+
handler: match[3],
|
|
243
|
+
handlerImportedFrom: findImportSource(match[3], imports),
|
|
244
|
+
line: getLineNumber(code, match.index || 0)
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
// Koa: router.get('/users', getUsers)
|
|
248
|
+
const koaPattern = /\brouter\.(get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"]\s*,\s*(\w+)/g;
|
|
249
|
+
for (const match of code.matchAll(koaPattern)) {
|
|
250
|
+
registrations.push({
|
|
251
|
+
framework: 'koa',
|
|
252
|
+
method: match[1].toUpperCase(),
|
|
253
|
+
path: match[2],
|
|
254
|
+
handler: match[3],
|
|
255
|
+
handlerImportedFrom: findImportSource(match[3], imports),
|
|
256
|
+
line: getLineNumber(code, match.index || 0)
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return registrations;
|
|
260
|
+
}
|
|
261
|
+
function findImportSource(functionName, imports) {
|
|
262
|
+
for (const imp of imports) {
|
|
263
|
+
if (imp.imported && Array.isArray(imp.imported)) {
|
|
264
|
+
if (imp.imported.some((i) => i.local === functionName || i.imported === functionName)) {
|
|
265
|
+
return imp.source;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
function getLineNumber(code, index) {
|
|
272
|
+
return code.substring(0, index).split('\n').length;
|
|
273
|
+
}
|
|
274
|
+
function extractParamInfo(param) {
|
|
275
|
+
if (param.type === 'Identifier') {
|
|
276
|
+
return {
|
|
277
|
+
name: param.name,
|
|
278
|
+
type: param.typeAnnotation ? getTypeAnnotation(param.typeAnnotation) : null,
|
|
279
|
+
optional: param.optional
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
else if (param.type === 'RestElement') {
|
|
283
|
+
return {
|
|
284
|
+
name: param.argument.name,
|
|
285
|
+
rest: true,
|
|
286
|
+
type: param.typeAnnotation ? getTypeAnnotation(param.typeAnnotation) : null
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
else if (param.type === 'AssignmentPattern') {
|
|
290
|
+
return {
|
|
291
|
+
name: param.left.name,
|
|
292
|
+
defaultValue: true,
|
|
293
|
+
type: param.left.typeAnnotation ? getTypeAnnotation(param.left.typeAnnotation) : null
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
else if (param.type === 'ObjectPattern') {
|
|
297
|
+
// Handle destructured object params like { x, y, z: zValue }
|
|
298
|
+
const properties = param.properties.map((prop) => {
|
|
299
|
+
if (prop.type === 'RestElement') {
|
|
300
|
+
return { name: prop.argument.name, rest: true };
|
|
301
|
+
}
|
|
302
|
+
return { name: prop.value.name };
|
|
303
|
+
});
|
|
304
|
+
return {
|
|
305
|
+
name: `{ ${properties.map((p) => p.name).join(', ')} }`,
|
|
306
|
+
destructured: true,
|
|
307
|
+
destructuredProps: properties,
|
|
308
|
+
type: param.typeAnnotation ? getTypeAnnotation(param.typeAnnotation) : null
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
else if (param.type === 'ArrayPattern') {
|
|
312
|
+
// Handle destructured array params like [a, b, c]
|
|
313
|
+
const elements = param.elements
|
|
314
|
+
.filter((el) => el !== null)
|
|
315
|
+
.map((el) => {
|
|
316
|
+
if (el.type === 'RestElement') {
|
|
317
|
+
return { name: el.argument.name, rest: true };
|
|
318
|
+
}
|
|
319
|
+
return { name: el.name };
|
|
320
|
+
});
|
|
321
|
+
return {
|
|
322
|
+
name: `[ ${elements.map((e) => e.name).join(', ')} ]`,
|
|
323
|
+
destructured: true,
|
|
324
|
+
destructuredElements: elements,
|
|
325
|
+
type: param.typeAnnotation ? getTypeAnnotation(param.typeAnnotation) : null
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
return { name: 'unknown' };
|
|
329
|
+
}
|
|
330
|
+
function getTypeAnnotation(typeNode) {
|
|
331
|
+
if (!typeNode)
|
|
332
|
+
return null;
|
|
333
|
+
if (typeNode.typeAnnotation) {
|
|
334
|
+
typeNode = typeNode.typeAnnotation;
|
|
335
|
+
}
|
|
336
|
+
if (typeNode.type === 'TSStringKeyword')
|
|
337
|
+
return 'string';
|
|
338
|
+
if (typeNode.type === 'TSNumberKeyword')
|
|
339
|
+
return 'number';
|
|
340
|
+
if (typeNode.type === 'TSBooleanKeyword')
|
|
341
|
+
return 'boolean';
|
|
342
|
+
if (typeNode.type === 'TSAnyKeyword')
|
|
343
|
+
return 'any';
|
|
344
|
+
if (typeNode.type === 'TSVoidKeyword')
|
|
345
|
+
return 'void';
|
|
346
|
+
if (typeNode.type === 'TSUndefinedKeyword')
|
|
347
|
+
return 'undefined';
|
|
348
|
+
if (typeNode.type === 'TSNullKeyword')
|
|
349
|
+
return 'null';
|
|
350
|
+
if (typeNode.type === 'TSNeverKeyword')
|
|
351
|
+
return 'never';
|
|
352
|
+
if (typeNode.type === 'TSUnknownKeyword')
|
|
353
|
+
return 'unknown';
|
|
354
|
+
if (typeNode.type === 'TSObjectKeyword')
|
|
355
|
+
return 'object';
|
|
356
|
+
if (typeNode.type === 'TSTypeReference' && typeNode.typeName) {
|
|
357
|
+
// Resolve qualified names like Foo.Bar
|
|
358
|
+
const name = typeNode.typeName.type === 'TSQualifiedName'
|
|
359
|
+
? `${typeNode.typeName.left?.name}.${typeNode.typeName.right?.name}`
|
|
360
|
+
: (typeNode.typeName.name || 'unknown');
|
|
361
|
+
// Preserve generics: Promise<User>, Array<string>, Record<string, any>, etc.
|
|
362
|
+
if (typeNode.typeParameters?.params?.length) {
|
|
363
|
+
const params = typeNode.typeParameters.params
|
|
364
|
+
.map((p) => getTypeAnnotation(p))
|
|
365
|
+
.join(', ');
|
|
366
|
+
return `${name}<${params}>`;
|
|
367
|
+
}
|
|
368
|
+
return name;
|
|
369
|
+
}
|
|
370
|
+
if (typeNode.type === 'TSArrayType') {
|
|
371
|
+
const elementType = getTypeAnnotation(typeNode.elementType);
|
|
372
|
+
return `${elementType}[]`;
|
|
373
|
+
}
|
|
374
|
+
if (typeNode.type === 'TSUnionType') {
|
|
375
|
+
return typeNode.types.map((t) => getTypeAnnotation(t)).join(' | ');
|
|
376
|
+
}
|
|
377
|
+
if (typeNode.type === 'TSIntersectionType') {
|
|
378
|
+
return typeNode.types.map((t) => getTypeAnnotation(t)).join(' & ');
|
|
379
|
+
}
|
|
380
|
+
if (typeNode.type === 'TSTupleType') {
|
|
381
|
+
const elements = (typeNode.elementTypes || typeNode.elements || [])
|
|
382
|
+
.map((t) => getTypeAnnotation(t))
|
|
383
|
+
.join(', ');
|
|
384
|
+
return `[${elements}]`;
|
|
385
|
+
}
|
|
386
|
+
if (typeNode.type === 'TSLiteralType') {
|
|
387
|
+
const lit = typeNode.literal;
|
|
388
|
+
if (lit?.type === 'StringLiteral')
|
|
389
|
+
return `'${lit.value}'`;
|
|
390
|
+
if (lit?.type === 'NumericLiteral')
|
|
391
|
+
return String(lit.value);
|
|
392
|
+
if (lit?.type === 'BooleanLiteral')
|
|
393
|
+
return String(lit.value);
|
|
394
|
+
}
|
|
395
|
+
if (typeNode.type === 'TSParenthesizedType') {
|
|
396
|
+
return getTypeAnnotation(typeNode.typeAnnotation);
|
|
397
|
+
}
|
|
398
|
+
if (typeNode.type === 'TSOptionalType') {
|
|
399
|
+
return `${getTypeAnnotation(typeNode.typeAnnotation)}?`;
|
|
400
|
+
}
|
|
401
|
+
return 'unknown';
|
|
402
|
+
}
|
|
403
|
+
function getFunctionAST(filePath, functionName) {
|
|
404
|
+
const searchInFile = (targetPath) => {
|
|
405
|
+
try {
|
|
406
|
+
const content = fsSync.readFileSync(targetPath, 'utf-8');
|
|
407
|
+
const ast = parseFileToAST(targetPath, content);
|
|
408
|
+
const lines = content.split('\n');
|
|
409
|
+
let functionInfo = null;
|
|
410
|
+
traverse(ast, {
|
|
411
|
+
FunctionDeclaration(path) {
|
|
412
|
+
if (path.node.id?.name === functionName) {
|
|
413
|
+
functionInfo = extractFunctionDetails(path, lines);
|
|
414
|
+
path.stop();
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
VariableDeclarator(path) {
|
|
418
|
+
if (path.node.id.name === functionName &&
|
|
419
|
+
(path.node.init?.type === 'ArrowFunctionExpression' ||
|
|
420
|
+
path.node.init?.type === 'FunctionExpression')) {
|
|
421
|
+
functionInfo = extractFunctionDetails(path, lines, true);
|
|
422
|
+
path.stop();
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
ClassMethod(path) {
|
|
426
|
+
if (path.node.key.name === functionName) {
|
|
427
|
+
functionInfo = extractFunctionDetails(path, lines);
|
|
428
|
+
path.stop();
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
if (functionInfo) {
|
|
433
|
+
return { success: true, ...functionInfo, filePath: targetPath };
|
|
434
|
+
}
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
// Try the provided file path first
|
|
442
|
+
try {
|
|
443
|
+
const result = searchInFile(filePath);
|
|
444
|
+
if (result) {
|
|
445
|
+
return result;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
catch (error) {
|
|
449
|
+
// File not found or other error - continue to fallback
|
|
450
|
+
}
|
|
451
|
+
// Fallback: If we have an indexer, search for the function in the index
|
|
452
|
+
if (globals_1.g.globalIndexer) {
|
|
453
|
+
const indexData = globals_1.g.globalIndexer.index;
|
|
454
|
+
if (indexData && indexData.files) {
|
|
455
|
+
for (const [indexedFilePath, fileData] of Object.entries(indexData.files)) {
|
|
456
|
+
const analysis = fileData.analysis;
|
|
457
|
+
const hasFunction = analysis.functions?.some((fn) => fn.name === functionName);
|
|
458
|
+
const hasMethod = analysis.classes?.some((cls) => cls.methods?.some((method) => method.name === functionName));
|
|
459
|
+
if (hasFunction || hasMethod) {
|
|
460
|
+
const result = searchInFile(indexedFilePath);
|
|
461
|
+
if (result) {
|
|
462
|
+
return result;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return {
|
|
468
|
+
success: false,
|
|
469
|
+
error: `Function ${functionName} not found in ${filePath} or any indexed files`
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
return {
|
|
473
|
+
success: false,
|
|
474
|
+
error: `Function ${functionName} not found in ${filePath}. Tip: Enable indexing for better function lookup across files.`
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Traverse a function's body and collect which properties are accessed on each
|
|
479
|
+
* named parameter. This is the primary type-inference mechanism for parameters
|
|
480
|
+
* annotated as `any` (or unannotated), where static type info is unavailable.
|
|
481
|
+
*
|
|
482
|
+
* Example: given `function fn(dto: any) { return dto.name + dto.email }`,
|
|
483
|
+
* returns `{ dto: ['name', 'email'] }`.
|
|
484
|
+
*/
|
|
485
|
+
function extractParameterUsages(path, paramNames) {
|
|
486
|
+
if (paramNames.length === 0)
|
|
487
|
+
return {};
|
|
488
|
+
const usages = {};
|
|
489
|
+
try {
|
|
490
|
+
path.traverse({
|
|
491
|
+
MemberExpression(memberPath) {
|
|
492
|
+
const obj = memberPath.node.object;
|
|
493
|
+
const prop = memberPath.node.property;
|
|
494
|
+
if (obj.type === 'Identifier' && paramNames.includes(obj.name)) {
|
|
495
|
+
if (!usages[obj.name])
|
|
496
|
+
usages[obj.name] = [];
|
|
497
|
+
// Only collect computed-string and identifier accesses (skip dynamic keys)
|
|
498
|
+
if (prop.type === 'Identifier' && !usages[obj.name].includes(prop.name)) {
|
|
499
|
+
usages[obj.name].push(prop.name);
|
|
500
|
+
}
|
|
501
|
+
else if (prop.type === 'StringLiteral' &&
|
|
502
|
+
!usages[obj.name].includes(prop.value)) {
|
|
503
|
+
usages[obj.name].push(prop.value);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
catch (_err) {
|
|
510
|
+
// Non-fatal: traversal can fail on unusual AST shapes; return what we have
|
|
511
|
+
}
|
|
512
|
+
// Only include params that actually had property accesses
|
|
513
|
+
return Object.fromEntries(Object.entries(usages).filter(([, props]) => props.length > 0));
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Calculate cyclomatic complexity for a function.
|
|
517
|
+
* Counts decision points: if/else, switch, case, for, while, do-while,
|
|
518
|
+
* ternary operators, logical operators (&&, ||), catch clauses
|
|
519
|
+
*/
|
|
520
|
+
function calculateCyclomaticComplexity(path) {
|
|
521
|
+
let complexity = 1;
|
|
522
|
+
try {
|
|
523
|
+
path.traverse({
|
|
524
|
+
IfStatement: () => { complexity++; },
|
|
525
|
+
SwitchStatement: () => { complexity++; },
|
|
526
|
+
SwitchCase: () => { complexity++; },
|
|
527
|
+
ForStatement: () => { complexity++; },
|
|
528
|
+
ForInStatement: () => { complexity++; },
|
|
529
|
+
ForOfStatement: () => { complexity++; },
|
|
530
|
+
WhileStatement: () => { complexity++; },
|
|
531
|
+
DoWhileStatement: () => { complexity++; },
|
|
532
|
+
ConditionalExpression: () => { complexity++; },
|
|
533
|
+
LogicalExpression: () => {
|
|
534
|
+
// && and || add complexity
|
|
535
|
+
complexity++;
|
|
536
|
+
},
|
|
537
|
+
CatchClause: () => { complexity++; }
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
catch (_err) {
|
|
541
|
+
// If traversal fails, return base complexity
|
|
542
|
+
}
|
|
543
|
+
return complexity;
|
|
544
|
+
}
|
|
545
|
+
function extractFunctionDetails(path, lines, isVariable = false) {
|
|
546
|
+
const node = isVariable ? path.node.init : path.node;
|
|
547
|
+
const startLine = node.loc.start.line - 1;
|
|
548
|
+
const endLine = node.loc.end.line - 1;
|
|
549
|
+
// Look backwards for JSDoc/comments (up to 20 lines)
|
|
550
|
+
let commentStartLine = startLine;
|
|
551
|
+
let lookbackLimit = Math.max(0, startLine - 20);
|
|
552
|
+
while (commentStartLine > lookbackLimit) {
|
|
553
|
+
const prevLine = lines[commentStartLine - 1]?.trim() || '';
|
|
554
|
+
if (prevLine.startsWith('*') ||
|
|
555
|
+
prevLine.startsWith('//') ||
|
|
556
|
+
prevLine.startsWith('/**') ||
|
|
557
|
+
prevLine.endsWith('*/') ||
|
|
558
|
+
prevLine === '') {
|
|
559
|
+
commentStartLine--;
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
const code = lines.slice(commentStartLine, endLine + 1).join('\n');
|
|
566
|
+
const calledFunctions = [];
|
|
567
|
+
const calledMethods = [];
|
|
568
|
+
try {
|
|
569
|
+
path.traverse({
|
|
570
|
+
CallExpression(callPath) {
|
|
571
|
+
const callee = callPath.node.callee;
|
|
572
|
+
if (callee.type === 'Identifier') {
|
|
573
|
+
const funcName = callee.name;
|
|
574
|
+
if (!calledFunctions.includes(funcName)) {
|
|
575
|
+
calledFunctions.push(funcName);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (callee.type === 'MemberExpression' && callee.property.type === 'Identifier') {
|
|
579
|
+
const objectName = callee.object.type === 'Identifier' ? callee.object.name :
|
|
580
|
+
callee.object.type === 'ThisExpression' ? 'this' : 'unknown';
|
|
581
|
+
const methodCall = `${objectName}.${callee.property.name}`;
|
|
582
|
+
if (!calledMethods.includes(methodCall)) {
|
|
583
|
+
calledMethods.push(methodCall);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
catch (error) {
|
|
590
|
+
// If traversal fails, just continue without called functions
|
|
591
|
+
console.warn('Could not extract called functions:', error);
|
|
592
|
+
}
|
|
593
|
+
const params = node.params.map((p) => extractParamInfo(p));
|
|
594
|
+
// Collect param names for usage analysis; skip params we couldn't resolve
|
|
595
|
+
const paramNames = node.params
|
|
596
|
+
.map((p) => {
|
|
597
|
+
if (p.type === 'Identifier')
|
|
598
|
+
return p.name;
|
|
599
|
+
if (p.type === 'AssignmentPattern' && p.left?.type === 'Identifier')
|
|
600
|
+
return p.left.name;
|
|
601
|
+
if (p.type === 'RestElement' && p.argument?.type === 'Identifier')
|
|
602
|
+
return p.argument.name;
|
|
603
|
+
return null;
|
|
604
|
+
})
|
|
605
|
+
.filter((n) => n !== null);
|
|
606
|
+
const parameterUsages = extractParameterUsages(path, paramNames);
|
|
607
|
+
return {
|
|
608
|
+
name: isVariable ? path.node.id.name : node.id?.name,
|
|
609
|
+
code,
|
|
610
|
+
startLine: commentStartLine + 1,
|
|
611
|
+
endLine: endLine + 1,
|
|
612
|
+
params,
|
|
613
|
+
returnType: node.returnType ? getTypeAnnotation(node.returnType) : null,
|
|
614
|
+
async: node.async,
|
|
615
|
+
calledFunctions: calledFunctions.length > 0 ? calledFunctions : undefined,
|
|
616
|
+
calledMethods: calledMethods.length > 0 ? calledMethods : undefined,
|
|
617
|
+
// Only included when at least one param has observable property accesses
|
|
618
|
+
parameterUsages: Object.keys(parameterUsages).length > 0 ? parameterUsages : undefined,
|
|
619
|
+
complexity: calculateCyclomaticComplexity(path)
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
function getImportsAST(filePath) {
|
|
623
|
+
try {
|
|
624
|
+
const content = fsSync.readFileSync(filePath, 'utf-8');
|
|
625
|
+
const ast = parseFileToAST(filePath, content);
|
|
626
|
+
const imports = [];
|
|
627
|
+
traverse(ast, {
|
|
628
|
+
ImportDeclaration(path) {
|
|
629
|
+
const node = path.node;
|
|
630
|
+
const imported = [];
|
|
631
|
+
node.specifiers.forEach(spec => {
|
|
632
|
+
if (spec.type === 'ImportDefaultSpecifier') {
|
|
633
|
+
imported.push({ name: spec.local.name, type: 'default' });
|
|
634
|
+
}
|
|
635
|
+
else if (spec.type === 'ImportSpecifier') {
|
|
636
|
+
imported.push({
|
|
637
|
+
name: spec.local.name,
|
|
638
|
+
imported: spec.imported.name,
|
|
639
|
+
type: 'named'
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
else if (spec.type === 'ImportNamespaceSpecifier') {
|
|
643
|
+
imported.push({ name: spec.local.name, type: 'namespace' });
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
imports.push({
|
|
647
|
+
source: node.source.value,
|
|
648
|
+
imported,
|
|
649
|
+
line: node.loc.start.line
|
|
650
|
+
});
|
|
651
|
+
},
|
|
652
|
+
// Handle require statements
|
|
653
|
+
CallExpression(path) {
|
|
654
|
+
if (path.node.callee.name === 'require' &&
|
|
655
|
+
path.node.arguments[0]?.type === 'StringLiteral') {
|
|
656
|
+
const parent = path.parent;
|
|
657
|
+
let variableName = null;
|
|
658
|
+
if (parent.type === 'VariableDeclarator') {
|
|
659
|
+
variableName = parent.id.name;
|
|
660
|
+
}
|
|
661
|
+
imports.push({
|
|
662
|
+
source: path.node.arguments[0].value,
|
|
663
|
+
imported: variableName ? [{ name: variableName, type: 'require' }] : [],
|
|
664
|
+
type: 'require',
|
|
665
|
+
line: path.node.loc.start.line
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
return { success: true, imports, filePath };
|
|
671
|
+
}
|
|
672
|
+
catch (error) {
|
|
673
|
+
if (globals_1.g.globalIndexer && (error.code === 'ENOENT' || error.message.includes('no such file'))) {
|
|
674
|
+
const indexData = globals_1.g.globalIndexer.index;
|
|
675
|
+
if (indexData && indexData.files) {
|
|
676
|
+
const match = (0, pathResolver_1.findBestMatchInIndex)(filePath, indexData.files);
|
|
677
|
+
if (match) {
|
|
678
|
+
console.log(` 💡 Did you mean: ${match.suggestion}?`);
|
|
679
|
+
return {
|
|
680
|
+
success: false,
|
|
681
|
+
error: `File not found: ${filePath}`,
|
|
682
|
+
suggestion: match.suggestion,
|
|
683
|
+
allMatches: match.allMatches.length > 1 ? match.allMatches : undefined
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return { success: false, error: error.message };
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
function getTypeDefinitions(filePath) {
|
|
692
|
+
try {
|
|
693
|
+
const content = fsSync.readFileSync(filePath, 'utf-8');
|
|
694
|
+
const ast = parseFileToAST(filePath, content);
|
|
695
|
+
const lines = content.split('\n');
|
|
696
|
+
const types = [];
|
|
697
|
+
traverse(ast, {
|
|
698
|
+
TSTypeAliasDeclaration(path) {
|
|
699
|
+
const node = path.node;
|
|
700
|
+
const startLine = node.loc.start.line - 1;
|
|
701
|
+
const endLine = node.loc.end.line - 1;
|
|
702
|
+
types.push({
|
|
703
|
+
name: node.id.name,
|
|
704
|
+
kind: 'type',
|
|
705
|
+
code: lines.slice(startLine, endLine + 1).join('\n'),
|
|
706
|
+
line: startLine + 1,
|
|
707
|
+
exported: path.parent.type === 'ExportNamedDeclaration'
|
|
708
|
+
});
|
|
709
|
+
},
|
|
710
|
+
TSInterfaceDeclaration(path) {
|
|
711
|
+
const node = path.node;
|
|
712
|
+
const startLine = node.loc.start.line - 1;
|
|
713
|
+
const endLine = node.loc.end.line - 1;
|
|
714
|
+
const properties = [];
|
|
715
|
+
if (node.body && node.body.body) {
|
|
716
|
+
node.body.body.forEach(member => {
|
|
717
|
+
if (member.type === 'TSPropertySignature') {
|
|
718
|
+
properties.push({
|
|
719
|
+
name: member.key.name,
|
|
720
|
+
type: member.typeAnnotation ?
|
|
721
|
+
getTypeAnnotation(member.typeAnnotation) : null,
|
|
722
|
+
optional: member.optional
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
types.push({
|
|
728
|
+
name: node.id.name,
|
|
729
|
+
kind: 'interface',
|
|
730
|
+
code: lines.slice(startLine, endLine + 1).join('\n'),
|
|
731
|
+
properties,
|
|
732
|
+
line: startLine + 1,
|
|
733
|
+
exported: path.parent.type === 'ExportNamedDeclaration'
|
|
734
|
+
});
|
|
735
|
+
},
|
|
736
|
+
TSEnumDeclaration(path) {
|
|
737
|
+
const node = path.node;
|
|
738
|
+
const startLine = node.loc.start.line - 1;
|
|
739
|
+
const endLine = node.loc.end.line - 1;
|
|
740
|
+
types.push({
|
|
741
|
+
name: node.id.name,
|
|
742
|
+
kind: 'enum',
|
|
743
|
+
code: lines.slice(startLine, endLine + 1).join('\n'),
|
|
744
|
+
line: startLine + 1,
|
|
745
|
+
exported: path.parent.type === 'ExportNamedDeclaration'
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
return { success: true, types, filePath };
|
|
750
|
+
}
|
|
751
|
+
catch (error) {
|
|
752
|
+
if (globals_1.g.globalIndexer && (error.code === 'ENOENT' || error.message.includes('no such file'))) {
|
|
753
|
+
const indexData = globals_1.g.globalIndexer.index;
|
|
754
|
+
if (indexData && indexData.files) {
|
|
755
|
+
const match = (0, pathResolver_1.findBestMatchInIndex)(filePath, indexData.files);
|
|
756
|
+
if (match) {
|
|
757
|
+
console.log(` 💡 Did you mean: ${match.suggestion}?`);
|
|
758
|
+
return {
|
|
759
|
+
success: false,
|
|
760
|
+
error: `File not found: ${filePath}`,
|
|
761
|
+
suggestion: match.suggestion,
|
|
762
|
+
allMatches: match.allMatches.length > 1 ? match.allMatches : undefined
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return { success: false, error: error.message };
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
function getFilePreamble(filePath) {
|
|
771
|
+
const extractPreamble = (targetPath) => {
|
|
772
|
+
try {
|
|
773
|
+
const content = fsSync.readFileSync(targetPath, 'utf-8');
|
|
774
|
+
const ast = parseFileToAST(targetPath, content);
|
|
775
|
+
const lines = content.split('\n');
|
|
776
|
+
const imports = [];
|
|
777
|
+
const mocks = [];
|
|
778
|
+
const setupBlocks = [];
|
|
779
|
+
const topLevelVariables = [];
|
|
780
|
+
if (ast.program && ast.program.body) {
|
|
781
|
+
for (const statement of ast.program.body) {
|
|
782
|
+
const startLine = statement.loc?.start.line || 0;
|
|
783
|
+
const endLine = statement.loc?.end.line || 0;
|
|
784
|
+
const code = lines.slice(startLine - 1, endLine).join('\n');
|
|
785
|
+
if (statement.type === 'ImportDeclaration') {
|
|
786
|
+
imports.push({
|
|
787
|
+
code,
|
|
788
|
+
startLine,
|
|
789
|
+
endLine,
|
|
790
|
+
source: statement.source?.value || ''
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
else if (statement.type === 'VariableDeclaration') {
|
|
794
|
+
statement.declarations.forEach((decl) => {
|
|
795
|
+
topLevelVariables.push({
|
|
796
|
+
code,
|
|
797
|
+
startLine,
|
|
798
|
+
endLine,
|
|
799
|
+
name: decl.id?.name || 'unknown',
|
|
800
|
+
kind: statement.kind
|
|
801
|
+
});
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
else if (statement.type === 'ExpressionStatement' && statement.expression?.type === 'CallExpression') {
|
|
805
|
+
const callExpr = statement.expression;
|
|
806
|
+
const callee = callExpr.callee;
|
|
807
|
+
if (callee.type === 'MemberExpression' &&
|
|
808
|
+
(callee.object?.name === 'vi' || callee.object?.name === 'jest') &&
|
|
809
|
+
callee.property?.name === 'mock') {
|
|
810
|
+
const moduleName = callExpr.arguments[0]?.value || 'unknown';
|
|
811
|
+
let isVirtual = false;
|
|
812
|
+
if (callExpr.arguments[1]?.type === 'ObjectExpression') {
|
|
813
|
+
const props = callExpr.arguments[1].properties || [];
|
|
814
|
+
isVirtual = props.some((prop) => prop.key?.name === 'virtual' && prop.value?.value === true);
|
|
815
|
+
}
|
|
816
|
+
mocks.push({
|
|
817
|
+
code,
|
|
818
|
+
startLine,
|
|
819
|
+
endLine,
|
|
820
|
+
moduleName,
|
|
821
|
+
isVirtual
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
else if (callee.type === 'Identifier' &&
|
|
825
|
+
['beforeAll', 'beforeEach', 'afterAll', 'afterEach'].includes(callee.name)) {
|
|
826
|
+
setupBlocks.push({
|
|
827
|
+
code,
|
|
828
|
+
startLine,
|
|
829
|
+
endLine,
|
|
830
|
+
type: callee.name
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
// Handle require statements
|
|
835
|
+
else if (statement.type === 'VariableDeclaration') {
|
|
836
|
+
statement.declarations.forEach((decl) => {
|
|
837
|
+
if (decl.init?.type === 'CallExpression' &&
|
|
838
|
+
decl.init.callee?.name === 'require' &&
|
|
839
|
+
decl.init.arguments[0]?.type === 'StringLiteral') {
|
|
840
|
+
imports.push({
|
|
841
|
+
code,
|
|
842
|
+
startLine,
|
|
843
|
+
endLine,
|
|
844
|
+
source: decl.init.arguments[0].value,
|
|
845
|
+
type: 'require'
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
const allItems = [
|
|
853
|
+
...imports.map(i => ({ ...i, category: 'import' })),
|
|
854
|
+
...mocks.map(m => ({ ...m, category: 'mock' })),
|
|
855
|
+
...setupBlocks.map(s => ({ ...s, category: 'setup' })),
|
|
856
|
+
...topLevelVariables.map(v => ({ ...v, category: 'variable' }))
|
|
857
|
+
].sort((a, b) => a.startLine - b.startLine);
|
|
858
|
+
return {
|
|
859
|
+
success: true,
|
|
860
|
+
filePath: targetPath,
|
|
861
|
+
preamble: {
|
|
862
|
+
imports,
|
|
863
|
+
mocks,
|
|
864
|
+
setupBlocks,
|
|
865
|
+
topLevelVariables
|
|
866
|
+
},
|
|
867
|
+
summary: {
|
|
868
|
+
importCount: imports.length,
|
|
869
|
+
mockCount: mocks.length,
|
|
870
|
+
setupBlockCount: setupBlocks.length,
|
|
871
|
+
variableCount: topLevelVariables.length,
|
|
872
|
+
totalLines: allItems.length > 0 ? allItems[allItems.length - 1].endLine : 0
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
catch (error) {
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
const result = extractPreamble(filePath);
|
|
881
|
+
if (result) {
|
|
882
|
+
return result;
|
|
883
|
+
}
|
|
884
|
+
if (globals_1.g.globalIndexer) {
|
|
885
|
+
const indexData = globals_1.g.globalIndexer.index;
|
|
886
|
+
if (indexData && indexData.files) {
|
|
887
|
+
const match = (0, pathResolver_1.findBestMatchInIndex)(filePath, indexData.files);
|
|
888
|
+
if (match) {
|
|
889
|
+
const retryResult = extractPreamble(match.suggestion);
|
|
890
|
+
if (retryResult) {
|
|
891
|
+
return retryResult;
|
|
892
|
+
}
|
|
893
|
+
return {
|
|
894
|
+
success: false,
|
|
895
|
+
error: `File not found: ${filePath}`,
|
|
896
|
+
suggestion: match.suggestion,
|
|
897
|
+
allMatches: match.allMatches.length > 1 ? match.allMatches : undefined
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
return {
|
|
903
|
+
success: false,
|
|
904
|
+
error: `File not found: ${filePath}. Tip: Enable indexing for better file lookup.`
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
function getClassMethods(filePath, className) {
|
|
908
|
+
const searchInFile = (targetPath) => {
|
|
909
|
+
try {
|
|
910
|
+
const content = fsSync.readFileSync(targetPath, 'utf-8');
|
|
911
|
+
const ast = parseFileToAST(targetPath, content);
|
|
912
|
+
const lines = content.split('\n');
|
|
913
|
+
let classInfo = null;
|
|
914
|
+
traverse(ast, {
|
|
915
|
+
ClassDeclaration(path) {
|
|
916
|
+
if (path.node.id?.name === className) {
|
|
917
|
+
const methods = [];
|
|
918
|
+
if (path.node.body && path.node.body.body) {
|
|
919
|
+
path.node.body.body.forEach(member => {
|
|
920
|
+
if (member.type === 'ClassMethod' || member.type === 'MethodDefinition') {
|
|
921
|
+
const startLine = member.loc.start.line - 1;
|
|
922
|
+
const endLine = member.loc.end.line - 1;
|
|
923
|
+
methods.push({
|
|
924
|
+
name: member.key.name,
|
|
925
|
+
kind: member.kind,
|
|
926
|
+
static: member.static,
|
|
927
|
+
async: member.async,
|
|
928
|
+
params: member.params ? member.params.map(p => extractParamInfo(p)) : [],
|
|
929
|
+
returnType: member.returnType ? getTypeAnnotation(member.returnType) : null,
|
|
930
|
+
code: lines.slice(startLine, endLine + 1).join('\n'),
|
|
931
|
+
line: startLine + 1
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
classInfo = {
|
|
937
|
+
name: className,
|
|
938
|
+
methods,
|
|
939
|
+
superClass: path.node.superClass?.name,
|
|
940
|
+
filePath: targetPath
|
|
941
|
+
};
|
|
942
|
+
path.stop();
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
if (classInfo) {
|
|
947
|
+
return { success: true, ...classInfo };
|
|
948
|
+
}
|
|
949
|
+
return null;
|
|
950
|
+
}
|
|
951
|
+
catch (error) {
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
try {
|
|
956
|
+
const result = searchInFile(filePath);
|
|
957
|
+
if (result) {
|
|
958
|
+
return result;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
catch (error) {
|
|
962
|
+
// File not found or other error - continue to fallback
|
|
963
|
+
}
|
|
964
|
+
if (globals_1.g.globalIndexer) {
|
|
965
|
+
const indexData = globals_1.g.globalIndexer.index;
|
|
966
|
+
if (indexData && indexData.files) {
|
|
967
|
+
for (const [indexedFilePath, fileData] of Object.entries(indexData.files)) {
|
|
968
|
+
const analysis = fileData.analysis;
|
|
969
|
+
const hasClass = analysis.classes?.some((cls) => cls.name === className);
|
|
970
|
+
if (hasClass) {
|
|
971
|
+
const result = searchInFile(indexedFilePath);
|
|
972
|
+
if (result) {
|
|
973
|
+
return result;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
return {
|
|
979
|
+
success: false,
|
|
980
|
+
error: `Class ${className} not found in ${filePath} or any indexed files`
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
return {
|
|
984
|
+
success: false,
|
|
985
|
+
error: `Class ${className} not found in ${filePath}. Tip: Enable indexing for better class lookup across files.`
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
//# sourceMappingURL=ast.js.map
|