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
JavaScript
/**
* @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
};