UNPKG

mira-consciousness

Version:

Memory & Intelligence Retention Archive - Preserving The Spark

307 lines 14.9 kB
/** * 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