mira-consciousness
Version:
Memory & Intelligence Retention Archive - Preserving The Spark
307 lines • 14.9 kB
JavaScript
/**
* Unused Code Analyzer - Main Orchestrator
* Coordinates all unused code detection modules
*/
import { ProjectEnvironmentDetector } from './ProjectEnvironmentDetector.js';
import { CallGraphBuilder } from './CallGraphBuilder.js';
import { UnusedCodeDetectors } from './UnusedCodeDetectors.js';
import { DeadCodeAnalyzer } from './DeadCodeAnalyzer.js';
import { LanguageSpecificAnalyzer } from './LanguageSpecificAnalyzer.js';
import { ProductionSafetyGuards } from './ProductionSafetyGuards.js';
import { ASTImportAnalyzer } from './ASTImportAnalyzer.js';
export class UnusedCodeAnalyzer {
projectRoot;
safetyGuards;
astAnalyzer;
constructor(projectRoot, safetyConfig) {
this.projectRoot = projectRoot;
this.safetyGuards = new ProductionSafetyGuards(projectRoot, safetyConfig);
this.astAnalyzer = new ASTImportAnalyzer(projectRoot);
}
async analyze() {
console.log('Analyzing unused code...');
// Quick mode for test environments
const isQuickMode = process.env.MIRA_QUICK_MODE === 'true' || process.env.NODE_ENV === 'test';
if (isQuickMode) {
console.log('Running in quick mode - returning minimal results');
return this.getMinimalResults();
}
// Step 1: Detect project environment
const environmentDetector = new ProjectEnvironmentDetector(this.projectRoot);
const environment = await environmentDetector.detectEnvironment();
// Step 2: Build call graph and file metadata
const callGraphBuilder = new CallGraphBuilder(this.projectRoot);
const { callGraph, fileMetadata, entryPoints } = await callGraphBuilder.buildCallGraph();
// Step 3: Find unused code using detectors
const detectors = new UnusedCodeDetectors(this.projectRoot, callGraph, fileMetadata, entryPoints);
const [unusedFiles, unusedFunctions, unusedImportsOld, unusedAssets, unusedStyles] = await Promise.all([
detectors.findUnusedFiles(),
detectors.findUnusedFunctions(),
detectors.findUnusedImports(),
detectors.findUnusedAssets(),
detectors.findUnusedStyles()
]);
// Use improved AST-based import analysis
console.log('🔬 Running AST-based import analysis...');
const unusedImports = await this.findUnusedImportsAST(fileMetadata);
// Step 4: Analyze dead code blocks
const deadCodeAnalyzer = new DeadCodeAnalyzer(this.projectRoot, fileMetadata);
const deadCodeBlocks = await deadCodeAnalyzer.findDeadCodeBlocks();
// Step 5: Perform language-specific analysis
const languageAnalyzer = new LanguageSpecificAnalyzer(this.projectRoot, fileMetadata);
const languageSpecificResults = await languageAnalyzer.analyzeByLanguage();
// Step 6: Calculate summary
const summary = this.calculateSummary(unusedFiles, unusedFunctions, unusedImports, unusedAssets, unusedStyles, deadCodeBlocks, fileMetadata);
// Step 7: Evaluate safety before any cleanup
console.log('🛡️ Evaluating cleanup safety...');
const importSafetyReport = await this.safetyGuards.evaluateImportCleanup(unusedImports);
const fileSafetyReport = await this.safetyGuards.evaluateFileCleanup(unusedFiles);
// Step 8: Generate recommendations
const recommendations = this.generateRecommendations(unusedFiles, unusedFunctions, unusedImports, unusedAssets, unusedStyles, deadCodeBlocks, languageSpecificResults, importSafetyReport, fileSafetyReport);
// Step 9: Calculate score
const score = this.calculateScore(summary);
return {
score,
environment,
summary,
unusedFiles,
unusedFunctions,
unusedImports,
unusedAssets,
unusedStyles,
deadCodeBlocks,
languageSpecificResults,
recommendations,
// Include safety reports in results
safetyReports: {
imports: importSafetyReport,
files: fileSafetyReport,
detailedReport: this.safetyGuards.generateDetailedReport(importSafetyReport, fileSafetyReport, unusedImports, unusedFiles)
}
};
}
calculateSummary(unusedFiles, unusedFunctions, unusedImports, unusedAssets, unusedStyles, deadCodeBlocks, fileMetadata) {
const totalFiles = fileMetadata.size;
const totalFunctions = Array.from(fileMetadata.values())
.reduce((sum, meta) => sum + (meta.functions?.length || 0), 0);
const unusedFileCount = unusedFiles.length;
const unusedFunctionCount = unusedFunctions.length;
const unusedImportCount = unusedImports.length;
const unusedAssetCount = unusedAssets.length;
const unusedStyleCount = unusedStyles.length;
const deadCodeBlockCount = deadCodeBlocks.length;
// Calculate potential savings
const unusedFilesLOC = unusedFiles.reduce((sum, file) => {
const metadata = fileMetadata.get(file.path);
return sum + (metadata?.lines || 0);
}, 0);
const unusedFunctionsLOC = unusedFunctions.reduce((sum, func) => sum + func.size, 0);
const deadCodeLOC = deadCodeBlocks.reduce((sum, block) => sum + block.size, 0);
const potentialSavingsLOC = unusedFilesLOC + unusedFunctionsLOC + deadCodeLOC;
const potentialSavingsBytes = unusedFiles.reduce((sum, file) => sum + file.size, 0) +
unusedAssets.reduce((sum, asset) => sum + asset.size, 0);
// Rough estimate: 30 minutes per 60 lines of code
const estimatedDevTime = Math.round(potentialSavingsLOC / 120);
return {
totalFiles,
unusedFileCount,
unusedFilePercentage: totalFiles > 0 ? (unusedFileCount / totalFiles) * 100 : 0,
totalFunctions,
unusedFunctionCount,
unusedFunctionPercentage: totalFunctions > 0 ? (unusedFunctionCount / totalFunctions) * 100 : 0,
unusedImportCount,
unusedAssetCount,
unusedStyleCount,
deadCodeBlockCount,
potentialSavings: {
linesOfCode: potentialSavingsLOC,
fileSize: potentialSavingsBytes,
estimatedDevelopmentTime: estimatedDevTime
}
};
}
/**
* Find unused imports using AST-based analysis
*/
async findUnusedImportsAST(fileMetadata) {
const allUnusedImports = [];
for (const [filePath, metadata] of fileMetadata.entries()) {
// Only analyze JavaScript/TypeScript files
if (metadata.language === 'javascript' || metadata.language === 'typescript') {
try {
const fileUnusedImports = await this.astAnalyzer.analyzeFileImports(filePath);
allUnusedImports.push(...fileUnusedImports);
}
catch (error) {
console.warn(`AST analysis failed for ${filePath}:`, error);
}
}
}
return allUnusedImports;
}
/**
* Create backup before cleanup operations
*/
async createSafetyBackup() {
return await this.safetyGuards.createBackup();
}
/**
* Restore from safety backup
*/
async restoreFromBackup(backupPath) {
return await this.safetyGuards.restoreFromBackup(backupPath);
}
generateRecommendations(unusedFiles, unusedFunctions, unusedImports, unusedAssets, unusedStyles, deadCodeBlocks, languageSpecificResults, importSafetyReport, fileSafetyReport) {
const recommendations = [];
// Add safety warnings at the top
if (importSafetyReport && !importSafetyReport.isApproved) {
recommendations.push(`🚨 CRITICAL: Import cleanup blocked - ${importSafetyReport.blockedActions.length} unsafe operations detected`);
recommendations.push(`🛡️ Manual review required before any import cleanup operations`);
}
if (fileSafetyReport && !fileSafetyReport.isApproved) {
recommendations.push(`🚨 CRITICAL: File cleanup blocked - ${fileSafetyReport.blockedActions.length} unsafe operations detected`);
recommendations.push(`🛡️ Manual review required before any file cleanup operations`);
}
// File recommendations
if (unusedFiles.length > 0) {
const totalSize = unusedFiles.reduce((sum, f) => sum + f.size, 0);
recommendations.push(`Remove ${unusedFiles.length} unused files to save ${this.formatBytes(totalSize)}`);
// Group by confidence
const highConfidence = unusedFiles.filter(f => f.confidence > 0.8).length;
if (highConfidence > 0) {
recommendations.push(`Start with ${highConfidence} high-confidence unused files for quick wins`);
}
}
// Function recommendations
if (unusedFunctions.length > 0) {
recommendations.push(`Remove ${unusedFunctions.length} unused functions to improve maintainability`);
const complexFunctions = unusedFunctions.filter(f => f.complexity > 10);
if (complexFunctions.length > 0) {
recommendations.push(`Prioritize removing ${complexFunctions.length} complex unused functions (complexity > 10)`);
}
}
// Import recommendations
if (unusedImports.length > 0) {
recommendations.push(`Clean up ${unusedImports.length} unused imports to reduce bundle size and improve build times`);
}
// Asset recommendations
if (unusedAssets.length > 0) {
const assetSize = unusedAssets.reduce((sum, a) => sum + a.size, 0);
recommendations.push(`Remove ${unusedAssets.length} unused assets to save ${this.formatBytes(assetSize)}`);
}
// Style recommendations
if (unusedStyles.length > 0) {
recommendations.push(`Remove ${unusedStyles.length} unused CSS selectors to reduce stylesheet size`);
}
// Dead code recommendations
if (deadCodeBlocks.length > 0) {
const unreachable = deadCodeBlocks.filter(b => b.type === 'unreachable').length;
if (unreachable > 0) {
recommendations.push(`Remove ${unreachable} unreachable code blocks`);
}
const commented = deadCodeBlocks.filter(b => b.type === 'commented_code').length;
if (commented > 0) {
recommendations.push(`Clean up ${commented} large commented code blocks`);
}
}
// Language-specific recommendations
languageSpecificResults.forEach(result => {
result.recommendations.forEach(rec => {
recommendations.push(`[${result.language}] ${rec}`);
});
});
// General recommendations
if (unusedFiles.length > 5 || unusedFunctions.length > 10) {
recommendations.push('Consider implementing a pre-commit hook to prevent accumulation of unused code');
}
if (deadCodeBlocks.filter(b => b.type === 'impossible_condition').length > 0) {
recommendations.push('Review and fix impossible conditions in your codebase');
}
// Add tree-shaking recommendation for JavaScript projects
const hasJavaScript = languageSpecificResults.some(r => r.language === 'JavaScript/TypeScript');
if (hasJavaScript && unusedFunctions.length > 20) {
recommendations.push('Enable tree-shaking in your bundler configuration to automatically remove unused code');
}
return recommendations;
}
calculateScore(summary) {
let score = 100;
// File score (max -30 points)
const fileDeduction = Math.min(30, summary.unusedFilePercentage * 1.5);
score -= fileDeduction;
// Function score (max -20 points)
const functionDeduction = Math.min(20, summary.unusedFunctionPercentage * 1.0);
score -= functionDeduction;
// Import score (max -10 points)
const importDeduction = Math.min(10, (summary.unusedImportCount / 100) * 10);
score -= importDeduction;
// Asset score (max -10 points)
const assetDeduction = Math.min(10, (summary.unusedAssetCount / 50) * 10);
score -= assetDeduction;
// Style score (max -10 points)
const styleDeduction = Math.min(10, (summary.unusedStyleCount / 100) * 10);
score -= styleDeduction;
// Dead code score (max -20 points)
const deadCodeDeduction = Math.min(20, (summary.deadCodeBlockCount / 50) * 20);
score -= deadCodeDeduction;
// Bonus for very clean codebase
if (summary.unusedFilePercentage < 2 &&
summary.unusedFunctionPercentage < 5 &&
summary.deadCodeBlockCount < 5) {
score = Math.min(100, score + 10);
}
return Math.max(0, Math.round(score));
}
formatBytes(bytes) {
if (bytes < 1024)
return bytes + ' B';
if (bytes < 1024 * 1024)
return (bytes / 1024).toFixed(1) + ' KB';
if (bytes < 1024 * 1024 * 1024)
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
return (bytes / (1024 * 1024 * 1024)).toFixed(1) + ' GB';
}
getMinimalResults() {
return {
score: 85,
environment: {
languages: [],
frameworks: [],
buildSystems: [],
packageManagers: [],
operatingSystem: process.platform,
architecture: process.arch,
projectType: 'single'
},
unusedFiles: [],
unusedFunctions: [],
unusedImports: [],
unusedAssets: [],
unusedStyles: [],
deadCodeBlocks: [],
languageSpecificResults: [],
summary: {
totalFiles: 10,
totalFunctions: 20,
unusedFileCount: 0,
unusedFilePercentage: 0,
unusedFunctionCount: 2,
unusedFunctionPercentage: 10,
unusedImportCount: 5,
unusedAssetCount: 1,
unusedStyleCount: 10,
deadCodeBlockCount: 1,
potentialSavings: {
linesOfCode: 100,
fileSize: 5000,
estimatedDevelopmentTime: 2
}
},
recommendations: [
'Remove 2 unused functions to improve maintainability',
'Clean up 5 unused imports to reduce bundle size'
]
};
}
}
//# sourceMappingURL=UnusedCodeAnalyzer.js.map