UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

252 lines 9.69 kB
import { promises as fs } from 'fs'; import path from 'path'; export class CodeAnalyzer { async analyzeFile(filePath) { const content = await fs.readFile(filePath, 'utf-8'); const language = this.detectLanguage(filePath); const analysis = { file: filePath, language, metrics: this.calculateMetrics(content), dependencies: this.extractDependencies(content, language), exports: this.extractExports(content, language), imports: this.extractImports(content, language), functions: this.extractFunctions(content, language), classes: this.extractClasses(content, language), complexity: this.analyzeComplexity(content, language), }; return analysis; } detectLanguage(filePath) { const ext = path.extname(filePath).toLowerCase(); const languageMap = { '.ts': 'typescript', '.tsx': 'typescript', '.js': 'javascript', '.jsx': 'javascript', '.py': 'python', '.rs': 'rust', '.go': 'go', '.java': 'java', '.cpp': 'cpp', '.c': 'c', }; return languageMap[ext] || 'unknown'; } calculateMetrics(content) { const lines = content.split('\n'); let loc = 0; let comments = 0; let blanks = 0; for (const line of lines) { const trimmed = line.trim(); if (trimmed === '') { blanks++; } else if (this.isComment(trimmed)) { comments++; } else { loc++; } } return { lines: lines.length, loc, comments, blanks, complexity: this.calculateCyclomaticComplexity(content), }; } isComment(line) { const commentPatterns = [ /^\s*\/\//, // JS/TS single line /^\s*\/\*/, // JS/TS multi line start /^\s*\*/, // JS/TS multi line continuation /^\s*#/, // Python, Shell /^\s*--/, // SQL, Haskell ]; return commentPatterns.some(pattern => pattern.test(line)); } calculateCyclomaticComplexity(content) { // Simplified cyclomatic complexity calculation const complexityPatterns = [ /\bif\b/g, /\belse\s+if\b/g, /\bwhile\b/g, /\bfor\b/g, /\bcase\b/g, /\bcatch\b/g, /\?\s*:/g, // Ternary operator /&&/g, // Logical AND /\|\|/g, // Logical OR ]; let complexity = 1; // Base complexity for (const pattern of complexityPatterns) { const matches = content.match(pattern); if (matches) { complexity += matches.length; } } return complexity; } extractDependencies(content, language) { const dependencies = []; if (language === 'typescript' || language === 'javascript') { // Extract import statements const importRegex = /import\s+.*\s+from\s+['"]([^'"]+)['"]/g; const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g; let match; while ((match = importRegex.exec(content)) !== null) { dependencies.push(match[1]); } while ((match = requireRegex.exec(content)) !== null) { dependencies.push(match[1]); } } return [...new Set(dependencies)]; } extractImports(content, language) { if (language === 'typescript' || language === 'javascript') { const imports = []; const regex = /import\s+(?:{([^}]+)}|(\w+)|(\*\s+as\s+\w+))\s+from\s+['"]([^'"]+)['"]/g; let match; while ((match = regex.exec(content)) !== null) { if (match[1]) { // Named imports imports.push(...match[1].split(',').map(s => s.trim())); } else if (match[2]) { // Default import imports.push(match[2]); } else if (match[3]) { // Namespace import imports.push(match[3]); } } return imports; } return []; } extractExports(content, language) { if (language === 'typescript' || language === 'javascript') { const exports = []; // Named exports const namedExportRegex = /export\s+(?:const|let|var|function|class)\s+(\w+)/g; let match; while ((match = namedExportRegex.exec(content)) !== null) { exports.push(match[1]); } // Export statements const exportStmtRegex = /export\s+{([^}]+)}/g; while ((match = exportStmtRegex.exec(content)) !== null) { exports.push(...match[1].split(',').map(s => s.trim().split(/\s+as\s+/)[0])); } return [...new Set(exports)]; } return []; } extractFunctions(content, language) { const functions = []; if (language === 'typescript' || language === 'javascript') { // Function declarations const funcRegex = /(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/g; const arrowRegex = /(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*=>/g; const lines = content.split('\n'); let match; while ((match = funcRegex.exec(content)) !== null) { const lineNumber = content.substring(0, match.index).split('\n').length; functions.push({ name: match[1], line: lineNumber, params: match[2].split(',').map(p => p.trim()).filter(p => p), complexity: 1, // Would need proper AST parsing for accurate complexity length: this.getFunctionLength(content, match.index), }); } while ((match = arrowRegex.exec(content)) !== null) { const lineNumber = content.substring(0, match.index).split('\n').length; functions.push({ name: match[1], line: lineNumber, params: match[2].split(',').map(p => p.trim()).filter(p => p), complexity: 1, length: this.getFunctionLength(content, match.index), }); } } return functions; } extractClasses(content, language) { const classes = []; if (language === 'typescript' || language === 'javascript') { const classRegex = /class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([^{]+))?\s*{/g; let match; while ((match = classRegex.exec(content)) !== null) { const lineNumber = content.substring(0, match.index).split('\n').length; classes.push({ name: match[1], line: lineNumber, extends: match[2], implements: match[3]?.split(',').map(i => i.trim()), methods: [], // Would need proper AST parsing properties: [], }); } } return classes; } getFunctionLength(content, startIndex) { // Simplified function length calculation // In reality, would need proper AST parsing const afterFunction = content.substring(startIndex); const lines = afterFunction.split('\n'); let braceCount = 0; let lineCount = 0; for (const line of lines) { lineCount++; braceCount += (line.match(/{/g) || []).length; braceCount -= (line.match(/}/g) || []).length; if (braceCount === 0 && lineCount > 1) { return lineCount; } } return lineCount; } analyzeComplexity(content, language) { const complexity = this.calculateCyclomaticComplexity(content); const loc = content.split('\n').filter(line => line.trim() && !this.isComment(line.trim())).length; // Simplified maintainability index const maintainabilityIndex = Math.max(0, Math.min(100, 171 - 5.2 * Math.log(complexity) - 0.23 * complexity - 16.2 * Math.log(loc))); const suggestions = []; if (complexity > 10) { suggestions.push({ type: 'refactor', description: 'Consider breaking this code into smaller functions', severity: complexity > 20 ? 'high' : 'medium', }); } if (loc > 200) { suggestions.push({ type: 'split', description: 'This file is getting large. Consider splitting it into multiple modules', severity: loc > 500 ? 'high' : 'medium', }); } if (maintainabilityIndex < 50) { suggestions.push({ type: 'simplify', description: 'Code maintainability is low. Consider refactoring for clarity', severity: 'high', }); } return { cyclomatic: complexity, cognitive: complexity * 1.2, // Simplified cognitive complexity maintainabilityIndex, suggestions, }; } } //# sourceMappingURL=analyzer.js.map