UNPKG

@neurolint/cli

Version:

NeuroLint CLI for React/Next.js modernization with advanced 6-layer orchestration and intelligent AST transformations

581 lines (539 loc) 19.4 kB
import chalk from "chalk"; import fs from "fs-extra"; import path from "path"; export function formatResults(results: any[], format: string, outputFile?: string) { switch (format) { case "json": const basicJson = JSON.stringify(results, null, 2); if (outputFile) { fs.writeFileSync(outputFile, basicJson); console.log(chalk.green(`JSON results written to: ${outputFile}`)); } else { console.log(basicJson); } break; case "detailed-json": const detailedJson = formatDetailedJSON(results); if (outputFile) { fs.writeFileSync(outputFile, detailedJson); console.log(chalk.green(`Detailed JSON results written to: ${outputFile}`)); } else { console.log(detailedJson); } break; case "compact-json": const compactJson = formatCompactJSON(results); if (outputFile) { fs.writeFileSync(outputFile, compactJson); console.log(chalk.green(`Compact JSON results written to: ${outputFile}`)); } else { console.log(compactJson); } break; case "html": const htmlOutput = formatHTMLReport(results); const htmlFile = outputFile || `neurolint-report-${Date.now()}.html`; fs.writeFileSync(htmlFile, htmlOutput); console.log(chalk.green(`HTML report generated: ${htmlFile}`)); break; case "summary": formatSummary(results); break; case "table": default: formatTable(results); break; } } function formatTable(results: any[]) { console.log(chalk.white("\nAnalysis Results:\n")); results.forEach((result, index) => { const status = result.success ? chalk.white("PASS") : chalk.white("FAIL"); console.log(`${status} ${result.file}`); if (result.success && result.layers) { result.layers.forEach((layer: any) => { const layerStatus = layer.status === "success" ? chalk.white("PASS") : chalk.gray("SKIP"); const changes = layer.changes || 0; console.log( ` ${layerStatus} Layer ${layer.id}: ${layer.name} ${changes > 0 ? chalk.gray(`(${changes} changes)`) : ""}`, ); if (layer.insights && layer.insights.length > 0) { layer.insights.forEach((insight: any) => { const severity = chalk.gray("•"); console.log(` ${severity} ${insight.message}`); }); } }); } else if (!result.success) { console.log(` ${chalk.white("ERROR:")} ${result.error}`); } if (index < results.length - 1) { console.log(""); } }); } function formatSummary(results: any[]) { const successful = results.filter((r) => r.success); const failed = results.filter((r) => !r.success); console.log(chalk.white("\nAnalysis Summary\n")); console.log(chalk.white(`Files processed: ${results.length}`)); console.log(chalk.white(`Successful: ${successful.length}`)); if (failed.length > 0) { console.log(chalk.white(`Failed: ${failed.length}`)); } if (successful.length > 0) { // Calculate layer statistics const layerStats: Record< string, { name: string; files: number; changes: number; issues: number } > = {}; let totalIssues = 0; successful.forEach((result: any) => { if (result.layers) { result.layers.forEach((layer: any) => { if (!layerStats[layer.id]) { layerStats[layer.id] = { name: layer.name, files: 0, changes: 0, issues: 0, }; } layerStats[layer.id].files++; layerStats[layer.id].changes += layer.changes || 0; if (layer.insights) { layerStats[layer.id].issues += layer.insights.length; totalIssues += layer.insights.length; } }); } }); console.log(chalk.white(`Total issues found: ${totalIssues}`)); console.log(chalk.white("\nLayer Performance:")); Object.entries(layerStats).forEach(([layerId, stats]: [string, any]) => { console.log(chalk.white(`Layer ${layerId} (${stats.name}):`)); console.log( chalk.gray( ` Files: ${stats.files}, Changes: ${stats.changes}, Issues: ${stats.issues}`, ), ); }); } } function formatDetailedJSON(results: any[]): string { const timestamp = new Date().toISOString(); const successful = results.filter((r) => r.success); const failed = results.filter((r) => !r.success); // Calculate comprehensive statistics const stats = { totalFiles: results.length, successfulFiles: successful.length, failedFiles: failed.length, totalIssues: 0, totalChanges: 0, totalExecutionTime: 0, layerBreakdown: {} as Record<string, any> }; // Process layer statistics successful.forEach((result: any) => { if (result.layers) { result.layers.forEach((layer: any) => { const layerId = layer.id; if (!stats.layerBreakdown[layerId]) { stats.layerBreakdown[layerId] = { name: layer.name, filesProcessed: 0, totalChanges: 0, totalIssues: 0, successRate: 0, avgExecutionTime: 0, issues: [] }; } stats.layerBreakdown[layerId].filesProcessed++; stats.layerBreakdown[layerId].totalChanges += layer.changes || 0; if (layer.insights) { stats.layerBreakdown[layerId].totalIssues += layer.insights.length; stats.layerBreakdown[layerId].issues.push(...layer.insights); stats.totalIssues += layer.insights.length; } stats.totalChanges += layer.changes || 0; if (layer.executionTime) { stats.totalExecutionTime += layer.executionTime; } }); } }); // Calculate success rates Object.keys(stats.layerBreakdown).forEach(layerId => { const layer = stats.layerBreakdown[layerId]; layer.successRate = Math.round((layer.filesProcessed / successful.length) * 100); }); const detailedReport = { metadata: { timestamp, version: "1.1.0", generatedBy: "NeuroLint CLI", analysisDuration: stats.totalExecutionTime }, summary: stats, results: results.map(result => ({ file: result.file, success: result.success, error: result.error || null, layers: result.layers || [], metrics: { linesOfCode: result.linesOfCode || 0, complexity: result.complexity || 0, maintainabilityIndex: result.maintainabilityIndex || 0 }, recommendations: result.recommendations || [] })), layerDetails: stats.layerBreakdown, failedFiles: failed.map(result => ({ file: result.file, error: result.error, timestamp: new Date().toISOString() })) }; return JSON.stringify(detailedReport, null, 2); } function formatCompactJSON(results: any[]): string { const compactResults = results.map(result => ({ f: result.file, // file s: result.success ? 1 : 0, // success e: result.error || null, // error l: result.layers ? result.layers.map((layer: any) => ({ i: layer.id, // id n: layer.name, // name c: layer.changes || 0, // changes t: layer.executionTime || 0, // time s: layer.status === 'success' ? 1 : 0, // status is: layer.insights ? layer.insights.length : 0 // issues count })) : [] })); const summary = { t: new Date().toISOString(), // timestamp tf: results.length, // total files sf: results.filter(r => r.success).length, // successful files ff: results.filter(r => !r.success).length // failed files }; return JSON.stringify({ s: summary, r: compactResults }); } function formatHTMLReport(results: any[]): string { const timestamp = new Date().toISOString(); const successful = results.filter((r) => r.success); const failed = results.filter((r) => !r.success); // Calculate statistics const stats = { totalFiles: results.length, successfulFiles: successful.length, failedFiles: failed.length, totalIssues: 0, totalChanges: 0, layerStats: {} as Record<string, any> }; successful.forEach((result: any) => { if (result.layers) { result.layers.forEach((layer: any) => { const layerId = layer.id; if (!stats.layerStats[layerId]) { stats.layerStats[layerId] = { name: layer.name, files: 0, changes: 0, issues: 0, topIssues: [] }; } stats.layerStats[layerId].files++; stats.layerStats[layerId].changes += layer.changes || 0; if (layer.insights) { stats.layerStats[layerId].issues += layer.insights.length; stats.totalIssues += layer.insights.length; // Store top issues for this layer layer.insights.forEach((insight: any) => { if (stats.layerStats[layerId].topIssues.length < 5) { stats.layerStats[layerId].topIssues.push({ ...insight, file: result.file }); } }); } stats.totalChanges += layer.changes || 0; }); } }); const html = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>NeuroLint Analysis Report</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0a0a0a; color: #ffffff; line-height: 1.6; padding: 20px; } .container { max-width: 1200px; margin: 0 auto; } .header { background: rgba(33, 150, 243, 0.1); padding: 30px; border-radius: 12px; border: 1px solid rgba(33, 150, 243, 0.3); margin-bottom: 30px; text-align: center; } .header h1 { color: #2196f3; font-size: 2.5rem; margin-bottom: 10px; font-weight: 700; } .header p { color: #888; font-size: 1.1rem; } .metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 30px; } .metric-card { background: rgba(255, 255, 255, 0.05); padding: 25px; border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.1); text-align: center; transition: transform 0.2s ease, box-shadow 0.2s ease; } .metric-card:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(33, 150, 243, 0.15); } .metric-value { font-size: 2.5rem; font-weight: bold; color: #2196f3; margin-bottom: 5px; } .metric-label { color: #ccc; font-size: 0.9rem; text-transform: uppercase; letter-spacing: 1px; } .section { background: rgba(255, 255, 255, 0.03); padding: 25px; margin-bottom: 25px; border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.1); } .section h2 { color: #2196f3; margin-bottom: 20px; font-size: 1.5rem; border-bottom: 2px solid rgba(33, 150, 243, 0.3); padding-bottom: 10px; } .layer-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; } .layer-card { background: rgba(255, 255, 255, 0.05); padding: 20px; border-radius: 8px; border-left: 4px solid #2196f3; } .layer-title { color: #2196f3; font-weight: 600; margin-bottom: 10px; font-size: 1.1rem; } .layer-stats { display: flex; justify-content: space-between; margin-bottom: 15px; font-size: 0.9rem; color: #ccc; } .issue-list { list-style: none; } .issue-item { background: rgba(255, 255, 255, 0.05); padding: 10px; margin: 8px 0; border-radius: 6px; border-left: 3px solid; font-size: 0.9rem; } .issue-error { border-left-color: #f44336; } .issue-warning { border-left-color: #ff9800; } .issue-info { border-left-color: #2196f3; } .file-table { width: 100%; border-collapse: collapse; margin-top: 20px; } .file-table th, .file-table td { padding: 12px; text-align: left; border-bottom: 1px solid rgba(255, 255, 255, 0.1); } .file-table th { background: rgba(33, 150, 243, 0.1); color: #2196f3; font-weight: 600; } .status-success { color: #4caf50; } .status-failed { color: #f44336; } .footer { text-align: center; margin-top: 40px; padding: 20px; color: #666; border-top: 1px solid rgba(255, 255, 255, 0.1); } @media (max-width: 768px) { .metrics-grid { grid-template-columns: 1fr; } .layer-grid { grid-template-columns: 1fr; } .header h1 { font-size: 2rem; } .container { padding: 10px; } } </style> </head> <body> <div class="container"> <div class="header"> <h1>NeuroLint Analysis Report</h1> <p>Generated on ${new Date(timestamp).toLocaleString()}</p> </div> <div class="metrics-grid"> <div class="metric-card"> <div class="metric-value">${stats.totalFiles}</div> <div class="metric-label">Files Analyzed</div> </div> <div class="metric-card"> <div class="metric-value">${stats.totalIssues}</div> <div class="metric-label">Issues Found</div> </div> <div class="metric-card"> <div class="metric-value">${stats.totalChanges}</div> <div class="metric-label">Changes Applied</div> </div> <div class="metric-card"> <div class="metric-value">${Object.keys(stats.layerStats).length}</div> <div class="metric-label">Layers Processed</div> </div> </div> ${Object.keys(stats.layerStats).length > 0 ? ` <div class="section"> <h2>Layer Analysis</h2> <div class="layer-grid"> ${Object.entries(stats.layerStats).map(([layerId, data]: [string, any]) => ` <div class="layer-card"> <div class="layer-title">Layer ${layerId}: ${data.name}</div> <div class="layer-stats"> <span>Files: ${data.files}</span> <span>Issues: ${data.issues}</span> <span>Changes: ${data.changes}</span> </div> ${data.topIssues.length > 0 ? ` <ul class="issue-list"> ${data.topIssues.map((issue: any) => ` <li class="issue-item issue-${issue.severity || 'info'}"> <strong>${issue.message || issue.description}</strong> <div style="font-size: 0.8rem; color: #888; margin-top: 4px;">${issue.file}</div> </li> `).join('')} </ul> ` : '<p style="color: #888; font-style: italic;">No issues found</p>'} </div> `).join('')} </div> </div> ` : ''} <div class="section"> <h2>File Results</h2> <table class="file-table"> <thead> <tr> <th>File</th> <th>Status</th> <th>Issues</th> <th>Changes</th> </tr> </thead> <tbody> ${results.map((result: any) => { const totalIssues = result.layers ? result.layers.reduce((sum: number, layer: any) => sum + (layer.insights?.length || 0), 0) : 0; const totalChanges = result.layers ? result.layers.reduce((sum: number, layer: any) => sum + (layer.changes || 0), 0) : 0; return ` <tr> <td>${result.file}</td> <td class="${result.success ? 'status-success' : 'status-failed'}"> ${result.success ? 'SUCCESS' : 'FAILED'} </td> <td>${totalIssues}</td> <td>${totalChanges}</td> </tr> `; }).join('')} </tbody> </table> </div> ${failed.length > 0 ? ` <div class="section"> <h2>Failed Files</h2> <ul class="issue-list"> ${failed.map((result: any) => ` <li class="issue-item issue-error"> <strong>${result.file}</strong> <div style="margin-top: 5px; color: #ccc;">${result.error}</div> </li> `).join('')} </ul> </div> ` : ''} <div class="section"> <h2>Recommendations</h2> <ul style="list-style: disc; margin-left: 20px; color: #ccc;"> <li>Review all detected issues and apply necessary fixes</li> <li>Focus on high-severity issues first to maximize impact</li> <li>Test your application thoroughly after applying changes</li> <li>Consider upgrading to NeuroLint Professional for advanced features</li> ${stats.totalIssues > 0 ? '<li>Run NeuroLint fix command to automatically resolve issues</li>' : ''} </ul> </div> <div class="footer"> <p>Generated by NeuroLint CLI v1.0.0 | Visit <a href="https://neurolint.dev" style="color: #2196f3;">neurolint.dev</a> for more information</p> </div> </div> </body> </html>`; return html; } // Export individual formatting functions for reuse export { formatDetailedJSON, formatCompactJSON, formatHTMLReport, formatTable, formatSummary };