@paulohenriquevn/m2js
Version:
Transform TypeScript/JavaScript code into LLM-friendly Markdown summaries + Smart Dead Code Detection + Graph-Deep Diff Analysis. Extract exported functions, classes, and JSDoc comments for better AI context with 60%+ token reduction. Intelligent dead cod
319 lines (311 loc) • 15.8 kB
JavaScript
;
/**
* CLI Integration for Dead Code Analysis
* Handles --detect-unused flag and output formatting
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeDeadCodeAnalysis = executeDeadCodeAnalysis;
exports.getDeadCodeHelpText = getDeadCodeHelpText;
const chalk_1 = __importDefault(require("chalk"));
const dead_code_analyzer_1 = require("./dead-code-analyzer");
const config_loader_1 = require("./config-loader");
/**
* Execute dead code analysis and output results
*/
async function executeDeadCodeAnalysis(files, options = {
format: 'table',
includeMetrics: true,
includeSuggestions: true,
}) {
try {
console.log(chalk_1.default.cyan.bold('Dead Code Analysis Report'));
console.log(chalk_1.default.dim(`Analyzing ${files.length} files...\n`));
// Load configuration from .m2jsrc and environment variables
const config = config_loader_1.ConfigLoader.loadConfig();
const performanceOptions = config_loader_1.ConfigLoader.toPerformanceOptions(config);
// Override showProgress based on file count if not explicitly set
if (files.length <= 10 && !process.env.M2JS_SHOW_PROGRESS) {
performanceOptions.showProgress = false;
}
const report = await (0, dead_code_analyzer_1.analyzeDeadCode)(files, performanceOptions);
if (options.format === 'json') {
outputJsonFormat(report);
}
else {
outputTableFormat(report, options);
}
// Exit with code 0 (informational, not error)
process.exit(0);
}
catch (error) {
console.error(chalk_1.default.red(`Error: ${error.message}`));
process.exit(1);
}
}
/**
* Output report in table format (default)
*/
function outputTableFormat(report, options) {
const { deadExports, unusedImports, suggestions, metrics, projectPath } = report;
// Project info
console.log(chalk_1.default.dim(`Project: ${projectPath}`));
if (options.includeMetrics) {
console.log(chalk_1.default.dim(`Files analyzed: ${metrics.totalFiles}`));
console.log(chalk_1.default.dim(`Analysis time: ${metrics.analysisTimeMs}ms\n`));
}
// Dead exports section
if (deadExports.length === 0 && unusedImports.length === 0) {
console.log(chalk_1.default.green.bold('Great! No dead code found!'));
console.log(chalk_1.default.green('All exports and imports are being used.\n'));
}
else {
console.log(chalk_1.default.red.bold(`Dead Exports (${deadExports.length} found):`));
console.log(chalk_1.default.gray('┌─────────────────────────────────────────────────┐'));
deadExports.forEach((deadExport, index) => {
const isLast = index === deadExports.length - 1;
const fileLocation = chalk_1.default.yellow(`${deadExport.file}:${deadExport.line}`);
const exportName = chalk_1.default.white.bold(deadExport.name);
const exportType = chalk_1.default.cyan(`${deadExport.type}`);
const reason = chalk_1.default.dim(deadExport.reason);
const confidenceColor = getConfidenceColor(deadExport.confidence);
const confidence = confidenceColor(`[${deadExport.confidence.toUpperCase()}]`);
console.log(chalk_1.default.gray('│ ') + fileLocation + ' ' + confidence);
console.log(chalk_1.default.gray('│ ') + chalk_1.default.gray('└─ ') + exportType + ' ' + exportName);
console.log(chalk_1.default.gray('│ ') + reason);
// Show risk factors if any
if (deadExport.riskFactors.length > 0) {
console.log(chalk_1.default.gray('│ ') + chalk_1.default.red('Risk factors:'));
deadExport.riskFactors.forEach(risk => {
console.log(chalk_1.default.gray('│ • ') + chalk_1.default.red(risk));
});
}
if (!isLast) {
console.log(chalk_1.default.gray('│'));
}
});
console.log(chalk_1.default.gray('└─────────────────────────────────────────────────┘\n'));
// Unused imports section
if (unusedImports.length > 0) {
console.log(chalk_1.default.yellow.bold(`Unused Imports (${unusedImports.length} found):`));
console.log(chalk_1.default.gray('┌─────────────────────────────────────────────────┐'));
unusedImports.forEach((unusedImport, index) => {
const isLast = index === unusedImports.length - 1;
const fileLocation = chalk_1.default.yellow(`${unusedImport.file}:${unusedImport.line}`);
const importName = chalk_1.default.white.bold(unusedImport.name);
const importFrom = chalk_1.default.cyan(`from '${unusedImport.from}'`);
const reason = chalk_1.default.dim(unusedImport.reason);
const confidenceColor = getConfidenceColor(unusedImport.confidence);
const confidence = confidenceColor(`[${unusedImport.confidence.toUpperCase()}]`);
console.log(chalk_1.default.gray('│ ') + fileLocation + ' ' + confidence);
console.log(chalk_1.default.gray('│ ') + chalk_1.default.gray('└─ ') + importName + ' ' + importFrom);
console.log(chalk_1.default.gray('│ ') + reason);
// Show risk factors if any
if (unusedImport.riskFactors.length > 0) {
console.log(chalk_1.default.gray('│ ') + chalk_1.default.red('Risk factors:'));
unusedImport.riskFactors.forEach(risk => {
console.log(chalk_1.default.gray('│ • ') + chalk_1.default.red(risk));
});
}
if (!isLast) {
console.log(chalk_1.default.gray('│'));
}
});
console.log(chalk_1.default.gray('└─────────────────────────────────────────────────┘\n'));
}
}
// Impact summary
if (options.includeMetrics &&
(deadExports.length > 0 || unusedImports.length > 0)) {
console.log(chalk_1.default.blue.bold('Impact Summary:'));
// Count by type
const typeCount = deadExports.reduce((acc, exp) => {
acc[exp.type] = (acc[exp.type] || 0) + 1;
return acc;
}, {});
Object.entries(typeCount).forEach(([type, count]) => {
const plural = count === 1 ? '' : 's';
console.log(chalk_1.default.blue(`• ${capitalize(type)}${plural}: ${count} unused`));
});
// Add unused imports to summary
if (unusedImports.length > 0) {
console.log(chalk_1.default.blue(`• Imports: ${unusedImports.length} unused`));
}
console.log(chalk_1.default.blue(`• Estimated size: ~${metrics.estimatedSavingsKB}KB`));
// Show performance metrics if available
if (metrics.performanceStats) {
const perfStats = metrics.performanceStats;
console.log(chalk_1.default.dim(`Cache: ${perfStats.cacheHits} hits, ${perfStats.cacheMisses} misses (${Math.round(perfStats.hitRate * 100)}% hit rate)`));
}
console.log();
}
// Removal suggestions section
if (options.includeSuggestions !== false &&
suggestions &&
suggestions.length > 0) {
console.log(chalk_1.default.blue.bold('Removal Suggestions:'));
console.log(chalk_1.default.gray('┌─────────────────────────────────────────────────┐'));
// Group suggestions by safety level
const safeSuggestions = suggestions.filter(s => s.safety === 'safe');
const reviewSuggestions = suggestions.filter(s => s.safety === 'review-needed');
const riskySuggestions = suggestions.filter(s => s.safety === 'risky');
// Show safe suggestions first
if (safeSuggestions.length > 0) {
console.log(chalk_1.default.gray('│ ') + chalk_1.default.green.bold('SAFE TO REMOVE:'));
safeSuggestions.slice(0, 5).forEach((suggestion, index) => {
const isLast = index === Math.min(4, safeSuggestions.length - 1);
const priorityIcon = getPriorityIcon(suggestion.priority);
console.log(chalk_1.default.gray('│ ') + priorityIcon + ' ' + chalk_1.default.white(suggestion.action));
console.log(chalk_1.default.gray('│ ') +
chalk_1.default.dim(`${suggestion.file}:${suggestion.line}`));
console.log(chalk_1.default.gray('│ ') + chalk_1.default.dim(suggestion.impact));
if (suggestion.command) {
console.log(chalk_1.default.gray('│ ') + chalk_1.default.cyan(suggestion.command));
}
if (!isLast) {
console.log(chalk_1.default.gray('│'));
}
});
if (safeSuggestions.length > 5) {
console.log(chalk_1.default.gray('│ ') +
chalk_1.default.dim(`... and ${safeSuggestions.length - 5} more safe suggestions`));
}
console.log(chalk_1.default.gray('│'));
}
// Show review-needed suggestions
if (reviewSuggestions.length > 0) {
console.log(chalk_1.default.gray('│ ') + chalk_1.default.yellow.bold('REVIEW BEFORE REMOVING:'));
reviewSuggestions.slice(0, 3).forEach((suggestion, index) => {
const isLast = index === Math.min(2, reviewSuggestions.length - 1);
const priorityIcon = getPriorityIcon(suggestion.priority);
console.log(chalk_1.default.gray('│ ') + priorityIcon + ' ' + chalk_1.default.white(suggestion.action));
console.log(chalk_1.default.gray('│ ') +
chalk_1.default.dim(`${suggestion.file}:${suggestion.line}`));
if (suggestion.warnings && suggestion.warnings.length > 0) {
suggestion.warnings.forEach(warning => {
console.log(chalk_1.default.gray('│ ') +
chalk_1.default.yellow('Warning: ') +
chalk_1.default.yellow(warning));
});
}
if (!isLast) {
console.log(chalk_1.default.gray('│'));
}
});
if (reviewSuggestions.length > 3) {
console.log(chalk_1.default.gray('│ ') +
chalk_1.default.dim(`... and ${reviewSuggestions.length - 3} more suggestions to review`));
}
console.log(chalk_1.default.gray('│'));
}
// Show risky suggestions (just count)
if (riskySuggestions.length > 0) {
console.log(chalk_1.default.gray('│ ') + chalk_1.default.red.bold('HIGH RISK:'));
console.log(chalk_1.default.gray('│ ') +
chalk_1.default.red(`${riskySuggestions.length} suggestions require careful analysis`));
console.log(chalk_1.default.gray('│ ') + chalk_1.default.red('Manual review strongly recommended'));
}
console.log(chalk_1.default.gray('└─────────────────────────────────────────────────┘\n'));
}
// Next steps guidance
if (deadExports.length > 0 || unusedImports.length > 0) {
console.log(chalk_1.default.blue.bold('Next Steps:'));
const safeSuggestions = suggestions?.filter(s => s.safety === 'safe') || [];
const reviewSuggestions = suggestions?.filter(s => s.safety === 'review-needed') || [];
const riskySuggestions = suggestions?.filter(s => s.safety === 'risky') || [];
if (safeSuggestions.length > 0) {
console.log(chalk_1.default.blue('1. Start with safe-to-remove items (high confidence)'));
console.log(chalk_1.default.blue('2. Use provided commands for quick removal'));
}
if (reviewSuggestions.length > 0) {
console.log(chalk_1.default.blue('3. Review medium-risk items manually'));
console.log(chalk_1.default.blue('4. Check external consumers and dynamic imports'));
}
if (riskySuggestions.length > 0) {
console.log(chalk_1.default.blue('5. Analyze high-risk items very carefully'));
console.log(chalk_1.default.blue('6. Consider keeping public API exports'));
}
}
}
/**
* Output report in JSON format
*/
function outputJsonFormat(report) {
console.log(JSON.stringify(report, null, 2));
}
/**
* Capitalize first letter of a string
*/
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
/**
* Get color function for confidence level
*/
function getConfidenceColor(confidence) {
switch (confidence) {
case 'high':
return chalk_1.default.green;
case 'medium':
return chalk_1.default.yellow;
case 'low':
return chalk_1.default.red;
default:
return chalk_1.default.gray;
}
}
/**
* Get priority icon for suggestions
*/
function getPriorityIcon(priority) {
switch (priority) {
case 'high':
return chalk_1.default.red('HIGH');
case 'medium':
return chalk_1.default.yellow('MED');
case 'low':
return chalk_1.default.blue('LOW');
default:
return '•';
}
}
/**
* Get help text for dead code analysis
*/
function getDeadCodeHelpText() {
return `
Dead Code Analysis:
--detect-unused Detect unused exports and imports with confidence levels
--format <type> Output format: table, json (default: table)
Performance Options:
Environment Variables:
M2JS_CACHE_ENABLED=true Enable/disable parsing cache (default: true)
M2JS_CACHE_SIZE=1000 Max files in cache (default: 1000)
M2JS_CHUNK_SIZE=50 Files processed per chunk (default: 50)
M2JS_SHOW_PROGRESS=true Show progress bar for large projects (default: true)
M2JS_MAX_FILE_SIZE=10 Max file size in MB (default: 10)
Configuration File (.m2jsrc):
Create a .m2jsrc file in your project root for persistent settings.
Run 'npx m2js --init-config' to generate an example configuration.
Examples:
# Basic dead code analysis
m2js src/**/*.ts --detect-unused
# Analyze specific file with JSON output
m2js src/utils.ts --detect-unused --format json
# Analyze entire project
m2js . --detect-unused
# Analyze with custom performance settings
M2JS_CHUNK_SIZE=100 M2JS_SHOW_PROGRESS=true m2js src --detect-unused
# Generate configuration file
npx m2js --init-config
Features:
✓ High-confidence removal suggestions with automated commands
! Medium-risk items flagged for manual review
!! High-risk items with detailed warnings
Performance metrics and caching for large codebases
Smart detection of public APIs, frameworks, and side-effects
`;
}
//# sourceMappingURL=dead-code-cli.js.map