UNPKG

@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
"use strict"; /** * 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