UNPKG

agentsqripts

Version:

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

390 lines (342 loc) โ€ข 12.9 kB
/** * @file Analysis runner for problem-scope CLI * @description Handles running different types of analyses */ const path = require('path'); const fs = require('fs'); const madge = require('madge'); // For circular dependency analysis const { analyzeCodeComplexity } = require('../../lib/code-complexity/analyzeCodeComplexity'); const { analyzeTechnicalDebt } = require('../../lib/code-complexity/technicalDebtAnalyzer'); const { analyzeProjectSecurity } = require('../../lib/security-vulns/projectSecurityAnalyzer'); const { analyzeSRPViolations } = require('../../lib/srp-violations/analyzeSRPViolations'); const { analyzeProjectCleanup } = require('../../lib/cleanup/analyzeCleanup'); const { analyzeProjectRefactoring } = require('../../lib/export-promotion/projectRefactoringAnalyzer'); /** * Run dependency analysis * @param {string} projectPath - Project path * @param {Object} options - Analysis options * @param {Object} results - Results object to populate */ async function runDependencyAnalysis(projectPath, options, results) { console.log('๐Ÿ“Š Running dependency analysis...'); try { // Use madge to analyze circular dependencies const circularDeps = await madge(projectPath, { fileExtensions: ['js'] }) .then(res => res.circular()); // Simple file structure analysis const fileStructure = await analyzeFileStructure(projectPath); results.analyses.dependencies = { circular: circularDeps, structure: fileStructure, circularCount: circularDeps.length, severity: circularDeps.length > 0 ? 'HIGH' : 'LOW' }; } catch (error) { console.error('โŒ Dependency analysis failed:', error.message); results.analyses.dependencies = { error: error.message }; } } /** * Analyze file structure * @param {string} projectPath - Project path * @returns {Promise<Object>} File structure analysis */ async function analyzeFileStructure(projectPath) { try { const stats = await fs.promises.stat(projectPath); if (!stats.isDirectory()) { return { type: 'file', totalFiles: 1 }; } const files = await getAllJSFiles(projectPath); const directoryCount = await getDirectoryCount(projectPath); return { type: 'directory', totalFiles: files.length, directories: directoryCount, avgFilesPerDirectory: files.length / Math.max(directoryCount, 1) }; } catch (error) { return { error: error.message }; } } /** * Get all JavaScript files recursively * @param {string} dir - Directory path * @returns {Promise<Array>} Array of file paths */ async function getAllJSFiles(dir) { const files = []; const items = await fs.promises.readdir(dir, { withFileTypes: true }); for (const item of items) { const fullPath = path.join(dir, item.name); if (item.isDirectory() && !['node_modules', '.git', 'dist', 'build'].includes(item.name)) { files.push(...await getAllJSFiles(fullPath)); } else if (item.isFile() && ['.js', '.ts', '.jsx', '.tsx'].includes(path.extname(item.name))) { files.push(fullPath); } } return files; } /** * Count directories recursively * @param {string} dir - Directory path * @returns {Promise<number>} Directory count */ async function getDirectoryCount(dir) { let count = 1; try { const items = await fs.promises.readdir(dir, { withFileTypes: true }); for (const item of items) { if (item.isDirectory() && !['node_modules', '.git', 'dist', 'build'].includes(item.name)) { count += await getDirectoryCount(path.join(dir, item.name)); } } } catch (error) { // Skip inaccessible directories } return count; } /** * Run complexity analysis * @param {string} projectPath - Project path * @param {Object} options - Analysis options * @param {Object} results - Results object to populate */ async function runComplexityAnalysis(projectPath, options, results) { console.log('๐Ÿงฎ Running complexity analysis...'); try { // Use the existing complexity project analyzer const { analyzeProjectComplexity } = require('../../lib/code-complexity/projectComplexityAnalyzer'); const complexityResults = await analyzeProjectComplexity(projectPath, options); results.analyses.complexity = { ...complexityResults, severity: calculateComplexitySeverity(complexityResults) }; } catch (error) { console.error('โŒ Complexity analysis failed:', error.message); results.analyses.complexity = { error: error.message }; } } /** * Calculate complexity severity based on results * @param {Object} results - Complexity analysis results * @returns {string} Severity level */ function calculateComplexitySeverity(results) { if (!results.complexityBreakdown) return 'LOW'; const { high = 0, critical = 0 } = results.complexityBreakdown; const totalFiles = results.analyzedFiles || 1; if (critical > 0 || (high / totalFiles) > 0.2) return 'CRITICAL'; if (high > 0 || (results.averageComplexityScore || 0) > 15) return 'HIGH'; if ((results.averageComplexityScore || 0) > 10) return 'MEDIUM'; return 'LOW'; } /** * Run security analysis * @param {string} projectPath - Project path * @param {Object} options - Analysis options * @param {Object} results - Results object to populate */ async function runSecurityAnalysis(projectPath, options, results) { console.log('๐Ÿ”’ Running security analysis...'); try { const securityAnalysis = await analyzeProjectSecurity(projectPath, { maxFiles: options.maxFiles || Infinity // No file limit by default }); results.analyses.security = { ...securityAnalysis, severity: calculateSecuritySeverity(securityAnalysis) }; } catch (error) { console.error('โŒ Security analysis failed:', error.message); results.analyses.security = { error: error.message }; } } /** * Calculate security severity based on results * @param {Object} results - Security analysis results * @returns {string} Severity level */ function calculateSecuritySeverity(results) { if (!results.vulnerabilities) return 'LOW'; const criticalCount = results.vulnerabilities.filter(v => v.severity === 'CRITICAL').length; const highCount = results.vulnerabilities.filter(v => v.severity === 'HIGH').length; if (criticalCount > 0) return 'CRITICAL'; if (highCount > 0) return 'HIGH'; if (results.vulnerabilities.length > 0) return 'MEDIUM'; return 'LOW'; } /** * Run technical debt analysis * @param {string} projectPath - Project path * @param {Object} options - Analysis options * @param {Object} results - Results object to populate */ async function runDebtAnalysis(projectPath, options, results) { console.log('๐Ÿ’ธ Running technical debt analysis...'); try { const debtAnalysis = await analyzeTechnicalDebt(projectPath, { maxFiles: options.maxFiles || Infinity // No file limit by default }); results.analyses.debt = { ...debtAnalysis, severity: calculateDebtSeverity(debtAnalysis) }; } catch (error) { console.error('โŒ Technical debt analysis failed:', error.message); results.analyses.debt = { error: error.message }; } } /** * Calculate debt severity based on results * @param {Object} results - Debt analysis results * @returns {string} Severity level */ function calculateDebtSeverity(results) { if (!results.debtLevel) return 'LOW'; const debtScore = results.debtScore || 0; if (debtScore > 80) return 'CRITICAL'; if (debtScore > 60) return 'HIGH'; if (debtScore > 40) return 'MEDIUM'; return 'LOW'; } /** * Run refactoring analysis * @param {string} projectPath - Project path * @param {Object} options - Analysis options * @param {Object} results - Results object to populate */ async function runRefactoringAnalysis(projectPath, options, results) { console.log('๐Ÿ”ง Running refactoring analysis...'); try { const refactoringResults = await analyzeProjectRefactoring(projectPath, { includeNonUtility: true, includeBarrelFiles: true }); results.analyses.refactoring = { ...refactoringResults, severity: calculateRefactoringSeverity(refactoringResults) }; } catch (error) { console.error('โŒ Refactoring analysis failed:', error.message); results.analyses.refactoring = { error: error.message }; } } /** * Calculate refactoring severity based on results * @param {Object} results - Refactoring analysis results * @returns {string} Severity level */ function calculateRefactoringSeverity(results) { if (!results.opportunities) return 'LOW'; const opportunityCount = results.opportunities.length || 0; if (opportunityCount > 20) return 'HIGH'; if (opportunityCount > 10) return 'MEDIUM'; if (opportunityCount > 0) return 'LOW'; return 'LOW'; } /** * Check if directory should be skipped * @param {string} dirName - Directory name * @returns {boolean} Whether to skip */ function shouldSkipDirectory(dirName) { const skipPatterns = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage']; return skipPatterns.includes(dirName); } /** * Check if file should be analyzed * @param {string} fileName - File name * @returns {boolean} Whether to analyze */ function shouldAnalyzeFile(fileName) { const extensions = ['.js', '.ts', '.jsx', '.tsx']; return extensions.includes(path.extname(fileName)); } /** * Run SRP analysis * @param {string} projectPath - Project path * @param {Object} options - Analysis options * @param {Object} results - Results object to populate */ async function runSRPAnalysis(projectPath, options, results) { console.log('๐Ÿ“ Running SRP analysis...'); try { const srpResults = await analyzeSRPViolations(projectPath, { maxFiles: options.maxFiles || Infinity // No file limit by default }); results.analyses.srp = { ...srpResults, severity: calculateSRPSeverity(srpResults) }; } catch (error) { console.error('โŒ SRP analysis failed:', error.message); results.analyses.srp = { error: error.message }; } } /** * Run cleanup analysis * @param {string} projectPath - Project path * @param {Object} options - Analysis options * @param {Object} results - Results object to populate */ async function runCleanupAnalysis(projectPath, options, results) { console.log('๐Ÿงน Running cleanup analysis...'); try { const cleanupResults = await analyzeProjectCleanup(projectPath); results.analyses.cleanup = { ...cleanupResults, severity: calculateCleanupSeverity(cleanupResults) }; } catch (error) { console.error('โŒ Cleanup analysis failed:', error.message); results.analyses.cleanup = { error: error.message }; } } /** * Calculate SRP severity * @param {Object} results - SRP analysis results * @returns {string} Severity level */ function calculateSRPSeverity(results) { if (!results.violations) return 'LOW'; const highViolations = results.violations.filter(v => v.severity === 'HIGH').length; const totalViolations = results.violations.length; if (highViolations > 5) return 'CRITICAL'; if (highViolations > 0 || totalViolations > 20) return 'HIGH'; if (totalViolations > 10) return 'MEDIUM'; return 'LOW'; } /** * Calculate cleanup severity * @param {Object} results - Cleanup analysis results * @returns {string} Severity level */ function calculateCleanupSeverity(results) { if (!results.opportunities) return 'LOW'; const opportunityCount = results.opportunities.length || 0; if (opportunityCount > 30) return 'HIGH'; if (opportunityCount > 15) return 'MEDIUM'; if (opportunityCount > 0) return 'LOW'; return 'LOW'; } module.exports = { runDependencyAnalysis, // Run dependency analysis for circular deps & structure runComplexityAnalysis, // Run code complexity analysis runSecurityAnalysis, // Run security vulnerability analysis runDebtAnalysis, // Run technical debt analysis runRefactoringAnalysis, // Run refactoring opportunity analysis runSRPAnalysis, // Run single-responsibility analysis runCleanupAnalysis, // Run cleanup opportunity analysis analyzeFileStructure, // Analyze file structure & counts getAllJSFiles, // Recursively gather all JS/TS files getDirectoryCount, // Recursively count directories calculateComplexitySeverity, // Determine severity of complexity findings calculateSecuritySeverity, // Determine severity of security issues calculateDebtSeverity, // Determine severity of technical debt calculateRefactoringSeverity, // Determine severity of refactoring opportunities shouldSkipDirectory, // Decide if a directory should be skipped shouldAnalyzeFile, // Decide if a file should be analyzed calculateSRPSeverity, // Determine severity of SRP violations calculateCleanupSeverity // Determine severity of cleanup opportunities };