UNPKG

agentsqripts

Version:

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

265 lines (239 loc) โ€ข 10.7 kB
#!/usr/bin/env node /** * @file Command-line interface for code cleanup analysis and maintenance optimization * @description Single responsibility: Provide interactive CLI for identifying cleanup opportunities * * This CLI tool serves as the user-facing interface for comprehensive code cleanup analysis, * detecting barrel files, dead code, orphaned comments, and other maintenance opportunities. * It implements a robust command-line interface with comprehensive help, flexible output * formats, and selective analysis options to support different development workflows. * * Design rationale: * - CLI-first design enables integration with development tools and CI/CD pipelines * - Modular analysis options allow targeted cleanup efforts based on team priorities * - Multiple output formats support different consumption patterns (human vs machine) * - Comprehensive help system reduces learning curve and improves adoption * - Error handling ensures robust operation across diverse project structures */ const fs = require('fs'); const path = require('path'); const { analyzeProjectCleanup, analyzeFileCleanup } = require('../lib/cleanup/analyzeCleanup'); const { createHelpFunction } = require('./lib/helpFormatter'); const { parseArgs: sharedParseArgs } = require('./lib/argumentParser'); const { handleAnalysisError, handleFileAccessError } = require('./lib/errorHandler'); const { getProcessArgs } = require('../lib/utils/processHelpers'); /** * Display help information */ const showHelp = createHelpFunction({ command: 'analyze-cleanup.js', description: 'Analyzes code for cleanup opportunities including barrel files, dead code, and orphaned comments.\nIdentifies maintenance tasks to improve code quality and reduce technical debt.', options: [ { flag: '--extensions <exts>', description: 'Comma-separated list of file extensions (default: .js,.ts,.jsx,.tsx)' }, { flag: '--output-format <fmt>', description: 'Output format: json, summary, detailed (default: summary)' }, { flag: '--include-barrels', description: 'Include barrel file detection (default: true)' }, { flag: '--include-dead-code', description: 'Include dead code detection (default: true)' }, { flag: '--include-comments', description: 'Include orphaned comment detection (default: true)' }, { flag: '--exclude-barrels', description: 'Exclude barrel file detection' }, { flag: '--exclude-dead-code', description: 'Exclude dead code detection' }, { flag: '--exclude-comments', description: 'Exclude orphaned comment detection' }, { flag: '--help', description: 'Show this help message' } ], modes: ' file Analyze a single file\n project Analyze entire project (default)', examples: [ 'node analyze-cleanup.js .', 'node analyze-cleanup.js --exclude-barrels src/', 'node analyze-cleanup.js --output-format json --include-dead-code .', 'node analyze-cleanup.js --extensions .js,.ts --output-format detailed src/' ], output: ' The tool identifies cleanup opportunities by checking:\n - Barrel files (files that only re-export content)\n - Dead code (unused variables, functions, unreachable code)\n - Orphaned comments (TODOs, FIXMEs, commented-out code)\n - Code maintenance suggestions', sections: { 'ISSUE TYPES': ' ๐Ÿ—‚๏ธ BARREL: Files that only re-export content\n ๐Ÿ’€ DEAD CODE: Unused variables, functions, unreachable code\n ๐Ÿ’ฌ COMMENTS: Outdated TODOs, FIXMEs, commented-out code\n โš ๏ธ CLEANUP: General maintenance opportunities' } }); const { formatAnalysisFooter, formatBreakdown, formatFileList, getSeverityEmoji, formatRecommendations } = require('./lib/formatUtils'); const { formatResults } = require('./lib/commonFormatters'); const { createSummaryFormatter, createRecommendationsSection, createTopIssuesSection } = require('./lib/summaryFormatter'); // Create custom summary formatter using shared utility const formatSummary = createSummaryFormatter({ title: '๐Ÿงน Cleanup Analysis Results', scoreField: 'cleanupScore', gradeField: 'cleanupGrade', filesField: 'totalFiles', issuesField: 'totalIssues', customMetrics: [ { field: 'totalEffort', icon: 'โฑ๏ธ', label: 'Total Effort' }, { field: 'barrelFiles', icon: '๐Ÿ—‚๏ธ', label: 'Barrel Files' }, { field: 'deadCodeInstances', icon: '๐Ÿ’€', label: 'Dead Code' }, { field: 'orphanedComments', icon: '๐Ÿ’ฌ', label: 'Orphaned Comments' } ], breakdowns: [ { field: 'typeBreakdown', type: 'category', icon: '๐Ÿ“Š', title: 'Issue Types', icons: { 'BARREL': '๐Ÿ—‚๏ธ', 'DEAD_CODE': '๐Ÿ’€', 'COMMENTS': '๐Ÿ’ฌ', 'CLEANUP': 'โš ๏ธ' } } ], customSections: [ createRecommendationsSection('Top Cleanup Priorities', '๐ŸŽฏ', (rec) => ` ${rec.priority || 'N/A'}. ${rec.description || 'No description available'}`), createTopIssuesSection({ field: 'topIssues', title: 'High Priority Cleanup Items', limit: 10, formatItem: (issue, i) => { const typeIcon = issue.type === 'BARREL' ? '๐Ÿ—‚๏ธ' : issue.type === 'DEAD_CODE' ? '๐Ÿ’€' : issue.type === 'COMMENTS' ? '๐Ÿ’ฌ' : 'โš ๏ธ'; return ` ${i + 1}. ${typeIcon} ${issue.description}\n ๐Ÿ“ ${issue.location}\n โฑ๏ธ Effort: ${issue.effort}/3 | ๐ŸŽฏ Impact: ${issue.impact}`; } }) ] }); // Old formatSummary implementation removed - using shared formatter // Create detailed formatter using shared utility const { createDetailedFormatter } = require('./lib/detailedFormatter'); const formatDetailed = createDetailedFormatter({ title: '๐Ÿ” Detailed Cleanup Analysis', formatSummary: formatSummary, filesField: 'files', issuesField: 'issues', formatIssue: (issue) => { const severityEmoji = { 'HIGH': '๐Ÿšจ', 'MEDIUM': 'โš ๏ธ', 'LOW': '๐Ÿ”' }[issue.severity] || '๐Ÿ”'; const typeEmoji = issue.type === 'barrel_file' ? '๐Ÿ—‚๏ธ' : (issue.type.includes('unused') || issue.type === 'unreachable_code' ? '๐Ÿ’€' : '๐Ÿ’ฌ'); const lines = [ ` ${severityEmoji} ${typeEmoji} [${issue.type.toUpperCase()}] ${issue.message}`, ` ๐Ÿ’ก ${issue.recommendation}` ]; if (issue.details) { const truncated = issue.details.length > 80 ? issue.details.slice(0, 80) + '...' : issue.details; lines.push(` ๐Ÿ“ ${truncated}`); } return lines; } }); // Old formatDetailed implementation removed - using shared formatter /** * Parse command line arguments */ function parseArgs() { const toolOptions = { includeBarrels: { flags: ['--include-barrels'], boolean: true, default: true, description: 'Include barrel file detection' }, includeDeadCode: { flags: ['--include-dead-code'], boolean: true, default: true, description: 'Include dead code detection' }, includeComments: { flags: ['--include-comments'], boolean: true, default: true, description: 'Include orphaned comment detection' }, excludeBarrels: { flags: ['--exclude-barrels'], boolean: true, description: 'Exclude barrel file detection' }, excludeDeadCode: { flags: ['--exclude-dead-code'], boolean: true, description: 'Exclude dead code detection' }, excludeComments: { flags: ['--exclude-comments'], boolean: true, description: 'Exclude orphaned comment detection' } }; const { targetPath, mode, options } = sharedParseArgs(getProcessArgs(), toolOptions); // Handle exclusion flags overriding inclusion defaults if (options.excludeBarrels) options.includeBarrels = false; if (options.excludeDeadCode) options.includeDeadCode = false; if (options.excludeComments) options.includeComments = false; if (options.help) { showHelp(); process.exit(0); } return { options, targetPath, mode }; } /** * Main execution function */ async function main() { try { const { options, targetPath, mode } = parseArgs(); try { await fs.promises.access(targetPath); } catch (error) { handleFileAccessError(targetPath); } const { logAnalysisStart, logMode, logExtensions } = require('../lib/utils/consoleHelpers'); logAnalysisStart('Cleanup', targetPath); logMode(mode); logExtensions(options.extensions); const includeOptions = []; if (options.includeBarrels) includeOptions.push('barrels'); if (options.includeDeadCode) includeOptions.push('dead-code'); if (options.includeComments) includeOptions.push('comments'); console.log(`๐Ÿ”ง Including: ${includeOptions.join(', ')}`); console.log(''); let results; if (mode === 'file') { const analysis = await analyzeFileCleanup(targetPath); if (analysis.error) { const { logAnalysisError } = require('../lib/utils/errorMessages'); logAnalysisError('cleanup', { message: analysis.error }); process.exit(1); } // Convert single file result to project-like format for consistent output results = { timestamp: new Date().toISOString(), summary: { totalFiles: 1, filesWithIssues: analysis.metrics.totalIssues > 0 ? 1 : 0, totalIssues: analysis.metrics.totalIssues, cleanupRate: analysis.metrics.totalIssues === 0 ? 100 : 0, issueBreakdown: { barrel_files: analysis.metrics.barrelFiles, dead_code: analysis.metrics.deadCodeIssues, orphaned_comments: analysis.metrics.commentIssues }, severityBreakdown: { HIGH: 0, MEDIUM: 0, LOW: 0 } }, files: analysis.metrics.totalIssues > 0 ? [analysis] : [], recommendations: [], analysisTime: 0 }; // Calculate severity breakdown without forEach const issues = analysis.issues; for (let issueIdx = 0; issueIdx < issues.length; issueIdx++) { const issue = issues[issueIdx]; results.summary.severityBreakdown[issue.severity]++; } } else { results = await analyzeProjectCleanup(targetPath, options); } console.log(formatResults(results, options.outputFormat, {}, formatSummary, formatDetailed)); } catch (error) { handleAnalysisError(error, 'generic'); } } // Run the CLI if (require.main === module) { main().catch(error => { console.error("Fatal error:", error); process.exit(1); }); } module.exports = { main, analyzeProjectCleanup, analyzeFileCleanup };