UNPKG

@aaswe/codebase-ai

Version:

AI-Assisted Software Engineering (AASWE) - Rich codebase context for IDE LLMs

644 lines 25.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CppAnalyzer = void 0; const BaseAnalyzer_1 = require("./BaseAnalyzer"); /** * C++ AST Analyzer * Analyzes C++ source code using regex-based parsing * Handles C++ specific constructs like classes, namespaces, templates, and preprocessor directives */ class CppAnalyzer extends BaseAnalyzer_1.BaseAnalyzer { constructor() { super('cpp'); } /** * Parse C++ source code (regex-based approach) */ async parseAST(content, context) { return { content, lines: content.split('\n'), filePath: context.filePath }; } /** * Extract AST nodes from C++ code */ async extractNodes(ast, context) { const nodes = []; const { content, lines } = ast; // Parse namespaces nodes.push(...this.parseNamespaces(content, lines, context.filePath)); // Parse preprocessor directives nodes.push(...this.parsePreprocessorDirectives(content, lines, context.filePath)); // Parse typedefs and using declarations nodes.push(...this.parseTypeDeclarations(content, lines, context.filePath)); // Parse global variables nodes.push(...this.parseGlobalVariables(content, lines, context.filePath)); return nodes; } /** * Extract functions from C++ code */ async extractFunctions(ast, context) { const functions = []; const { content, lines } = ast; // Parse regular functions (including member functions) const functionRegex = /^(\s*)(?:(virtual|static|inline|explicit|friend)\s+)*(?:(template\s*<[^>]*>\s+))?(?:(\w+(?:::\w+)*)\s+)?(\w+)\s*\(([^)]*)\)(?:\s*(const|volatile|noexcept|override|final))*\s*(?::\s*[^{]*)?(?:\s*\{|;)/gm; let match; while ((match = functionRegex.exec(content)) !== null) { const [fullMatch, , modifiers, , returnType, name, params,] = match; // Skip constructors and destructors in this pass (handled in class parsing) if (name.startsWith('~') || (!returnType && !modifiers?.includes('explicit'))) { continue; } const startLine = this.getLineNumber(content, match.index); // Find function end if it has a body let endLine = startLine; if (fullMatch.endsWith('{')) { endLine = this.findBlockEnd(content, match.index + fullMatch.length - 1, lines); } // Parse parameters const parameters = this.parseCppParameters(params); // Determine visibility and other properties // const isStatic = modifiers?.includes('static') || false; // const isVirtual = modifiers?.includes('virtual') || false; // const isInline = modifiers?.includes('inline') || false; // const isConst = qualifiers?.includes('const') || false; // Calculate complexity if function has body let complexity = 1; if (fullMatch.endsWith('{')) { const functionCode = lines.slice(startLine - 1, endLine).join('\n'); const complexityMetrics = await this.calculateFunctionComplexity(functionCode); complexity = complexityMetrics.cyclomaticComplexity; } functions.push({ id: this.generateId(), name, parameters, returnType: returnType?.trim() || 'void', complexity, startLine, endLine, filePath: context.filePath, isAsync: false, // C++ doesn't have native async like JS/TS isExported: true, // Assume exported unless in anonymous namespace visibility: 'public', // Default, will be refined in class context dependencies: [], calls: this.extractFunctionCalls(fullMatch.endsWith('{') ? lines.slice(startLine - 1, endLine).join('\n') : '') }); } return functions; } /** * Extract classes from C++ code */ async extractClasses(ast, context) { const classes = []; const { content, lines } = ast; // Parse classes and structs const classRegex = /^(\s*)(?:(template\s*<[^>]*>\s+))?(class|struct)\s+(?:(\w+)\s+)?(\w+)(?:\s*:\s*([^{]+))?\s*\{/gm; let match; while ((match = classRegex.exec(content)) !== null) { const [fullMatch, , , , , name, inheritance] = match; const startLine = this.getLineNumber(content, match.index); const endLine = this.findBlockEnd(content, match.index + fullMatch.length - 1, lines); // Parse class body const classCode = lines.slice(startLine - 1, endLine).join('\n'); const methods = await this.parseClassMethods(classCode, context.filePath, name); const properties = this.parseClassProperties(classCode); // Parse inheritance const baseClasses = []; if (inheritance) { const inheritanceList = inheritance.split(',').map(i => i.trim()); inheritanceList.forEach(base => { // Remove access specifiers (public, private, protected, virtual) const cleanBase = base.replace(/^(public|private|protected|virtual)\s+/, '').trim(); baseClasses.push(cleanBase); }); } classes.push({ id: this.generateId(), name, methods, properties, extends: baseClasses[0] || undefined, implements: baseClasses.slice(1), // Additional base classes treated as interfaces startLine, endLine, filePath: context.filePath, isExported: true, visibility: 'public', isAbstract: this.isAbstractClass(classCode) }); } return classes; } /** * Extract imports (#include directives) from C++ code */ async extractImports(ast, context) { const imports = []; const { content } = ast; const includeRegex = /^#include\s*([<"]([^>"]+)[>"])/gm; let match; while ((match = includeRegex.exec(content)) !== null) { const lineNumber = this.getLineNumber(content, match.index); const includePath = match[2]; // const isSystemHeader = match[1].startsWith('<'); imports.push({ id: this.generateId(), source: includePath, imports: [{ name: includePath.split('/').pop()?.replace(/\.[^.]*$/, '') || includePath, alias: undefined, isDefault: true, isNamespace: false }], filePath: context.filePath, startLine: lineNumber, endLine: lineNumber }); } return imports; } /** * Extract exports from C++ code */ async extractExports(ast, context) { const exports = []; const { content } = ast; // In C++, exports are typically determined by header files and linkage // For simplicity, we'll consider public class members and global functions as exports // Export global functions const functionRegex = /^(?!.*static)(?:(?:inline|extern)\s+)?(?:\w+(?:::\w+)*\s+)?(\w+)\s*\([^)]*\)\s*(?:\{|;)/gm; let match; while ((match = functionRegex.exec(content)) !== null) { const lineNumber = this.getLineNumber(content, match.index); const name = match[1]; exports.push({ id: this.generateId(), name, type: 'function', isDefault: false, filePath: context.filePath, startLine: lineNumber, endLine: lineNumber }); } // Export classes and structs const classRegex = /^(?:class|struct)\s+(?:\w+\s+)?(\w+)/gm; while ((match = classRegex.exec(content)) !== null) { const lineNumber = this.getLineNumber(content, match.index); const name = match[1]; exports.push({ id: this.generateId(), name, type: 'class', isDefault: false, filePath: context.filePath, startLine: lineNumber, endLine: lineNumber }); } return exports; } /** * Extract dependencies from C++ code */ async extractDependencies(ast, _context) { const dependencies = new Set(); const { content } = ast; // Extract from #include directives const includeRegex = /^#include\s*[<"]([^>"]+)[>"]/gm; let match; while ((match = includeRegex.exec(content)) !== null) { const includePath = match[1]; // Extract the base name without extension const baseName = includePath.split('/').pop()?.replace(/\.[^.]*$/, ''); if (baseName) { dependencies.add(baseName); } } return Array.from(dependencies); } /** * Calculate complexity metrics for C++ code */ async calculateComplexity(ast, _context) { const { content, lines } = ast; const linesOfCode = lines.filter((line) => { const trimmed = line.trim(); return trimmed && !trimmed.startsWith('//') && !trimmed.startsWith('/*') && !trimmed.startsWith('*'); }).length; let cyclomaticComplexity = 1; // Base complexity let cognitiveComplexity = 0; // C++ 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 /\bdo\b/g, // do-while loops /\bswitch\b/g, // switch statements /\bcase\b/g, // case labels /\bcatch\b/g, // exception handling /\bthrow\b/g, // throw statements /&&|\|\|/g, // logical operators /\?.*:/g, // ternary operators ]; complexityPatterns.forEach((pattern, index) => { const matches = content.match(pattern) || []; if (index === 6) { // case labels cyclomaticComplexity += Math.max(0, matches.length - 1); // First case doesn't add complexity 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, /\btry\b.*\{[\s\S]*?\btry\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 C++ */ getParserConfig() { return { language: 'cpp', sourceType: 'module', allowImportExportEverywhere: false, allowReturnOutsideFunction: false, strictMode: false, 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 C++ namespaces */ parseNamespaces(content, _lines, filePath) { const nodes = []; const namespaceRegex = /^(\s*)namespace\s+(\w+)?\s*\{/gm; let match; while ((match = namespaceRegex.exec(content)) !== null) { const lineNumber = this.getLineNumber(content, match.index); const name = match[2] || 'anonymous'; nodes.push({ id: this.generateId(), type: 'namespace', name, startLine: lineNumber, endLine: lineNumber, startColumn: 0, endColumn: match[0].length, filePath, parent: undefined, children: [], properties: { language: 'cpp' } }); } return nodes; } /** * Parse preprocessor directives */ parsePreprocessorDirectives(content, _lines, filePath) { const nodes = []; const directiveRegex = /^#(\w+)(?:\s+(.*))?$/gm; let match; while ((match = directiveRegex.exec(content)) !== null) { const lineNumber = this.getLineNumber(content, match.index); const directive = match[1]; const value = match[2] || ''; // Skip include directives as they're handled separately if (directive === 'include') continue; nodes.push({ id: this.generateId(), type: 'preprocessor', name: `#${directive}`, startLine: lineNumber, endLine: lineNumber, startColumn: 0, endColumn: match[0].length, filePath, parent: undefined, children: [], properties: { directive, value, language: 'cpp' } }); } return nodes; } /** * Parse type declarations (typedef, using) */ parseTypeDeclarations(content, _lines, filePath) { const nodes = []; // Parse typedef const typedefRegex = /^(\s*)typedef\s+(.+?)\s+(\w+)\s*;/gm; let match; while ((match = typedefRegex.exec(content)) !== null) { const lineNumber = this.getLineNumber(content, match.index); const originalType = match[2]; const aliasName = match[3]; nodes.push({ id: this.generateId(), type: 'typedef', name: aliasName, startLine: lineNumber, endLine: lineNumber, startColumn: 0, endColumn: match[0].length, filePath, parent: undefined, children: [], properties: { originalType, language: 'cpp' } }); } // Parse using declarations const usingRegex = /^(\s*)using\s+(\w+)\s*=\s*(.+?)\s*;/gm; while ((match = usingRegex.exec(content)) !== null) { const lineNumber = this.getLineNumber(content, match.index); const aliasName = match[2]; const originalType = match[3]; nodes.push({ id: this.generateId(), type: 'using', name: aliasName, startLine: lineNumber, endLine: lineNumber, startColumn: 0, endColumn: match[0].length, filePath, parent: undefined, children: [], properties: { originalType, language: 'cpp' } }); } return nodes; } /** * Parse global variables */ parseGlobalVariables(content, _lines, filePath) { const nodes = []; const globalVarRegex = /^(\s*)(?:(extern|static|const|constexpr)\s+)*(\w+(?:::\w+)*(?:\s*[*&]+)?)\s+(\w+)(?:\s*=\s*[^;]+)?\s*;/gm; let match; while ((match = globalVarRegex.exec(content)) !== null) { const lineNumber = this.getLineNumber(content, match.index); const modifiers = match[2] || ''; const type = match[3]; const name = match[4]; nodes.push({ id: this.generateId(), type: 'variable', name, startLine: lineNumber, endLine: lineNumber, startColumn: 0, endColumn: match[0].length, filePath, parent: undefined, children: [], properties: { dataType: type, modifiers, isGlobal: true, language: 'cpp' } }); } return nodes; } /** * Parse C++ function parameters */ parseCppParameters(params) { if (!params.trim()) return []; return params.split(',').map(param => { const trimmed = param.trim(); if (!trimmed) return null; // Handle parameter with default value: type name = value const defaultMatch = trimmed.match(/^(.+?)\s+(\w+)\s*=\s*(.+)$/); if (defaultMatch) { return { name: defaultMatch[2], type: defaultMatch[1].trim(), optional: true, defaultValue: defaultMatch[3].trim() }; } // Handle regular parameter: type name const regularMatch = trimmed.match(/^(.+?)\s+(\w+)$/); if (regularMatch) { return { name: regularMatch[2], type: regularMatch[1].trim(), optional: false }; } // Handle type-only parameter (in function declarations) return { name: 'param', type: trimmed, optional: false }; }).filter(p => p !== null); } /** * Parse class methods */ async parseClassMethods(classCode, filePath, className) { const methods = []; const methodRegex = /^(\s*)(public|private|protected)?\s*:?\s*(?:(virtual|static|inline|explicit)\s+)*(?:(\w+(?:::\w+)*)\s+)?(\w+|~\w+)\s*\(([^)]*)\)(?:\s*(const|volatile|noexcept|override|final))*\s*(?::\s*[^{]*)?(?:\s*\{|;)/gm; let match; while ((match = methodRegex.exec(classCode)) !== null) { const [fullMatch, , visibility, , returnType, name, params,] = match; const startLine = this.getLineNumber(classCode, match.index); // Skip if this looks like a class declaration line if (name === className) continue; let endLine = startLine; if (fullMatch.endsWith('{')) { endLine = this.findBlockEnd(classCode, match.index + fullMatch.length - 1, classCode.split('\n')); } const parameters = this.parseCppParameters(params); const isConstructor = name === className; const isDestructor = name.startsWith('~'); // const isStatic = modifiers?.includes('static') || false; // const isVirtual = modifiers?.includes('virtual') || false; let complexity = 1; if (fullMatch.endsWith('{')) { const methodCode = classCode.split('\n').slice(startLine - 1, endLine).join('\n'); const complexityMetrics = await this.calculateFunctionComplexity(methodCode); complexity = complexityMetrics.cyclomaticComplexity; } methods.push({ id: this.generateId(), name, parameters, returnType: isConstructor || isDestructor ? 'void' : (returnType?.trim() || 'void'), complexity, startLine, endLine, filePath, isAsync: false, isExported: visibility !== 'private', visibility: visibility || 'private', dependencies: [], calls: fullMatch.endsWith('{') ? this.extractFunctionCalls(classCode.split('\n').slice(startLine - 1, endLine).join('\n')) : [] }); } return methods; } /** * Parse class properties */ parseClassProperties(classCode) { const properties = []; const propertyRegex = /^(\s*)(public|private|protected)?\s*:?\s*(?:(static|const|mutable)\s+)*(\w+(?:::\w+)*(?:\s*[*&]+)?)\s+(\w+)(?:\s*=\s*[^;]+)?\s*;/gm; let match; while ((match = propertyRegex.exec(classCode)) !== null) { const [, , visibility, modifiers, type, name] = match; const isStatic = modifiers?.includes('static') || false; const isConst = modifiers?.includes('const') || false; // const isMutable = modifiers?.includes('mutable') || false; properties.push({ name, type: type.trim(), visibility: visibility || 'private', isStatic, isReadonly: isConst, defaultValue: undefined }); } return properties; } /** * Check if class is abstract (has pure virtual functions) */ isAbstractClass(classCode) { return /virtual\s+\w+.*=\s*0\s*;/.test(classCode); } /** * 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', 'switch', 'catch', 'sizeof', 'typeof'].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; // C++ 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 /\bdo\b/g, // do-while loops /\bswitch\b/g, // switch statements /\bcase\b/g, // case labels /\bcatch\b/g, // exception handling /&&|\|\|/g, // logical operators /\?.*:/g, // ternary operators ]; complexityPatterns.forEach((pattern, index) => { const matches = code.match(pattern) || []; if (index === 6) { // case labels cyclomaticComplexity += Math.max(0, matches.length - 1); cognitiveComplexity += matches.length; } else { cyclomaticComplexity += matches.length; cognitiveComplexity += matches.length; } }); const linesOfCode = code.split('\n').filter(line => { const trimmed = line.trim(); return trimmed && !trimmed.startsWith('//') && !trimmed.startsWith('/*') && !trimmed.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.CppAnalyzer = CppAnalyzer; //# sourceMappingURL=CppAnalyzer.js.map