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.
Files changed (50) hide show
  1. package/README.md +157 -1034
  2. package/dist/ai.d.ts +8 -0
  3. package/dist/ai.d.ts.map +1 -0
  4. package/dist/ai.js +332 -0
  5. package/dist/ai.js.map +1 -0
  6. package/dist/ast.d.ts +8 -0
  7. package/dist/ast.d.ts.map +1 -0
  8. package/dist/ast.js +988 -0
  9. package/dist/ast.js.map +1 -0
  10. package/dist/config.d.ts +4 -0
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +4 -0
  13. package/dist/config.js.map +1 -1
  14. package/dist/git.d.ts +18 -0
  15. package/dist/git.d.ts.map +1 -0
  16. package/dist/git.js +208 -0
  17. package/dist/git.js.map +1 -0
  18. package/dist/globals.d.ts +24 -0
  19. package/dist/globals.d.ts.map +1 -0
  20. package/dist/globals.js +40 -0
  21. package/dist/globals.js.map +1 -0
  22. package/dist/index.d.ts +9 -54
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +85 -5434
  25. package/dist/index.js.map +1 -1
  26. package/dist/pathResolver.d.ts +12 -0
  27. package/dist/pathResolver.d.ts.map +1 -0
  28. package/dist/pathResolver.js +44 -0
  29. package/dist/pathResolver.js.map +1 -0
  30. package/dist/reviewer.d.ts +13 -0
  31. package/dist/reviewer.d.ts.map +1 -0
  32. package/dist/reviewer.js +402 -0
  33. package/dist/reviewer.js.map +1 -0
  34. package/dist/testGenerator.d.ts +24 -0
  35. package/dist/testGenerator.d.ts.map +1 -0
  36. package/dist/testGenerator.js +1107 -0
  37. package/dist/testGenerator.js.map +1 -0
  38. package/dist/toolDefinitions.d.ts +6 -0
  39. package/dist/toolDefinitions.d.ts.map +1 -0
  40. package/dist/toolDefinitions.js +370 -0
  41. package/dist/toolDefinitions.js.map +1 -0
  42. package/dist/toolHandlers.d.ts +76 -0
  43. package/dist/toolHandlers.d.ts.map +1 -0
  44. package/dist/toolHandlers.js +1430 -0
  45. package/dist/toolHandlers.js.map +1 -0
  46. package/dist/types.d.ts +74 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +3 -0
  49. package/dist/types.js.map +1 -0
  50. 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