@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
310 lines (303 loc) • 14 kB
JavaScript
;
/**
* CLI Integration for Duplicate Code Analysis
* Handles --detect-duplicates flag and LLM-friendly output formatting
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeDuplicateCodeAnalysis = executeDuplicateCodeAnalysis;
exports.getDuplicateCodeHelpText = getDuplicateCodeHelpText;
const chalk_1 = __importDefault(require("chalk"));
const duplicate_code_analyzer_1 = require("./duplicate-code-analyzer");
/**
* Execute duplicate code analysis and output LLM-friendly results
*/
async function executeDuplicateCodeAnalysis(files, options = {
format: 'table',
includeContext: true,
includeSuggestions: true,
}) {
try {
console.log(chalk_1.default.cyan.bold('Duplicate Code Analysis Report'));
console.log(chalk_1.default.dim(`Analyzing ${files.length} files...\n`));
const report = await (0, duplicate_code_analyzer_1.analyzeDuplicateCode)(files, options);
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 { duplicates, suggestions, metrics, projectPath } = report;
// Project info
console.log(chalk_1.default.dim(`Project: ${projectPath}`));
console.log(chalk_1.default.dim(`Files analyzed: ${metrics.totalFiles}`));
console.log(chalk_1.default.dim(`Analysis time: ${metrics.analysisTimeMs}ms\n`));
// Summary section
if (duplicates.length === 0) {
console.log(chalk_1.default.green.bold('Great! No duplicate code found!'));
console.log(chalk_1.default.green('All code appears to be unique.\n'));
}
else {
console.log(chalk_1.default.red.bold(`Duplicate Code Found (${duplicates.length} blocks):`));
console.log(chalk_1.default.gray('┌─────────────────────────────────────────────────┐'));
duplicates.forEach((duplicate, index) => {
const isLast = index === duplicates.length - 1;
const complexity = getComplexityIndicator(duplicate.complexity);
const duplicateType = chalk_1.default.cyan(`[${duplicate.type.toUpperCase()}]`);
console.log(chalk_1.default.gray('│ ') +
chalk_1.default.yellow(`Block ${duplicate.id}`) +
' ' +
duplicateType +
' ' +
complexity);
console.log(chalk_1.default.gray('│ ') +
chalk_1.default.gray('└─ ') +
chalk_1.default.white(`${duplicate.lines} lines, ${duplicate.tokens} tokens`));
console.log(chalk_1.default.gray('│ ') + chalk_1.default.dim(`${duplicate.similarity}% similarity`));
// Show locations
duplicate.locations.forEach((location, locIndex) => {
const isLastLocation = locIndex === duplicate.locations.length - 1;
const locationPrefix = isLastLocation ? '└─' : '├─';
console.log(chalk_1.default.gray('│ ') +
chalk_1.default.gray(locationPrefix) +
' ' +
chalk_1.default.blue(`${location.file}:${location.startLine}-${location.endLine}`));
if (location.context) {
const contextPrefix = isLastLocation ? ' ' : '│ ';
console.log(chalk_1.default.gray('│ ') +
chalk_1.default.gray(contextPrefix) +
' ' +
chalk_1.default.dim(`in ${location.context}`));
}
});
// Show code preview if requested
if (options.includeContext && duplicate.code) {
const preview = getCodePreview(duplicate.code);
console.log(chalk_1.default.gray('│ ') + chalk_1.default.dim('Code preview:'));
console.log(chalk_1.default.gray('│ ') + chalk_1.default.gray(preview));
}
if (!isLast) {
console.log(chalk_1.default.gray('│'));
}
});
console.log(chalk_1.default.gray('└─────────────────────────────────────────────────┘\n'));
}
// Impact summary
if (duplicates.length > 0) {
console.log(chalk_1.default.blue.bold('Impact Analysis:'));
console.log(chalk_1.default.blue(`• Total duplicated lines: ${metrics.duplicatedLines}`));
console.log(chalk_1.default.blue(`• Duplication percentage: ${metrics.duplicationPercentage.toFixed(1)}%`));
console.log(chalk_1.default.blue(`• Average duplicate size: ${Math.round(metrics.averageDuplicateSize)} lines`));
console.log(chalk_1.default.blue(`• Largest duplicate: ${metrics.largestDuplicateSize} lines`));
console.log(chalk_1.default.blue(`• Potential savings: ${metrics.potentialSavings} lines`));
if (metrics.mostDuplicatedFiles.length > 0) {
console.log(chalk_1.default.blue('• Most affected files:'));
metrics.mostDuplicatedFiles.slice(0, 3).forEach(file => {
console.log(chalk_1.default.blue(` - ${file}`));
});
}
console.log();
}
// Refactoring suggestions section
if (options.includeSuggestions !== false &&
suggestions &&
suggestions.length > 0) {
console.log(chalk_1.default.blue.bold('Refactoring Suggestions:'));
console.log(chalk_1.default.gray('┌─────────────────────────────────────────────────┐'));
// Group suggestions by priority
const highPriority = suggestions.filter(s => s.priority === 'high');
const mediumPriority = suggestions.filter(s => s.priority === 'medium');
const lowPriority = suggestions.filter(s => s.priority === 'low');
// Show high priority suggestions first
if (highPriority.length > 0) {
console.log(chalk_1.default.gray('│ ') + chalk_1.default.red.bold('HIGH PRIORITY:'));
highPriority.slice(0, 3).forEach((suggestion, index) => {
const isLast = index === Math.min(2, highPriority.length - 1);
outputSuggestion(suggestion, isLast);
});
if (highPriority.length > 3) {
console.log(chalk_1.default.gray('│ ') +
chalk_1.default.dim(`... and ${highPriority.length - 3} more high priority suggestions`));
}
console.log(chalk_1.default.gray('│'));
}
// Show medium priority suggestions
if (mediumPriority.length > 0) {
console.log(chalk_1.default.gray('│ ') + chalk_1.default.yellow.bold('MEDIUM PRIORITY:'));
mediumPriority.slice(0, 2).forEach((suggestion, index) => {
const isLast = index === Math.min(1, mediumPriority.length - 1);
outputSuggestion(suggestion, isLast);
});
if (mediumPriority.length > 2) {
console.log(chalk_1.default.gray('│ ') +
chalk_1.default.dim(`... and ${mediumPriority.length - 2} more medium priority suggestions`));
}
console.log(chalk_1.default.gray('│'));
}
// Show count of low priority suggestions
if (lowPriority.length > 0) {
console.log(chalk_1.default.gray('│ ') + chalk_1.default.blue.bold('LOW PRIORITY:'));
console.log(chalk_1.default.gray('│ ') +
chalk_1.default.blue(`${lowPriority.length} additional suggestions available`));
console.log(chalk_1.default.gray('│ ') +
chalk_1.default.blue('Run with --format json for complete list'));
}
console.log(chalk_1.default.gray('└─────────────────────────────────────────────────┘\n'));
}
// Next steps guidance
if (duplicates.length > 0) {
console.log(chalk_1.default.blue.bold('Recommended Next Steps:'));
const highPrioritySuggestions = suggestions?.filter(s => s.priority === 'high') || [];
const mediumPrioritySuggestions = suggestions?.filter(s => s.priority === 'medium') || [];
if (highPrioritySuggestions.length > 0) {
console.log(chalk_1.default.blue('1. Address high priority duplications first'));
console.log(chalk_1.default.blue('2. Focus on extract-function refactoring for immediate wins'));
}
if (mediumPrioritySuggestions.length > 0) {
console.log(chalk_1.default.blue('3. Plan medium priority refactoring in next sprint'));
console.log(chalk_1.default.blue('4. Consider extract-class for larger duplications'));
}
if (metrics.duplicationPercentage > 10) {
console.log(chalk_1.default.blue('5. Set up pre-commit hooks to prevent new duplications'));
console.log(chalk_1.default.blue('6. Consider code review guidelines for duplication'));
}
console.log(chalk_1.default.blue('7. Re-run analysis after refactoring to track progress'));
}
}
/**
* Output a single suggestion
*/
function outputSuggestion(suggestion, isLast) {
const priorityIcon = getPriorityIcon(suggestion.priority);
const effortBadge = getEffortBadge(suggestion.effort);
console.log(chalk_1.default.gray('│ ') + priorityIcon + ' ' + chalk_1.default.white(suggestion.description));
console.log(chalk_1.default.gray('│ ') +
chalk_1.default.dim(`Type: ${suggestion.type} | Effort: `) +
effortBadge);
console.log(chalk_1.default.gray('│ ') + chalk_1.default.dim(suggestion.impact));
if (suggestion.suggestedName) {
console.log(chalk_1.default.gray('│ ') +
chalk_1.default.cyan(`Suggested name: ${suggestion.suggestedName}`));
}
if (suggestion.example) {
const exampleLines = suggestion.example.split('\n').slice(0, 2);
console.log(chalk_1.default.gray('│ ') + chalk_1.default.dim('Example:'));
exampleLines.forEach(line => {
console.log(chalk_1.default.gray('│ ') + chalk_1.default.gray(line));
});
}
if (!isLast) {
console.log(chalk_1.default.gray('│'));
}
}
/**
* Output report in JSON format
*/
function outputJsonFormat(report) {
console.log(JSON.stringify(report, null, 2));
}
/**
* Get complexity indicator
*/
function getComplexityIndicator(complexity) {
if (complexity >= 8)
return chalk_1.default.red('[HIGH COMPLEXITY]');
if (complexity >= 5)
return chalk_1.default.yellow('[MEDIUM COMPLEXITY]');
return chalk_1.default.green('[LOW COMPLEXITY]');
}
/**
* 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 effort badge
*/
function getEffortBadge(effort) {
switch (effort) {
case 'high':
return chalk_1.default.red(effort);
case 'medium':
return chalk_1.default.yellow(effort);
case 'low':
return chalk_1.default.green(effort);
default:
return effort;
}
}
/**
* Get code preview (first few lines)
*/
function getCodePreview(code) {
const lines = code.split('\n');
const preview = lines.slice(0, 2).join(' ').trim();
return preview.length > 60 ? preview.substring(0, 60) + '...' : preview;
}
/**
* Get help text for duplicate code analysis
*/
function getDuplicateCodeHelpText() {
return `
Duplicate Code Analysis:
--detect-duplicates Analyze code for duplicate blocks using jscpd
--min-lines <number> Minimum lines to consider duplicate (default: 5)
--min-tokens <number> Minimum tokens to consider duplicate (default: 50)
--format <type> Output format: table, json (default: table)
Configuration Options:
Environment Variables:
M2JS_DUPLICATE_MIN_LINES=5 Minimum lines for duplication
M2JS_DUPLICATE_MIN_TOKENS=50 Minimum tokens for duplication
M2JS_DUPLICATE_IGNORE=pattern Comma-separated ignore patterns
Configuration File (.m2jsrc):
Add duplicate detection settings to your .m2jsrc file:
{
"duplicateCode": {
"minLines": 5,
"minTokens": 50,
"ignore": ["*.test.ts", "*.spec.ts"]
}
}
Examples:
# Basic duplicate code analysis
m2js src/ --detect-duplicates
# Custom thresholds
m2js src/ --detect-duplicates --min-lines 10 --min-tokens 100
# JSON output for tool integration
m2js src/ --detect-duplicates --format json
# Analyze specific files
m2js src/utils.ts src/helpers.ts --detect-duplicates
Features:
✓ Integration with jscpd for accurate detection
✓ LLM-friendly reporting with context and suggestions
✓ Intelligent refactoring recommendations
✓ Complexity analysis and priority scoring
✓ Support for TypeScript, JavaScript, JSX, TSX
✓ Configurable thresholds and ignore patterns
`;
}
//# sourceMappingURL=duplicate-code-cli.js.map