@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
252 lines • 9.69 kB
JavaScript
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