UNPKG

agentsqripts

Version:

Comprehensive static code analysis toolkit for identifying technical debt, security vulnerabilities, performance issues, and code quality problems

208 lines (173 loc) 6.82 kB
/** * @file Project performance analyzer with cross-file analysis * @description Enhanced project analyzer that includes cross-file pattern detection */ const { analyzeFilePerformance } = require('./performanceFileAnalyzerAST'); const { analyzeCrossFilePatterns } = require('./crossFileAnalyzer'); const fs = require('fs'); const path = require('path'); /** * Analyzes performance patterns across an entire project * @param {string} projectPath - Root path to analyze * @param {Object} options - Analysis options * @returns {Promise<Object>} Project performance analysis results */ async function analyzeProjectPerformance(projectPath, options = {}) { const excludePatterns = options.excludePatterns || ['node_modules', '.git', 'dist', 'build']; console.log(`🔍 Analyzing project performance: ${projectPath}`); // Get all files to analyze const files = await getAllFiles(projectPath, excludePatterns); const jsFiles = files.filter(file => /\.(js|jsx|ts|tsx)$/.test(file)); console.log(`📁 Files to analyze: ${files.length} (${jsFiles.length} JS files)`); // Analyze individual files const fileResults = []; let totalIssues = 0; let totalEffort = 0; const categoryBreakdown = {}; const severityBreakdown = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 }; // Use Promise.all for parallel analysis (but limit concurrency) const batchSize = 10; for (let i = 0; i < files.length; i += batchSize) { const batch = files.slice(i, i + batchSize); const batchResults = await Promise.all( batch.map(async (file) => { try { const result = await analyzeFilePerformance(file); return result; } catch (error) { console.warn(`Warning: Could not analyze ${file}: ${error.message}`); return { file, issues: [], performanceScore: 100, error: error.message }; } }) ); fileResults.push(...batchResults); } // Analyze cross-file patterns console.log(`🔗 Analyzing cross-file patterns...`); let crossFileIssues = []; try { crossFileIssues = analyzeCrossFilePatterns(projectPath, { excludePatterns }); } catch (error) { console.warn(`Warning: Cross-file analysis failed: ${error.message}`); } // Aggregate results const allIssues = []; const fileMetrics = []; fileResults.forEach(result => { if (result.issues && result.issues.length > 0) { allIssues.push(...result.issues); totalIssues += result.issues.length; totalEffort += result.metrics?.totalEffort || 0; // Update category breakdown Object.entries(result.metrics?.categoryBreakdown || {}).forEach(([category, count]) => { categoryBreakdown[category] = (categoryBreakdown[category] || 0) + count; }); // Update severity breakdown Object.entries(result.metrics?.severityBreakdown || {}).forEach(([severity, count]) => { severityBreakdown[severity] = (severityBreakdown[severity] || 0) + count; }); } fileMetrics.push({ file: result.file, score: result.performanceScore, grade: result.metrics?.grade, issues: result.issues?.length || 0, effort: result.metrics?.totalEffort || 0 }); }); // Add cross-file issues if (crossFileIssues.length > 0) { allIssues.push(...crossFileIssues); totalIssues += crossFileIssues.length; crossFileIssues.forEach(issue => { categoryBreakdown[issue.category] = (categoryBreakdown[issue.category] || 0) + 1; severityBreakdown[issue.severity] = (severityBreakdown[issue.severity] || 0) + 1; totalEffort += issue.effort || 1; }); } // Calculate overall project score const filesWithIssues = fileResults.filter(r => r.issues && r.issues.length > 0).length; const avgScore = fileResults.reduce((sum, r) => sum + (r.performanceScore || 100), 0) / fileResults.length; // Penalty for cross-file issues const crossFilePenalty = Math.min(crossFileIssues.length * 2, 20); const projectScore = Math.max(0, Math.round(avgScore - crossFilePenalty)); const grade = getGradeFromScore(projectScore); // Sort issues by priority allIssues.sort((a, b) => { const severityOrder = { CRITICAL: 4, HIGH: 3, MEDIUM: 2, LOW: 1 }; return (severityOrder[b.severity] || 0) - (severityOrder[a.severity] || 0); }); // Get high priority issues (CRITICAL and HIGH) const highPriorityIssues = allIssues.filter(issue => issue.severity === 'CRITICAL' || issue.severity === 'HIGH' ); console.log(`✅ Analysis complete: ${totalIssues} issues found across ${files.length} files`); return { projectPath, totalFiles: files.length, analyzedFiles: fileResults.length, filesWithIssues, performanceScore: projectScore, grade, totalIssues, highPriorityIssues: highPriorityIssues.length, totalEffort, crossFileIssues: crossFileIssues.length, categoryBreakdown, severityBreakdown, issues: allIssues, fileMetrics, crossFileAnalysis: { enabled: true, issuesFound: crossFileIssues.length, patterns: crossFileIssues } }; } /** * Get all files in directory recursively (async version) */ async function getAllFiles(dirPath, excludePatterns = []) { const files = []; async function scanDirectory(currentPath) { try { const entries = await fs.promises.readdir(currentPath, { withFileTypes: true }); const promises = []; for (const entry of entries) { const fullPath = path.join(currentPath, entry.name); const relativePath = path.relative(dirPath, fullPath); // Skip excluded patterns if (excludePatterns.some(pattern => relativePath.includes(pattern))) { continue; } if (entry.isDirectory()) { promises.push(scanDirectory(fullPath)); } else if (entry.isFile()) { // Only analyze text files that could contain performance issues const ext = path.extname(entry.name).toLowerCase(); const supportedExts = ['.js', '.jsx', '.ts', '.tsx', '.vue', '.py', '.java', '.cpp', '.c', '.cs', '.go', '.rs', '.php']; if (supportedExts.includes(ext)) { files.push(fullPath); } } } // Process subdirectories in parallel await Promise.all(promises); } catch (error) { console.warn(`Warning: Could not scan directory ${currentPath}: ${error.message}`); } } await scanDirectory(dirPath); return files; } const { getLetterGrade } = require('../utils/gradeUtils'); /** * Convert score to grade */ function getGradeFromScore(score) { return getLetterGrade(score); } module.exports = { analyzeProjectPerformance, getAllFiles };