blue-beatle
Version:
š¤ AI-Powered Development Assistant - Intelligent code analysis, problem solving, and terminal automation using Gemini API
653 lines (552 loc) ⢠25.3 kB
JavaScript
/**
* š Code Analyzer - Advanced code analysis and quality assessment
* Provides security, performance, and quality analysis
*/
const fs = require('fs-extra');
const path = require('path');
const chalk = require('chalk');
const ora = require('ora');
const Table = require('cli-table3');
const chokidar = require('chokidar');
const { execSync } = require('child_process');
class CodeAnalyzer {
constructor() {
this.analysisRules = {
security: [
{
name: 'SQL Injection',
pattern: /(?:SELECT|INSERT|UPDATE|DELETE).*(?:\+|\$\{|\$\()/gi,
severity: 'high',
description: 'Potential SQL injection vulnerability'
},
{
name: 'XSS Vulnerability',
pattern: /innerHTML\s*=\s*.*(?:\+|\$\{)/gi,
severity: 'high',
description: 'Potential XSS vulnerability with innerHTML'
},
{
name: 'Hardcoded Secrets',
pattern: /(password|secret|key|token)\s*[:=]\s*["'][^"']{8,}["']/gi,
severity: 'critical',
description: 'Hardcoded secrets detected'
},
{
name: 'Eval Usage',
pattern: /\beval\s*\(/gi,
severity: 'high',
description: 'Use of eval() function detected'
},
{
name: 'Unsafe Deserialization',
pattern: /(?:pickle\.loads|yaml\.load|JSON\.parse).*(?:request|input)/gi,
severity: 'medium',
description: 'Potentially unsafe deserialization'
}
],
performance: [
{
name: 'Nested Loops',
pattern: /for\s*\([^}]*\{[^}]*for\s*\(/gi,
severity: 'medium',
description: 'Nested loops detected - potential performance issue'
},
{
name: 'Synchronous File Operations',
pattern: /fs\.(?:readFileSync|writeFileSync|existsSync)/gi,
severity: 'medium',
description: 'Synchronous file operations can block the event loop'
},
{
name: 'Large Array Operations',
pattern: /\.(?:map|filter|reduce|forEach)\s*\([^)]*\)\.(?:map|filter|reduce)/gi,
severity: 'low',
description: 'Chained array operations - consider optimization'
},
{
name: 'Memory Leaks',
pattern: /setInterval\s*\([^}]*(?!clearInterval)/gi,
severity: 'medium',
description: 'Potential memory leak - setInterval without clearInterval'
}
],
quality: [
{
name: 'Long Functions',
pattern: /function\s+\w+\s*\([^)]*\)\s*\{(?:[^{}]*\{[^{}]*\})*[^{}]{200,}\}/gi,
severity: 'low',
description: 'Function is too long - consider breaking it down'
},
{
name: 'Magic Numbers',
pattern: /(?<![a-zA-Z_])\d{2,}(?![a-zA-Z_])/g,
severity: 'low',
description: 'Magic numbers detected - consider using constants'
},
{
name: 'TODO Comments',
pattern: /(?:TODO|FIXME|HACK|XXX):/gi,
severity: 'info',
description: 'TODO/FIXME comments found'
},
{
name: 'Console Logs',
pattern: /console\.(?:log|debug|info|warn|error)/gi,
severity: 'low',
description: 'Console statements found - remove before production'
},
{
name: 'Empty Catch Blocks',
pattern: /catch\s*\([^)]*\)\s*\{\s*\}/gi,
severity: 'medium',
description: 'Empty catch block - should handle errors properly'
}
]
};
this.fileExtensions = {
javascript: ['.js', '.jsx', '.mjs'],
typescript: ['.ts', '.tsx'],
python: ['.py'],
java: ['.java'],
cpp: ['.cpp', '.cc', '.cxx'],
c: ['.c'],
go: ['.go'],
rust: ['.rs'],
php: ['.php'],
ruby: ['.rb']
};
}
async analyze(targetPath, options = {}) {
const spinner = ora('š Analyzing codebase...').start();
try {
const analysisResults = {
summary: {
totalFiles: 0,
totalLines: 0,
totalIssues: 0,
criticalIssues: 0,
highIssues: 0,
mediumIssues: 0,
lowIssues: 0
},
files: [],
issues: []
};
const files = await this.getFilesToAnalyze(targetPath, options.recursive);
analysisResults.summary.totalFiles = files.length;
for (const file of files) {
const fileAnalysis = await this.analyzeFile(file, options.type);
analysisResults.files.push(fileAnalysis);
analysisResults.issues.push(...fileAnalysis.issues);
analysisResults.summary.totalLines += fileAnalysis.lines;
}
// Calculate issue counts
analysisResults.issues.forEach(issue => {
analysisResults.summary.totalIssues++;
switch (issue.severity) {
case 'critical': analysisResults.summary.criticalIssues++; break;
case 'high': analysisResults.summary.highIssues++; break;
case 'medium': analysisResults.summary.mediumIssues++; break;
case 'low': analysisResults.summary.lowIssues++; break;
}
});
spinner.stop();
// Display results
this.displayAnalysisResults(analysisResults, options);
// Auto-fix if requested
if (options.fix) {
await this.autoFixIssues(analysisResults);
}
return analysisResults;
} catch (error) {
spinner.stop();
console.error(chalk.red('ā Analysis failed:'), error.message);
return null;
}
}
async getFilesToAnalyze(targetPath, recursive = true) {
const files = [];
const stat = await fs.stat(targetPath);
if (stat.isFile()) {
if (this.isAnalyzableFile(targetPath)) {
files.push(targetPath);
}
} else if (stat.isDirectory()) {
const items = await fs.readdir(targetPath);
for (const item of items) {
if (item.startsWith('.') || item === 'node_modules' || item === '__pycache__') {
continue;
}
const fullPath = path.join(targetPath, item);
const itemStat = await fs.stat(fullPath);
if (itemStat.isFile() && this.isAnalyzableFile(fullPath)) {
files.push(fullPath);
} else if (itemStat.isDirectory() && recursive) {
const subFiles = await this.getFilesToAnalyze(fullPath, recursive);
files.push(...subFiles);
}
}
}
return files;
}
isAnalyzableFile(filePath) {
const ext = path.extname(filePath).toLowerCase();
return Object.values(this.fileExtensions).some(exts => exts.includes(ext));
}
async analyzeFile(filePath, analysisType = 'all') {
try {
const content = await fs.readFile(filePath, 'utf8');
const lines = content.split('\n').length;
const issues = [];
const rulesToApply = analysisType === 'all'
? [...this.analysisRules.security, ...this.analysisRules.performance, ...this.analysisRules.quality]
: this.analysisRules[analysisType] || [];
for (const rule of rulesToApply) {
const matches = content.matchAll(rule.pattern);
for (const match of matches) {
const lineNumber = content.substring(0, match.index).split('\n').length;
issues.push({
file: filePath,
line: lineNumber,
rule: rule.name,
severity: rule.severity,
description: rule.description,
code: match[0],
category: this.getCategoryForRule(rule.name)
});
}
}
return {
file: filePath,
lines,
issues,
size: content.length
};
} catch (error) {
return {
file: filePath,
lines: 0,
issues: [{
file: filePath,
line: 0,
rule: 'File Read Error',
severity: 'error',
description: `Failed to read file: ${error.message}`,
code: '',
category: 'system'
}],
size: 0
};
}
}
getCategoryForRule(ruleName) {
if (this.analysisRules.security.some(rule => rule.name === ruleName)) return 'security';
if (this.analysisRules.performance.some(rule => rule.name === ruleName)) return 'performance';
if (this.analysisRules.quality.some(rule => rule.name === ruleName)) return 'quality';
return 'other';
}
displayAnalysisResults(results, options) {
console.log(chalk.cyan('\nš Code Analysis Results\n'));
// Summary table
const summaryTable = new Table({
head: ['Metric', 'Value'],
colWidths: [20, 15]
});
summaryTable.push(
['Total Files', results.summary.totalFiles],
['Total Lines', results.summary.totalLines.toLocaleString()],
['Total Issues', results.summary.totalIssues],
[chalk.red('Critical'), results.summary.criticalIssues],
[chalk.magenta('High'), results.summary.highIssues],
[chalk.yellow('Medium'), results.summary.mediumIssues],
[chalk.blue('Low'), results.summary.lowIssues]
);
console.log(summaryTable.toString());
if (options.output === 'json') {
console.log('\nš JSON Output:');
console.log(JSON.stringify(results, null, 2));
return;
}
if (options.output === 'markdown') {
this.generateMarkdownReport(results);
return;
}
// Issues table
if (results.issues.length > 0) {
console.log(chalk.cyan('\nšØ Issues Found:\n'));
const issuesTable = new Table({
head: ['File', 'Line', 'Severity', 'Rule', 'Description'],
colWidths: [30, 6, 10, 20, 40]
});
// Sort issues by severity
const sortedIssues = results.issues.sort((a, b) => {
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
return severityOrder[a.severity] - severityOrder[b.severity];
});
sortedIssues.slice(0, 50).forEach(issue => {
const severityColor = this.getSeverityColor(issue.severity);
issuesTable.push([
path.relative(process.cwd(), issue.file),
issue.line,
severityColor(issue.severity.toUpperCase()),
issue.rule,
issue.description
]);
});
console.log(issuesTable.toString());
if (results.issues.length > 50) {
console.log(chalk.gray(`\n... and ${results.issues.length - 50} more issues`));
}
} else {
console.log(chalk.green('\nā
No issues found! Great job!'));
}
// Recommendations
this.displayRecommendations(results);
}
getSeverityColor(severity) {
switch (severity) {
case 'critical': return chalk.red.bold;
case 'high': return chalk.magenta;
case 'medium': return chalk.yellow;
case 'low': return chalk.blue;
case 'info': return chalk.gray;
default: return chalk.white;
}
}
displayRecommendations(results) {
console.log(chalk.cyan('\nš” Recommendations:\n'));
if (results.summary.criticalIssues > 0) {
console.log(chalk.red('šØ Critical issues found! Address these immediately:'));
console.log(chalk.red(' - Review security vulnerabilities'));
console.log(chalk.red(' - Fix hardcoded secrets'));
console.log(chalk.red(' - Address high-risk patterns\n'));
}
if (results.summary.highIssues > 0) {
console.log(chalk.magenta('ā ļø High priority issues detected:'));
console.log(chalk.magenta(' - Review security patterns'));
console.log(chalk.magenta(' - Fix potential vulnerabilities\n'));
}
if (results.summary.mediumIssues > 0) {
console.log(chalk.yellow('š Medium priority improvements:'));
console.log(chalk.yellow(' - Optimize performance bottlenecks'));
console.log(chalk.yellow(' - Improve error handling\n'));
}
if (results.summary.lowIssues > 0) {
console.log(chalk.blue('š§ Code quality improvements:'));
console.log(chalk.blue(' - Clean up console statements'));
console.log(chalk.blue(' - Address TODO comments'));
console.log(chalk.blue(' - Refactor long functions\n'));
}
// Overall score
const totalPossibleIssues = results.summary.totalLines / 10; // Rough estimate
const score = Math.max(0, Math.min(100, 100 - (results.summary.totalIssues / totalPossibleIssues) * 100));
console.log(chalk.cyan(`š Code Quality Score: ${score.toFixed(1)}/100`));
if (score >= 90) {
console.log(chalk.green('š Excellent code quality!'));
} else if (score >= 70) {
console.log(chalk.yellow('š Good code quality with room for improvement'));
} else {
console.log(chalk.red('š Consider refactoring to improve code quality'));
}
}
generateMarkdownReport(results) {
const report = `# Code Analysis Report
## Summary
- **Total Files:** ${results.summary.totalFiles}
- **Total Lines:** ${results.summary.totalLines.toLocaleString()}
- **Total Issues:** ${results.summary.totalIssues}
- **Critical Issues:** ${results.summary.criticalIssues}
- **High Issues:** ${results.summary.highIssues}
- **Medium Issues:** ${results.summary.mediumIssues}
- **Low Issues:** ${results.summary.lowIssues}
## Issues by Category
### Security Issues
${results.issues.filter(i => i.category === 'security').map(i =>
`- **${i.rule}** (${i.severity}) in \`${path.relative(process.cwd(), i.file)}:${i.line}\`: ${i.description}`
).join('\n')}
### Performance Issues
${results.issues.filter(i => i.category === 'performance').map(i =>
`- **${i.rule}** (${i.severity}) in \`${path.relative(process.cwd(), i.file)}:${i.line}\`: ${i.description}`
).join('\n')}
### Quality Issues
${results.issues.filter(i => i.category === 'quality').map(i =>
`- **${i.rule}** (${i.severity}) in \`${path.relative(process.cwd(), i.file)}:${i.line}\`: ${i.description}`
).join('\n')}
Generated on ${new Date().toISOString()}
`;
const reportPath = path.join(process.cwd(), 'code-analysis-report.md');
fs.writeFileSync(reportPath, report);
console.log(chalk.green(`š Markdown report saved to: ${reportPath}`));
}
async autoFixIssues(results) {
console.log(chalk.cyan('\nš§ Auto-fixing issues...\n'));
const fixableIssues = results.issues.filter(issue =>
['Console Logs', 'TODO Comments'].includes(issue.rule)
);
if (fixableIssues.length === 0) {
console.log(chalk.yellow('ā¹ļø No auto-fixable issues found.'));
return;
}
for (const issue of fixableIssues) {
try {
await this.fixIssue(issue);
console.log(chalk.green(`ā
Fixed: ${issue.rule} in ${path.relative(process.cwd(), issue.file)}`));
} catch (error) {
console.error(chalk.red(`ā Failed to fix ${issue.rule}:`), error.message);
}
}
}
async fixIssue(issue) {
const content = await fs.readFile(issue.file, 'utf8');
let fixedContent = content;
switch (issue.rule) {
case 'Console Logs':
// Comment out console statements
fixedContent = content.replace(
/console\.(?:log|debug|info|warn|error)/g,
'// console.$1'
);
break;
case 'TODO Comments':
// Add timestamp to TODO comments
fixedContent = content.replace(
/(TODO|FIXME|HACK|XXX):/gi,
`$1 (${new Date().toISOString().split('T')[0]}):`
);
break;
}
if (fixedContent !== content) {
await fs.writeFile(issue.file, fixedContent);
}
}
async watchMode(targetPath, options = {}) {
console.log(chalk.cyan(`š Watching for changes in: ${targetPath}`));
console.log(chalk.gray('Press Ctrl+C to stop watching\n'));
const watcher = chokidar.watch(targetPath, {
ignored: /(^|[\/\\])\../, // ignore dotfiles
persistent: true,
ignoreInitial: true
});
watcher.on('change', async (filePath) => {
if (this.isAnalyzableFile(filePath)) {
console.log(chalk.blue(`š File changed: ${path.relative(process.cwd(), filePath)}`));
const fileAnalysis = await this.analyzeFile(filePath, 'all');
if (fileAnalysis.issues.length > 0) {
console.log(chalk.yellow(`ā ļø Found ${fileAnalysis.issues.length} issue(s):`));
fileAnalysis.issues.forEach(issue => {
const color = this.getSeverityColor(issue.severity);
console.log(` ${color(issue.severity.toUpperCase())}: ${issue.rule} (line ${issue.line})`);
});
if (options.autoFix) {
await this.autoFixIssues({ issues: fileAnalysis.issues });
}
if (options.notify) {
// Could integrate with system notifications here
console.log(chalk.cyan('š Notification: Issues detected in watched file'));
}
} else {
console.log(chalk.green('ā
No issues found'));
}
console.log(''); // Empty line for readability
}
});
// Keep the process running
process.on('SIGINT', () => {
console.log(chalk.yellow('\nš Stopping file watcher...'));
watcher.close();
process.exit(0);
});
}
async performanceAnalysis(targetPath, options = {}) {
console.log(chalk.cyan('ā” Starting Performance Analysis\n'));
const results = await this.analyze(targetPath, { type: 'performance', recursive: true });
if (options.profile) {
await this.runPerformanceProfiling(targetPath);
}
if (options.memory) {
await this.analyzeMemoryUsage(targetPath);
}
if (options.benchmark) {
await this.runBenchmarks(targetPath);
}
return results;
}
async runPerformanceProfiling(targetPath) {
console.log(chalk.blue('š Running performance profiling...'));
try {
// This would integrate with actual profiling tools
console.log(chalk.gray('Performance profiling would run here with tools like:'));
console.log(chalk.gray('- Node.js --prof flag'));
console.log(chalk.gray('- Chrome DevTools'));
console.log(chalk.gray('- Custom timing measurements'));
} catch (error) {
console.error(chalk.red('ā Profiling failed:'), error.message);
}
}
async analyzeMemoryUsage(targetPath) {
console.log(chalk.blue('š§ Analyzing memory usage patterns...'));
// Analyze for common memory leak patterns
const memoryIssues = [
'Event listeners not removed',
'Circular references',
'Large object retention',
'Unclosed resources'
];
console.log(chalk.yellow('Common memory issues to check:'));
memoryIssues.forEach(issue => {
console.log(chalk.gray(` - ${issue}`));
});
}
async runBenchmarks(targetPath) {
console.log(chalk.blue('š Running benchmarks...'));
try {
// This would run actual benchmarks
console.log(chalk.gray('Benchmark results would appear here'));
console.log(chalk.gray('Including execution time, memory usage, etc.'));
} catch (error) {
console.error(chalk.red('ā Benchmarking failed:'), error.message);
}
}
async securityAudit(targetPath, options = {}) {
console.log(chalk.cyan('š Starting Security Audit\n'));
const results = await this.analyze(targetPath, { type: 'security', recursive: true });
if (options.dependencies) {
await this.auditDependencies();
}
if (options.code) {
console.log(chalk.blue('š Static code security analysis completed'));
}
if (options.fix) {
await this.fixSecurityIssues(results);
}
return results;
}
async auditDependencies() {
console.log(chalk.blue('š¦ Auditing dependencies for vulnerabilities...'));
try {
// Check if npm audit is available
const auditResult = execSync('npm audit --json', { encoding: 'utf8' });
const audit = JSON.parse(auditResult);
if (audit.vulnerabilities && Object.keys(audit.vulnerabilities).length > 0) {
console.log(chalk.red(`ā ļø Found ${Object.keys(audit.vulnerabilities).length} vulnerable dependencies`));
console.log(chalk.yellow('Run "npm audit fix" to attempt automatic fixes'));
} else {
console.log(chalk.green('ā
No vulnerable dependencies found'));
}
} catch (error) {
console.log(chalk.yellow('ā ļø Could not run dependency audit (npm not available or no package.json)'));
}
}
async fixSecurityIssues(results) {
console.log(chalk.cyan('š§ Attempting to fix security issues...\n'));
const securityIssues = results.issues.filter(issue => issue.category === 'security');
if (securityIssues.length === 0) {
console.log(chalk.green('ā
No security issues to fix'));
return;
}
console.log(chalk.yellow('ā ļø Security issues require manual review and fixing'));
console.log(chalk.gray('Automated fixes for security issues are not recommended'));
console.log(chalk.gray('Please review each issue carefully and apply appropriate fixes'));
}
}
module.exports = CodeAnalyzer;