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
JavaScript
#!/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 };