UNPKG

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
/** * šŸ” 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;