@aaswe/codebase-ai
Version:
AI-Assisted Software Engineering (AASWE) - Rich codebase context for IDE LLMs
692 lines • 26 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RustAnalyzer = void 0;
const BaseAnalyzer_1 = require("./BaseAnalyzer");
/**
* Rust AST Analyzer
* Analyzes Rust source code using regex-based parsing
* Handles Rust-specific constructs like traits, impls, modules, and ownership
*/
class RustAnalyzer extends BaseAnalyzer_1.BaseAnalyzer {
constructor() {
super('rust');
}
/**
* Parse Rust source code (regex-based approach)
*/
async parseAST(content, context) {
return {
content,
lines: content.split('\n'),
filePath: context.filePath
};
}
/**
* Extract AST nodes from Rust code
*/
async extractNodes(ast, context) {
const nodes = [];
const { content, lines } = ast;
// Parse modules
nodes.push(...this.parseModules(content, lines, context.filePath));
// Parse constants
nodes.push(...this.parseConstants(content, lines, context.filePath));
// Parse static variables
nodes.push(...this.parseStatics(content, lines, context.filePath));
return nodes;
}
/**
* Extract functions from Rust code
*/
async extractFunctions(ast, context) {
const functions = [];
const { content, lines } = ast;
const functionRegex = /^(\s*)(?:(pub(?:\([^)]*\))?)\s+)?(?:(async)\s+)?(?:(unsafe)\s+)?fn\s+(\w+)(?:<[^>]*>)?\s*\(([^)]*)\)(?:\s*->\s*([^{]+?))?\s*\{/gm;
let match;
while ((match = functionRegex.exec(content)) !== null) {
const [fullMatch, , visibility, isAsync, , name, params, returnType] = match;
const startLine = this.getLineNumber(content, match.index);
// Find function end
const endLine = this.findBlockEnd(content, match.index + fullMatch.length - 1, lines);
// Parse parameters
const parameters = this.parseRustParameters(params);
// Determine visibility
const isPublic = visibility?.includes('pub') || false;
// Calculate complexity
const functionCode = lines.slice(startLine - 1, endLine).join('\n');
const complexity = await this.calculateFunctionComplexity(functionCode);
functions.push({
id: this.generateId(),
name,
parameters,
returnType: returnType?.trim() || 'void',
complexity: complexity.cyclomaticComplexity,
startLine,
endLine,
filePath: context.filePath,
isAsync: !!isAsync,
isExported: isPublic,
visibility: isPublic ? 'public' : 'private',
dependencies: [],
calls: this.extractFunctionCalls(functionCode)
});
}
return functions;
}
/**
* Extract classes (structs, enums, traits) from Rust code
*/
async extractClasses(ast, context) {
const classes = [];
const { content, lines } = ast;
// Parse structs
classes.push(...await this.parseStructs(content, lines, context.filePath));
// Parse enums
classes.push(...await this.parseEnums(content, lines, context.filePath));
// Parse traits
classes.push(...await this.parseTraits(content, lines, context.filePath));
return classes;
}
/**
* Extract imports (use statements) from Rust code
*/
async extractImports(ast, context) {
const imports = [];
const { content } = ast;
const useRegex = /^(?:pub\s+)?use\s+([^;]+);/gm;
let match;
while ((match = useRegex.exec(content)) !== null) {
const lineNumber = this.getLineNumber(content, match.index);
const usePath = match[1].trim();
// const isPublic = match[0].includes('pub');
// Parse different use patterns
let modulePath = '';
let importedItems = [];
if (usePath.includes('{')) {
// use std::collections::{HashMap, HashSet};
const pathMatch = usePath.match(/^([^{]+)\{([^}]+)\}/);
if (pathMatch) {
modulePath = pathMatch[1].replace(/::$/, '');
importedItems = pathMatch[2].split(',').map(item => item.trim());
}
}
else if (usePath.includes('::*')) {
// use std::collections::*;
modulePath = usePath.replace(/::?\*$/, '');
importedItems = ['*'];
}
else if (usePath.includes(' as ')) {
// use std::collections::HashMap as Map;
const asMatch = usePath.match(/^(.+)\s+as\s+(\w+)$/);
if (asMatch) {
modulePath = asMatch[1];
importedItems = [asMatch[2]];
}
}
else {
// use std::collections::HashMap;
const parts = usePath.split('::');
if (parts.length > 1) {
modulePath = parts.slice(0, -1).join('::');
importedItems = [parts[parts.length - 1]];
}
else {
modulePath = usePath;
importedItems = [usePath];
}
}
imports.push({
id: this.generateId(),
source: modulePath,
imports: importedItems.map(item => ({
name: item,
alias: undefined,
isDefault: false,
isNamespace: item === '*'
})),
filePath: context.filePath,
startLine: lineNumber,
endLine: lineNumber
});
}
return imports;
}
/**
* Extract exports from Rust code
*/
async extractExports(ast, context) {
const exports = [];
const { content } = ast;
// In Rust, exports are determined by pub visibility
const pubRegex = /^(\s*)pub\s+(?:(fn|struct|enum|trait|const|static|mod)\s+(\w+)|use\s+([^;]+);)/gm;
let match;
while ((match = pubRegex.exec(content)) !== null) {
const lineNumber = this.getLineNumber(content, match.index);
const itemType = match[2];
const name = match[3] || match[4] || 'unknown';
let exportType = 'variable';
switch (itemType) {
case 'fn':
exportType = 'function';
break;
case 'struct':
case 'enum':
exportType = 'class';
break;
case 'trait':
exportType = 'interface';
break;
case 'const':
case 'static':
exportType = 'variable';
break;
default:
exportType = 'type';
}
exports.push({
id: this.generateId(),
name,
type: exportType,
isDefault: false,
filePath: context.filePath,
startLine: lineNumber,
endLine: lineNumber
});
}
return exports;
}
/**
* Extract dependencies from Rust code
*/
async extractDependencies(ast, _context) {
const dependencies = new Set();
const { content } = ast;
// Extract from use statements
const useRegex = /^use\s+([^;]+);/gm;
let match;
while ((match = useRegex.exec(content)) !== null) {
const usePath = match[1].trim();
const rootModule = usePath.split('::')[0];
if (rootModule && !rootModule.startsWith('self') && !rootModule.startsWith('super')) {
dependencies.add(rootModule);
}
}
return Array.from(dependencies);
}
/**
* Calculate complexity metrics for Rust code
*/
async calculateComplexity(ast, _context) {
const { content, lines } = ast;
const linesOfCode = lines.filter((line) => line.trim() && !line.trim().startsWith('//')).length;
let cyclomaticComplexity = 1; // Base complexity
let cognitiveComplexity = 0;
// Rust-specific complexity patterns
const complexityPatterns = [
/\bif\b/g, // if statements
/\belse\s+if\b/g, // else if
/\bwhile\b/g, // while loops
/\bfor\b/g, // for loops
/\bloop\b/g, // infinite loops
/\bmatch\b/g, // match expressions
/\b=>\b/g, // match arms (subtract 1 since match adds 1)
/\?\s*;/g, // error propagation
/\bunwrap\(\)/g, // unwrap calls
/\bexpect\(/g, // expect calls
/&&|\|\|/g, // logical operators
];
complexityPatterns.forEach((pattern, index) => {
const matches = content.match(pattern) || [];
if (index === 6) { // match arms
cyclomaticComplexity += Math.max(0, matches.length - 1);
cognitiveComplexity += matches.length;
}
else {
cyclomaticComplexity += matches.length;
cognitiveComplexity += matches.length;
}
});
// Additional cognitive complexity for nesting
const nestingPatterns = [
/\bif\b.*\{[\s\S]*?\bif\b/g,
/\bfor\b.*\{[\s\S]*?\bfor\b/g,
/\bwhile\b.*\{[\s\S]*?\bwhile\b/g,
/\bmatch\b.*\{[\s\S]*?\bmatch\b/g,
];
nestingPatterns.forEach(pattern => {
const matches = content.match(pattern) || [];
cognitiveComplexity += matches.length * 2; // Nesting penalty
});
// Calculate maintainability index (simplified)
const maintainabilityIndex = Math.max(0, 171 - 5.2 * Math.log(linesOfCode) - 0.23 * cyclomaticComplexity);
// Calculate technical debt (simplified)
const technicalDebt = Math.max(0, cyclomaticComplexity - 10) * 0.5;
return {
cyclomaticComplexity,
cognitiveComplexity,
linesOfCode,
maintainabilityIndex: Math.round(maintainabilityIndex),
technicalDebt: Math.round(technicalDebt)
};
}
/**
* Get parser configuration for Rust
*/
getParserConfig() {
return {
language: 'rust',
sourceType: 'module',
allowImportExportEverywhere: true,
allowReturnOutsideFunction: false,
strictMode: true,
plugins: []
};
}
// Helper methods
/**
* Get line number from character index
*/
getLineNumber(content, index) {
return content.substring(0, index).split('\n').length;
}
/**
* Find the end of a code block
*/
findBlockEnd(content, startIndex, _lines) {
let braceCount = 1;
let currentIndex = startIndex + 1;
while (currentIndex < content.length && braceCount > 0) {
const char = content[currentIndex];
if (char === '{') {
braceCount++;
}
else if (char === '}') {
braceCount--;
}
currentIndex++;
}
return this.getLineNumber(content, currentIndex - 1);
}
/**
* Parse Rust modules
*/
parseModules(content, _lines, filePath) {
const nodes = [];
const moduleRegex = /^(?:pub\s+)?mod\s+(\w+)(?:\s*\{|\s*;)/gm;
let match;
while ((match = moduleRegex.exec(content)) !== null) {
const lineNumber = this.getLineNumber(content, match.index);
const isPublic = match[0].includes('pub');
nodes.push({
id: this.generateId(),
type: 'module',
name: match[1],
startLine: lineNumber,
endLine: lineNumber,
startColumn: 0,
endColumn: match[0].length,
filePath,
parent: undefined,
children: [],
properties: {
isPublic,
language: 'rust'
}
});
}
return nodes;
}
/**
* Parse Rust constants
*/
parseConstants(content, _lines, filePath) {
const constants = [];
const constRegex = /^(\s*)(?:(pub(?:\([^)]*\))?)\s+)?const\s+(\w+)\s*:\s*([^=]+)\s*=\s*([^;]+);/gm;
let match;
while ((match = constRegex.exec(content)) !== null) {
const [fullMatch, , visibility, name, type, value] = match;
const lineNumber = this.getLineNumber(content, match.index);
const isPublic = visibility?.includes('pub') || false;
constants.push({
id: this.generateId(),
type: 'constant',
name,
startLine: lineNumber,
endLine: lineNumber,
startColumn: 0,
endColumn: fullMatch.length,
filePath,
parent: undefined,
children: [],
properties: {
isPublic,
isConst: true,
dataType: type.trim(),
value: value.trim(),
visibility: visibility || 'private',
language: 'rust'
}
});
}
return constants;
}
/**
* Parse Rust static variables
*/
parseStatics(content, _lines, filePath) {
const statics = [];
const staticRegex = /^(\s*)(?:(pub(?:\([^)]*\))?)\s+)?static\s+(?:(mut)\s+)?(\w+)\s*:\s*([^=]+)\s*=\s*([^;]+);/gm;
let match;
while ((match = staticRegex.exec(content)) !== null) {
const [fullMatch, , visibility, isMut, name, type, value] = match;
const lineNumber = this.getLineNumber(content, match.index);
const isPublic = visibility?.includes('pub') || false;
statics.push({
id: this.generateId(),
type: 'static',
name,
startLine: lineNumber,
endLine: lineNumber,
startColumn: 0,
endColumn: fullMatch.length,
filePath,
parent: undefined,
children: [],
properties: {
isPublic,
isStatic: true,
isMutable: !!isMut,
dataType: type.trim(),
value: value.trim(),
visibility: visibility || 'private',
language: 'rust'
}
});
}
return statics;
}
/**
* Parse Rust function parameters
*/
parseRustParameters(params) {
if (!params.trim())
return [];
return params.split(',').map(param => {
const trimmed = param.trim();
if (!trimmed)
return null;
// Handle self parameter
if (trimmed === 'self' || trimmed === '&self' || trimmed === '&mut self') {
return { name: 'self', type: 'Self', optional: false };
}
// Handle regular parameters: name: type
const colonIndex = trimmed.indexOf(':');
if (colonIndex !== -1) {
const name = trimmed.substring(0, colonIndex).trim();
const type = trimmed.substring(colonIndex + 1).trim();
return { name, type, optional: false };
}
return { name: trimmed, type: 'unknown', optional: false };
}).filter(p => p !== null);
}
/**
* Parse Rust structs
*/
async parseStructs(content, lines, filePath) {
const structs = [];
const structRegex = /^(\s*)(?:(pub(?:\([^)]*\))?)\s+)?struct\s+(\w+)(?:<[^>]*>)?\s*(?:\{|;|\()/gm;
let match;
while ((match = structRegex.exec(content)) !== null) {
const [fullMatch, , visibility, name] = match;
const startLine = this.getLineNumber(content, match.index);
const isPublic = visibility?.includes('pub') || false;
let endLine = startLine;
let properties = [];
// Handle different struct types
if (fullMatch.endsWith('{')) {
// Regular struct with fields
endLine = this.findBlockEnd(content, match.index + fullMatch.length - 1, lines);
const structCode = lines.slice(startLine - 1, endLine).join('\n');
properties = this.parseStructFields(structCode);
}
else if (fullMatch.endsWith('(')) {
// Tuple struct
const tupleEnd = content.indexOf(')', match.index + fullMatch.length);
if (tupleEnd !== -1) {
endLine = this.getLineNumber(content, tupleEnd);
const tupleContent = content.substring(match.index + fullMatch.length, tupleEnd);
properties = this.parseTupleFields(tupleContent);
}
}
// Unit struct (ends with ;) - no fields
structs.push({
id: this.generateId(),
name,
methods: [],
properties,
extends: undefined,
implements: [],
startLine,
endLine,
filePath,
isExported: isPublic,
visibility: isPublic ? 'public' : 'private',
isAbstract: false
});
}
return structs;
}
/**
* Parse Rust enums
*/
async parseEnums(content, lines, filePath) {
const enums = [];
const enumRegex = /^(\s*)(?:(pub(?:\([^)]*\))?)\s+)?enum\s+(\w+)(?:<[^>]*>)?\s*\{/gm;
let match;
while ((match = enumRegex.exec(content)) !== null) {
const [fullMatch, , visibility, name] = match;
const startLine = this.getLineNumber(content, match.index);
const endLine = this.findBlockEnd(content, match.index + fullMatch.length - 1, lines);
const isPublic = visibility?.includes('pub') || false;
// Parse enum variants
const enumCode = lines.slice(startLine - 1, endLine).join('\n');
const properties = this.parseEnumVariants(enumCode);
enums.push({
id: this.generateId(),
name,
methods: [],
properties,
extends: undefined,
implements: [],
startLine,
endLine,
filePath,
isExported: isPublic,
visibility: isPublic ? 'public' : 'private',
isAbstract: false
});
}
return enums;
}
/**
* Parse Rust traits
*/
async parseTraits(content, lines, filePath) {
const traits = [];
const traitRegex = /^(\s*)(?:(pub(?:\([^)]*\))?)\s+)?trait\s+(\w+)(?:<[^>]*>)?(?:\s*:\s*([^{]+))?\s*\{/gm;
let match;
while ((match = traitRegex.exec(content)) !== null) {
const [fullMatch, , visibility, name, bounds] = match;
const startLine = this.getLineNumber(content, match.index);
const endLine = this.findBlockEnd(content, match.index + fullMatch.length - 1, lines);
const isPublic = visibility?.includes('pub') || false;
// Parse trait methods
const traitCode = lines.slice(startLine - 1, endLine).join('\n');
const methods = await this.parseTraitMethods(traitCode, filePath);
// Parse supertraits
const supertraits = bounds ? bounds.split('+').map(b => b.trim()) : [];
traits.push({
id: this.generateId(),
name,
methods,
properties: [],
extends: undefined,
implements: supertraits,
startLine,
endLine,
filePath,
isExported: isPublic,
visibility: isPublic ? 'public' : 'private',
isAbstract: true
});
}
return traits;
}
/**
* Parse struct fields
*/
parseStructFields(structCode) {
const fields = [];
const fieldRegex = /^\s*(?:(pub(?:\([^)]*\))?)\s+)?(\w+)\s*:\s*([^,\n}]+)/gm;
let match;
while ((match = fieldRegex.exec(structCode)) !== null) {
const [, visibility, name, type] = match;
const isPublic = visibility?.includes('pub') || false;
fields.push({
name,
type: type.trim(),
visibility: isPublic ? 'public' : 'private',
isStatic: false,
isReadonly: false,
defaultValue: undefined
});
}
return fields;
}
/**
* Parse tuple struct fields
*/
parseTupleFields(tupleContent) {
const fields = [];
const types = tupleContent.split(',').map(t => t.trim()).filter(t => t);
types.forEach((type, index) => {
const isPublic = type.startsWith('pub ');
const cleanType = isPublic ? type.substring(4) : type;
fields.push({
name: `field_${index}`,
type: cleanType,
visibility: isPublic ? 'public' : 'private',
isStatic: false,
isReadonly: false,
defaultValue: undefined
});
});
return fields;
}
/**
* Parse enum variants
*/
parseEnumVariants(enumCode) {
const variants = [];
const variantRegex = /^\s*(\w+)(?:\([^)]*\)|\s*\{[^}]*\})?/gm;
let match;
while ((match = variantRegex.exec(enumCode)) !== null) {
if (match[1] !== 'enum') { // Skip the enum keyword itself
variants.push({
name: match[1],
type: 'variant',
visibility: 'public',
isStatic: false,
isReadonly: true,
defaultValue: undefined
});
}
}
return variants;
}
/**
* Parse trait methods
*/
async parseTraitMethods(traitCode, filePath) {
const methods = [];
const methodRegex = /^\s*fn\s+(\w+)(?:<[^>]*>)?\s*\(([^)]*)\)(?:\s*->\s*([^{;]+))?(?:\s*\{|\s*;)/gm;
let match;
while ((match = methodRegex.exec(traitCode)) !== null) {
const [, name, params, returnType] = match;
const startLine = this.getLineNumber(traitCode, match.index);
methods.push({
id: this.generateId(),
name,
parameters: this.parseRustParameters(params),
returnType: returnType?.trim() || 'void',
complexity: 1,
startLine,
endLine: startLine,
filePath,
isAsync: false,
isExported: true,
visibility: 'public',
dependencies: [],
calls: []
});
}
return methods;
}
/**
* Extract function calls from code
*/
extractFunctionCalls(code) {
const calls = [];
const callRegex = /(\w+)\s*\(/g;
let match;
while ((match = callRegex.exec(code)) !== null) {
const functionName = match[1];
if (functionName && !['if', 'while', 'for', 'match', 'loop'].includes(functionName)) {
calls.push(functionName);
}
}
return [...new Set(calls)]; // Remove duplicates
}
/**
* Calculate complexity for a single function
*/
async calculateFunctionComplexity(code) {
let cyclomaticComplexity = 1; // Base complexity
let cognitiveComplexity = 0;
// Rust-specific complexity patterns
const complexityPatterns = [
/\bif\b/g, // if statements
/\belse\s+if\b/g, // else if
/\bwhile\b/g, // while loops
/\bfor\b/g, // for loops
/\bloop\b/g, // infinite loops
/\bmatch\b/g, // match expressions
/\b=>\b/g, // match arms (subtract 1 since match adds 1)
/\?\s*;/g, // error propagation
/&&|\|\|/g, // logical operators
];
complexityPatterns.forEach((pattern, index) => {
const matches = code.match(pattern) || [];
if (index === 6) { // match arms
cyclomaticComplexity += Math.max(0, matches.length - 1);
cognitiveComplexity += matches.length;
}
else {
cyclomaticComplexity += matches.length;
cognitiveComplexity += matches.length;
}
});
const linesOfCode = code.split('\n').filter(line => line.trim() && !line.trim().startsWith('//')).length;
const maintainabilityIndex = Math.max(0, 171 - 5.2 * Math.log(linesOfCode) - 0.23 * cyclomaticComplexity);
const technicalDebt = Math.max(0, cyclomaticComplexity - 10) * 0.5;
return {
cyclomaticComplexity,
cognitiveComplexity,
linesOfCode,
maintainabilityIndex: Math.round(maintainabilityIndex),
technicalDebt: Math.round(technicalDebt)
};
}
}
exports.RustAnalyzer = RustAnalyzer;
//# sourceMappingURL=RustAnalyzer.js.map