UNPKG

smart-ast-analyzer

Version:

Advanced AST-based project analysis tool with deep complexity analysis, security scanning, and optional AI enhancement

823 lines (734 loc) 24.5 kB
const fs = require('fs').promises; const path = require('path'); class HTMLGenerator { static async generate(report) { const html = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Smart AST Analysis Report - ${report.projectInfo.framework}</title> <style> ${this.getCSS()} </style> <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> </head> <body> <div class="container"> ${this.generateHeader(report)} ${this.generateNavigation()} ${this.generateContent(report)} ${this.generateFooter()} </div> <script> ${this.generateJavaScript(report)} </script> </body> </html> `; return html.trim(); } static generateHeader(report) { return ` <header class="header"> <h1>🔍 Smart AST Analysis Report</h1> <div class="metadata"> <div class="meta-item"> <strong>Framework:</strong> ${report.projectInfo.framework} </div> <div class="meta-item"> <strong>Generated:</strong> ${new Date(report.timestamp).toLocaleString()} </div> <div class="meta-item"> <strong>Language:</strong> ${report.projectInfo.language} </div> </div> </header> `; } static generateNavigation() { return ` <nav class="navigation"> <ul> <li><a href="#summary" class="nav-link active">Summary</a></li> <li><a href="#apis" class="nav-link">API Analysis</a></li> <li><a href="#components" class="nav-link">Components</a></li> <li><a href="#websockets" class="nav-link">WebSockets</a></li> <li><a href="#auth" class="nav-link">Auth</a></li> <li><a href="#database" class="nav-link">Database</a></li> <li><a href="#performance" class="nav-link">Performance</a></li> <li><a href="#insights" class="nav-link">Insights</a></li> <li><a href="#recommendations" class="nav-link">Recommendations</a></li> </ul> </nav> `; } static generateContent(report) { return ` <main class="content"> ${this.generateSummarySection(report)} ${this.generateAnalysisSection('apis', 'API Analysis', report.results.api)} ${this.generateAnalysisSection('components', 'Component Architecture', report.results.components)} ${this.generateAnalysisSection('websockets', 'WebSocket Events', report.results.websocket)} ${this.generateAnalysisSection('auth', 'Authentication & Authorization', report.results.auth)} ${this.generateAnalysisSection('database', 'Database Analysis', report.results.database)} ${this.generateAnalysisSection('performance', 'Performance Analysis', report.results.performance)} ${this.generateInsightsSection(report)} ${this.generateRecommendationsSection(report)} </main> `; } static generateSummarySection(report) { return ` <section id="summary" class="section"> <h2>📊 Executive Summary</h2> <div class="summary-grid"> ${this.generateSummaryCards(report)} </div> <div class="summary-text"> <p>${report.summary}</p> </div> <div class="metrics-chart"> <canvas id="metricsChart" width="400" height="200"></canvas> </div> </section> `; } static generateSummaryCards(report) { const cards = [ { title: 'Framework', value: report.projectInfo.framework, icon: '🏗️', class: 'framework' }, { title: 'Total Files', value: report.projectInfo.metrics?.totalFiles || 0, icon: '📁', class: 'files' }, { title: 'Lines of Code', value: report.projectInfo.metrics?.totalLines || 0, icon: '📝', class: 'lines' }, { title: 'Dependencies', value: report.projectInfo.dependencies?.total || 0, icon: '📦', class: 'deps' } ]; return cards.map(card => ` <div class="summary-card ${card.class}"> <div class="card-icon">${card.icon}</div> <div class="card-content"> <h3>${card.title}</h3> <div class="card-value">${card.value}</div> </div> </div> `).join(''); } static generateAnalysisSection(id, title, data) { if (!data) return ''; return ` <section id="${id}" class="section"> <h2>${this.getSectionIcon(id)} ${title}</h2> <div class="analysis-content"> ${this.generateAnalysisContent(id, data)} </div> </section> `; } static getSectionIcon(section) { const icons = { apis: '🔗', components: '🧩', websockets: '🔌', auth: '🔐', database: '🗄️', performance: '⚡' }; return icons[section] || '📋'; } static generateAnalysisContent(type, data) { const generators = { apis: this.generateAPIContent.bind(this), components: this.generateComponentContent.bind(this), websockets: this.generateWebSocketContent.bind(this), auth: this.generateAuthContent.bind(this), database: this.generateDatabaseContent.bind(this), performance: this.generatePerformanceContent.bind(this) }; const generator = generators[type]; return generator ? generator(data) : '<p>No data available</p>'; } static generateAPIContent(data) { if (!data.endpoints) return '<p>No API endpoints found</p>'; return ` <div class="api-overview"> <div class="stats-grid"> <div class="stat-card"> <h4>Total Endpoints</h4> <div class="stat-value">${data.endpoints.length}</div> </div> <div class="stat-card"> <h4>Security Issues</h4> <div class="stat-value ${data.securityIssues?.length > 0 ? 'warning' : 'good'}">${data.securityIssues?.length || 0}</div> </div> <div class="stat-card"> <h4>Orphaned Endpoints</h4> <div class="stat-value ${data.orphanedEndpoints?.length > 0 ? 'warning' : 'good'}">${data.orphanedEndpoints?.length || 0}</div> </div> </div> <div class="endpoints-table"> <table> <thead> <tr> <th>Method</th> <th>Path</th> <th>Handler</th> <th>Auth</th> <th>Issues</th> </tr> </thead> <tbody> ${data.endpoints.slice(0, 20).map(endpoint => ` <tr> <td><span class="method ${endpoint.method.toLowerCase()}">${endpoint.method}</span></td> <td><code>${endpoint.path}</code></td> <td>${endpoint.handler}</td> <td>${endpoint.auth?.required ? '✅' : '❌'}</td> <td>${endpoint.issues?.length || 0}</td> </tr> `).join('')} </tbody> </table> </div> </div> `; } static generateComponentContent(data) { if (!data.components) return '<p>No components found</p>'; const componentCount = Object.keys(data.components).length; return ` <div class="component-overview"> <div class="stats-grid"> <div class="stat-card"> <h4>Total Components</h4> <div class="stat-value">${componentCount}</div> </div> <div class="stat-card"> <h4>Unused Components</h4> <div class="stat-value ${data.unusedComponents?.length > 0 ? 'warning' : 'good'}">${data.unusedComponents?.length || 0}</div> </div> <div class="stat-card"> <h4>Circular Dependencies</h4> <div class="stat-value ${data.circularDependencies?.length > 0 ? 'danger' : 'good'}">${data.circularDependencies?.length || 0}</div> </div> </div> <div class="component-grid"> ${Object.entries(data.components).slice(0, 12).map(([name, comp]) => ` <div class="component-card"> <h4>${name}</h4> <div class="component-info"> <div class="info-item"> <strong>Type:</strong> ${comp.type} </div> <div class="info-item"> <strong>Props:</strong> ${Object.keys(comp.props || {}).length} </div> <div class="info-item"> <strong>Issues:</strong> ${comp.issues?.length || 0} </div> </div> </div> `).join('')} </div> </div> `; } static generateWebSocketContent(data) { if (!data.events) return '<p>No WebSocket events found</p>'; const clientEvents = Object.keys(data.events.client || {}).length; const serverEvents = Object.keys(data.events.server || {}).length; return ` <div class="websocket-overview"> <div class="stats-grid"> <div class="stat-card"> <h4>Client Events</h4> <div class="stat-value">${clientEvents}</div> </div> <div class="stat-card"> <h4>Server Events</h4> <div class="stat-value">${serverEvents}</div> </div> <div class="stat-card"> <h4>Rooms</h4> <div class="stat-value">${Object.keys(data.rooms || {}).length}</div> </div> </div> <div class="events-list"> <h4>Client Events</h4> <ul> ${Object.entries(data.events.client || {}).slice(0, 10).map(([name, event]) => ` <li> <strong>${name}</strong> - ${event.file}:${event.line} </li> `).join('')} </ul> </div> </div> `; } static generateAuthContent(data) { if (!data.authentication) return '<p>No authentication analysis available</p>'; return ` <div class="auth-overview"> <div class="stats-grid"> <div class="stat-card"> <h4>Auth Methods</h4> <div class="stat-value">${data.authentication.methods?.length || 0}</div> </div> <div class="stat-card"> <h4>Protected Routes</h4> <div class="stat-value">${data.protectedRoutes?.length || 0}</div> </div> <div class="stat-card"> <h4>Vulnerabilities</h4> <div class="stat-value ${data.security?.vulnerabilities?.length > 0 ? 'danger' : 'good'}">${data.security?.vulnerabilities?.length || 0}</div> </div> </div> <div class="auth-methods"> <h4>Authentication Methods</h4> <ul> ${data.authentication.methods?.map(method => `<li>${method}</li>`).join('') || '<li>No methods detected</li>'} </ul> </div> </div> `; } static generateDatabaseContent(data) { if (!data.models) return '<p>No database analysis available</p>'; return ` <div class="database-overview"> <div class="stats-grid"> <div class="stat-card"> <h4>Models</h4> <div class="stat-value">${data.models.length}</div> </div> <div class="stat-card"> <h4>N+1 Queries</h4> <div class="stat-value ${data.performance?.nPlusOneQueries?.length > 0 ? 'warning' : 'good'}">${data.performance?.nPlusOneQueries?.length || 0}</div> </div> <div class="stat-card"> <h4>Slow Queries</h4> <div class="stat-value ${data.performance?.slowQueries?.length > 0 ? 'warning' : 'good'}">${data.performance?.slowQueries?.length || 0}</div> </div> </div> <div class="models-list"> <h4>Database Models</h4> <div class="models-grid"> ${data.models.slice(0, 8).map(model => ` <div class="model-card"> <h5>${model.name}</h5> <div class="model-info"> <div>Fields: ${model.fields?.length || 0}</div> <div>Relations: ${model.relationships?.length || 0}</div> </div> </div> `).join('')} </div> </div> </div> `; } static generatePerformanceContent(data) { return ` <div class="performance-overview"> <div class="stats-grid"> <div class="stat-card"> <h4>Large Dependencies</h4> <div class="stat-value ${data.bundle?.largeDependencies?.length > 0 ? 'warning' : 'good'}">${data.bundle?.largeDependencies?.length || 0}</div> </div> <div class="stat-card"> <h4>Heavy Components</h4> <div class="stat-value ${data.rendering?.heavyComponents?.length > 0 ? 'warning' : 'good'}">${data.rendering?.heavyComponents?.length || 0}</div> </div> <div class="stat-card"> <h4>Memory Leaks</h4> <div class="stat-value ${data.memory?.potentialLeaks?.length > 0 ? 'danger' : 'good'}">${data.memory?.potentialLeaks?.length || 0}</div> </div> </div> <div class="performance-metrics"> <div class="metric-item"> <h4>Bundle Analysis</h4> <div class="metric-content"> ${data.bundle?.largeDependencies?.slice(0, 5).map(dep => ` <div class="dependency-item"> <strong>${dep.name}</strong>: ${dep.size} </div> `).join('') || '<div>No large dependencies found</div>'} </div> </div> </div> </div> `; } static generateInsightsSection(report) { return ` <section id="insights" class="section"> <h2>💡 Key Insights</h2> <div class="insights-grid"> ${report.insights.map(insight => ` <div class="insight-card"> <div class="insight-icon">🔍</div> <p>${insight}</p> </div> `).join('')} </div> </section> `; } static generateRecommendationsSection(report) { return ` <section id="recommendations" class="section"> <h2>🎯 Recommendations</h2> <div class="recommendations"> ${report.recommendations.map((rec, i) => ` <div class="recommendation ${rec.priority}"> <div class="rec-header"> <h3>${i + 1}. ${rec.title}</h3> <div class="rec-badges"> <span class="badge priority-${rec.priority}">${rec.priority}</span> <span class="badge effort">${rec.effort} effort</span> <span class="badge impact">${rec.impact} impact</span> </div> </div> <p>${rec.description}</p> </div> `).join('')} </div> </section> `; } static generateFooter() { return ` <footer class="footer"> <p>Generated by Smart AST Analyzer • Built with ❤️ for developers</p> </footer> `; } static getCSS() { return ` * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; background: #f8fafc; } .container { max-width: 1200px; margin: 0 auto; background: white; min-height: 100vh; box-shadow: 0 0 20px rgba(0,0,0,0.1); } .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 2rem; text-align: center; } .header h1 { font-size: 2.5rem; margin-bottom: 1rem; } .metadata { display: flex; justify-content: center; gap: 2rem; flex-wrap: wrap; } .meta-item { background: rgba(255,255,255,0.1); padding: 0.5rem 1rem; border-radius: 20px; backdrop-filter: blur(10px); } .navigation { background: #2d3748; padding: 0; position: sticky; top: 0; z-index: 100; } .navigation ul { display: flex; list-style: none; overflow-x: auto; } .nav-link { display: block; padding: 1rem 1.5rem; color: #e2e8f0; text-decoration: none; white-space: nowrap; transition: background 0.3s; } .nav-link:hover, .nav-link.active { background: #4a5568; color: white; } .content { padding: 2rem; } .section { margin-bottom: 3rem; scroll-margin-top: 80px; } .section h2 { font-size: 2rem; margin-bottom: 1.5rem; border-bottom: 3px solid #667eea; padding-bottom: 0.5rem; } .summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1.5rem; margin-bottom: 2rem; } .summary-card { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; padding: 1.5rem; border-radius: 12px; display: flex; align-items: center; gap: 1rem; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } .card-icon { font-size: 2.5rem; } .card-value { font-size: 1.8rem; font-weight: bold; } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; } .stat-card { background: white; border: 1px solid #e2e8f0; border-radius: 8px; padding: 1.5rem; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.05); } .stat-value { font-size: 2rem; font-weight: bold; margin-top: 0.5rem; } .stat-value.good { color: #48bb78; } .stat-value.warning { color: #ed8936; } .stat-value.danger { color: #f56565; } .endpoints-table table { width: 100%; border-collapse: collapse; margin-top: 1rem; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.05); } .endpoints-table th, .endpoints-table td { padding: 1rem; text-align: left; border-bottom: 1px solid #e2e8f0; } .endpoints-table th { background: #f7fafc; font-weight: 600; } .method { padding: 0.25rem 0.5rem; border-radius: 4px; color: white; font-size: 0.8rem; font-weight: bold; } .method.get { background: #48bb78; } .method.post { background: #4299e1; } .method.put { background: #ed8936; } .method.delete { background: #f56565; } .component-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1rem; } .component-card { background: white; border: 1px solid #e2e8f0; border-radius: 8px; padding: 1.5rem; box-shadow: 0 2px 4px rgba(0,0,0,0.05); } .insights-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; } .insight-card { background: white; border: 1px solid #e2e8f0; border-radius: 8px; padding: 1.5rem; display: flex; align-items: flex-start; gap: 1rem; box-shadow: 0 2px 4px rgba(0,0,0,0.05); } .insight-icon { font-size: 1.5rem; flex-shrink: 0; } .recommendation { background: white; border: 1px solid #e2e8f0; border-radius: 8px; padding: 1.5rem; margin-bottom: 1rem; box-shadow: 0 2px 4px rgba(0,0,0,0.05); } .recommendation.high { border-left: 4px solid #f56565; } .recommendation.medium { border-left: 4px solid #ed8936; } .recommendation.low { border-left: 4px solid #48bb78; } .rec-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 1rem; } .rec-badges { display: flex; gap: 0.5rem; flex-wrap: wrap; } .badge { padding: 0.25rem 0.5rem; border-radius: 12px; font-size: 0.8rem; font-weight: 500; } .priority-high { background: #fed7d7; color: #c53030; } .priority-medium { background: #feebc8; color: #c05621; } .priority-low { background: #c6f6d5; color: #2f855a; } .footer { background: #2d3748; color: white; text-align: center; padding: 2rem; } @media (max-width: 768px) { .container { margin: 0; } .content { padding: 1rem; } .metadata { flex-direction: column; align-items: center; } .rec-header { flex-direction: column; gap: 1rem; } } `; } static generateJavaScript(report) { return ` // Initialize Mermaid mermaid.initialize({ startOnLoad: true }); // Chart.js setup const ctx = document.getElementById('metricsChart'); if (ctx) { new Chart(ctx, { type: 'doughnut', data: { labels: ['APIs', 'Components', 'Models', 'Tests'], datasets: [{ data: [ ${report.results.api?.endpoints?.length || 0}, ${Object.keys(report.results.components?.components || {}).length}, ${report.results.database?.models?.length || 0}, ${report.projectInfo.files?.tests?.length || 0} ], backgroundColor: [ '#4299e1', '#48bb78', '#ed8936', '#9f7aea' ] }] }, options: { responsive: true, plugins: { legend: { position: 'bottom' } } } }); } // Navigation document.querySelectorAll('.nav-link').forEach(link => { link.addEventListener('click', function(e) { e.preventDefault(); // Update active state document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active')); this.classList.add('active'); // Scroll to section const target = document.querySelector(this.getAttribute('href')); if (target) { target.scrollIntoView({ behavior: 'smooth' }); } }); }); // Highlight code blocks document.querySelectorAll('code').forEach(block => { block.style.background = '#f7fafc'; block.style.padding = '0.25rem 0.5rem'; block.style.borderRadius = '4px'; block.style.fontSize = '0.9em'; }); console.log('Smart AST Analyzer Report loaded successfully!'); `; } } module.exports = HTMLGenerator;