ai-test-generator
Version:
AI-powered test generator for JavaScript and TypeScript codebases using Google Gemini
243 lines (242 loc) • 10 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypeScriptParser = void 0;
const ts = __importStar(require("typescript"));
class TypeScriptParser {
/**
* Parses TypeScript code and extracts components that can be tested
* @param code The source code to parse
* @param filePath The path to the source file
* @returns List of components found in the source
*/
async parse(code, filePath) {
const components = [];
// Create source file
const sourceFile = ts.createSourceFile(filePath, code, ts.ScriptTarget.Latest, true);
// Track the current node and its hierarchy
const parentChain = [];
// Visit nodes recursively
const visit = (node) => {
parentChain.push(node);
switch (node.kind) {
case ts.SyntaxKind.FunctionDeclaration:
this.processFunctionDeclaration(node, sourceFile, components, code);
break;
case ts.SyntaxKind.ClassDeclaration:
this.processClassDeclaration(node, sourceFile, components, code);
break;
case ts.SyntaxKind.MethodDeclaration:
this.processMethodDeclaration(node, sourceFile, components, code, this.findParentClass(parentChain));
break;
case ts.SyntaxKind.InterfaceDeclaration:
this.processInterfaceDeclaration(node, sourceFile, components, code);
break;
case ts.SyntaxKind.ArrowFunction:
this.processArrowFunction(node, sourceFile, components, code, parentChain);
break;
}
ts.forEachChild(node, visit);
parentChain.pop();
};
// Start visiting from the root
visit(sourceFile);
return components;
}
/**
* Process function declaration nodes
*/
processFunctionDeclaration(node, sourceFile, components, code) {
if (!node.name)
return; // Skip anonymous functions
const name = node.name.getText(sourceFile);
const { line: startLine } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
const { line: endLine } = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
const nodeText = node.getText(sourceFile);
const complexity = this.calculateComplexity(node);
components.push({
name,
type: 'function',
startLine: startLine + 1,
endLine: endLine + 1,
code: nodeText,
needsTest: false, // Will be determined by the analyzer
complexity
});
}
/**
* Process class declaration nodes
*/
processClassDeclaration(node, sourceFile, components, code) {
if (!node.name)
return; // Skip anonymous classes
const name = node.name.getText(sourceFile);
const { line: startLine } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
const { line: endLine } = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
const nodeText = node.getText(sourceFile);
const complexity = this.calculateComplexity(node);
components.push({
name,
type: 'class',
startLine: startLine + 1,
endLine: endLine + 1,
code: nodeText,
needsTest: false, // Will be determined by the analyzer
complexity
});
}
/**
* Process method declaration nodes
*/
processMethodDeclaration(node, sourceFile, components, code, parentClass) {
const methodName = node.name.getText(sourceFile);
let name = methodName;
// Include class name if available
if (parentClass && parentClass.name) {
name = `${parentClass.name.getText(sourceFile)}.${methodName}`;
}
const { line: startLine } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
const { line: endLine } = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
const nodeText = node.getText(sourceFile);
const complexity = this.calculateComplexity(node);
components.push({
name,
type: 'method',
startLine: startLine + 1,
endLine: endLine + 1,
code: nodeText,
needsTest: false, // Will be determined by the analyzer
complexity
});
}
/**
* Process interface declaration nodes
*/
processInterfaceDeclaration(node, sourceFile, components, code) {
const name = node.name.getText(sourceFile);
const { line: startLine } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
const { line: endLine } = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
const nodeText = node.getText(sourceFile);
// Interfaces typically have low complexity as they're just declarations
const complexity = 1;
components.push({
name,
type: 'interface',
startLine: startLine + 1,
endLine: endLine + 1,
code: nodeText,
needsTest: false, // Will be determined by the analyzer
complexity
});
}
/**
* Process arrow function expressions
*/
processArrowFunction(node, sourceFile, components, code, parentChain) {
// Try to get the name from the parent (if it's a variable declaration)
let name = "anonymousFunction";
// Try to extract the name from a variable declaration
const parent = parentChain[parentChain.length - 2];
if (parent && ts.isVariableDeclaration(parent) && parent.name) {
name = parent.name.getText(sourceFile);
}
// Or from property assignment
if (parent && ts.isPropertyAssignment(parent) && parent.name) {
name = parent.name.getText(sourceFile);
}
const { line: startLine } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
const { line: endLine } = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
const nodeText = node.getText(sourceFile);
const complexity = this.calculateComplexity(node);
// Only track non-trivial arrow functions (those with more than a simple return)
if (complexity > 1 || (node.body && ts.isExpressionStatement(node.body.parent))) {
components.push({
name,
type: 'function',
startLine: startLine + 1,
endLine: endLine + 1,
code: nodeText,
needsTest: false, // Will be determined by the analyzer
complexity
});
}
}
/**
* Calculate the cyclomatic complexity of a node
*/
calculateComplexity(node) {
let complexity = 1; // Base complexity
// Count conditions, loops, and other complexity indicators
const incrementComplexity = (node) => {
switch (node.kind) {
// Control flow statements
case ts.SyntaxKind.IfStatement:
case ts.SyntaxKind.ConditionalExpression: // ternary
case ts.SyntaxKind.ForStatement:
case ts.SyntaxKind.ForInStatement:
case ts.SyntaxKind.ForOfStatement:
case ts.SyntaxKind.WhileStatement:
case ts.SyntaxKind.DoStatement:
case ts.SyntaxKind.CaseClause:
case ts.SyntaxKind.CatchClause:
complexity++;
break;
// Logical operators can add complexity
case ts.SyntaxKind.BinaryExpression:
const binary = node;
if (binary.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
binary.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
complexity++;
}
break;
}
ts.forEachChild(node, incrementComplexity);
};
incrementComplexity(node);
return complexity;
}
/**
* Find the parent class of a node in the parent chain
*/
findParentClass(parentChain) {
for (let i = parentChain.length - 1; i >= 0; i--) {
if (ts.isClassDeclaration(parentChain[i])) {
return parentChain[i];
}
}
return undefined;
}
}
exports.TypeScriptParser = TypeScriptParser;