UNPKG

smart-ast-analyzer

Version:

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

425 lines (351 loc) 15.8 kB
const fs = require('fs').promises; const path = require('path'); const JSONFormatter = require('./json-formatter'); const MarkdownFormatter = require('./markdown-formatter'); const HTMLGenerator = require('./html-generator'); const MetricsVisualizer = require('./metrics-visualizer'); const Helpers = require('../utils/helpers'); class ReportBuilder { constructor(options = {}) { this.outputDir = path.resolve(options.output || './smart-ast-output'); this.formats = options.format === 'all' ? ['json', 'markdown', 'html'] : [options.format]; this.metricsVisualizer = new MetricsVisualizer(options); } async build(analysisResults, projectInfo) { const timestamp = new Date().toISOString(); const reportId = `analysis-${Date.now()}`; const report = { id: reportId, timestamp, projectInfo, results: analysisResults, summary: this.generateSummary(analysisResults, projectInfo), insights: this.generateInsights(analysisResults), recommendations: this.generateRecommendations(analysisResults), metrics: this.calculateMetrics(analysisResults, projectInfo) }; // Ensure output directory exists await fs.mkdir(this.outputDir, { recursive: true }); // Generate reports in requested formats const generatedFiles = []; for (const format of this.formats) { try { const file = await this[`generate${format.toUpperCase()}`](report); generatedFiles.push(file); } catch (error) { console.error(`Failed to generate ${format} report:`, error.message); } } // Generate interactive visualizations if we have deep analysis data if (analysisResults.deepAnalysis) { try { const vizDir = await this.metricsVisualizer.generateVisualization(analysisResults, this.outputDir); generatedFiles.push(vizDir); } catch (error) { console.warn('Failed to generate visualizations:', error.message); } } // Update index await this.updateIndex(report); return { reportId, files: generatedFiles, summary: report.summary, outputDir: this.outputDir }; } async generateJSON(report) { const filename = `${report.id}.json`; const filepath = path.join(this.outputDir, filename); const jsonContent = JSONFormatter.format(report); await fs.writeFile(filepath, jsonContent); return filepath; } async generateMARKDOWN(report) { const filename = `${report.id}.md`; const filepath = path.join(this.outputDir, filename); const markdownContent = MarkdownFormatter.format(report); await fs.writeFile(filepath, markdownContent); return filepath; } async generateHTML(report) { const filename = `${report.id}.html`; const filepath = path.join(this.outputDir, filename); const htmlContent = await HTMLGenerator.generate(report); await fs.writeFile(filepath, htmlContent); return filepath; } generateSummary(results, projectInfo) { const summaryParts = []; // Project overview summaryParts.push(`Analyzed ${projectInfo.framework} project with ${projectInfo.metrics?.totalFiles || 0} files`); // API analysis if (results.api) { const apiCount = results.api.endpoints?.length || 0; const orphaned = results.api.orphanedEndpoints?.length || 0; const securityIssues = results.api.securityIssues?.length || 0; summaryParts.push(`Found ${apiCount} API endpoints`); if (orphaned > 0) summaryParts.push(`${orphaned} orphaned endpoints`); if (securityIssues > 0) summaryParts.push(`${securityIssues} security issues`); } // Component analysis if (results.components) { const compCount = Object.keys(results.components.components || {}).length; const unused = results.components.unusedComponents?.length || 0; const circular = results.components.circularDependencies?.length || 0; summaryParts.push(`analyzed ${compCount} components`); if (unused > 0) summaryParts.push(`${unused} unused components`); if (circular > 0) summaryParts.push(`${circular} circular dependencies`); } // WebSocket analysis if (results.websocket && results.websocket.events) { const clientEvents = Object.keys(results.websocket.events.client || {}).length; const serverEvents = Object.keys(results.websocket.events.server || {}).length; if (clientEvents + serverEvents > 0) { summaryParts.push(`identified ${clientEvents + serverEvents} WebSocket events`); } } // Database analysis if (results.database) { const models = results.database.models?.length || 0; const nPlusOne = results.database.performance?.nPlusOneQueries?.length || 0; if (models > 0) summaryParts.push(`${models} database models`); if (nPlusOne > 0) summaryParts.push(`${nPlusOne} N+1 query issues`); } // Performance analysis if (results.performance) { const largeDeps = results.performance.bundle?.largeDependencies?.length || 0; const heavyComps = results.performance.rendering?.heavyComponents?.length || 0; if (largeDeps > 0) summaryParts.push(`${largeDeps} large dependencies`); if (heavyComps > 0) summaryParts.push(`${heavyComps} performance concerns`); } return summaryParts.join(', ') + '.'; } generateInsights(results) { const insights = []; // API insights if (results.api) { if (results.api.securityIssues?.length > 0) { insights.push(`Found ${results.api.securityIssues.length} security vulnerabilities in API endpoints`); } if (results.api.orphanedEndpoints?.length > 0) { insights.push(`${results.api.orphanedEndpoints.length} API endpoints are not being used and can be removed`); } const unprotectedEndpoints = results.api.endpoints?.filter(e => !e.auth?.required).length || 0; if (unprotectedEndpoints > results.api.endpoints?.length * 0.7) { insights.push(`${unprotectedEndpoints} endpoints lack authentication protection`); } } // Component insights if (results.components) { if (results.components.circularDependencies?.length > 0) { insights.push(`Detected ${results.components.circularDependencies.length} circular dependencies that may cause runtime issues`); } if (results.components.propDrilling?.length > 0) { const deepDrilling = results.components.propDrilling.filter(pd => pd.depth > 3); if (deepDrilling.length > 0) { insights.push(`Found ${deepDrilling.length} instances of deep prop drilling (>3 levels)`); } } if (results.components.unusedComponents?.length > 0) { insights.push(`${results.components.unusedComponents.length} components are defined but never used`); } } // Performance insights if (results.performance) { if (results.performance.bundle?.largeDependencies?.length > 0) { const totalSize = results.performance.bundle.largeDependencies.reduce((sum, dep) => { const sizeMatch = dep.size?.match(/(\d+(?:\.\d+)?)\s*(KB|MB)/); if (sizeMatch) { const value = parseFloat(sizeMatch[1]); const unit = sizeMatch[2]; return sum + (unit === 'MB' ? value * 1024 : value); } return sum; }, 0); if (totalSize > 500) { // > 500KB insights.push(`Large dependencies contribute ${Math.round(totalSize)}KB to bundle size`); } } if (results.performance.rendering?.heavyComponents?.length > 0) { insights.push(`${results.performance.rendering.heavyComponents.length} components may cause performance bottlenecks`); } } // Database insights if (results.database) { if (results.database.performance?.nPlusOneQueries?.length > 0) { insights.push(`${results.database.performance.nPlusOneQueries.length} potential N+1 query problems detected`); } if (results.database.performance?.missingIndexes?.length > 0) { insights.push(`${results.database.performance.missingIndexes.length} database queries could benefit from indexes`); } } // Authentication insights if (results.auth) { const criticalVulns = results.auth.security?.vulnerabilities?.filter(v => v.severity === 'critical').length || 0; if (criticalVulns > 0) { insights.push(`Found ${criticalVulns} critical security vulnerabilities in authentication system`); } } return insights; } generateRecommendations(results) { const recommendations = []; // API recommendations if (results.api?.orphanedEndpoints?.length > 0) { recommendations.push({ title: 'Remove Orphaned API Endpoints', description: `${results.api.orphanedEndpoints.length} API endpoints are not being used by any frontend code. Removing these will reduce attack surface and maintenance overhead.`, priority: 'medium', effort: 'low', impact: 'medium' }); } if (results.api?.securityIssues?.length > 0) { const criticalCount = results.api.securityIssues.filter(i => i.severity === 'critical').length; recommendations.push({ title: 'Fix Critical Security Issues', description: `Address ${results.api.securityIssues.length} security issues in API endpoints, including ${criticalCount} critical vulnerabilities.`, priority: criticalCount > 0 ? 'high' : 'medium', effort: 'medium', impact: 'high' }); } // Component recommendations if (results.components?.unusedComponents?.length > 0) { recommendations.push({ title: 'Remove Unused Components', description: `${results.components.unusedComponents.length} components are defined but never used. Removing them will reduce bundle size and maintenance overhead.`, priority: 'low', effort: 'low', impact: 'medium' }); } if (results.components?.circularDependencies?.length > 0) { recommendations.push({ title: 'Resolve Circular Dependencies', description: `${results.components.circularDependencies.length} circular dependencies detected. These can cause runtime errors and make code harder to maintain.`, priority: 'high', effort: 'medium', impact: 'high' }); } // Performance recommendations if (results.performance?.bundle?.largeDependencies?.length > 0) { recommendations.push({ title: 'Optimize Bundle Size', description: `${results.performance.bundle.largeDependencies.length} large dependencies detected. Consider code splitting, tree shaking, or alternative libraries.`, priority: 'medium', effort: 'medium', impact: 'high' }); } if (results.performance?.rendering?.heavyComponents?.length > 0) { recommendations.push({ title: 'Optimize Component Performance', description: `${results.performance.rendering.heavyComponents.length} components have performance issues. Consider memoization, virtualization, or code splitting.`, priority: 'medium', effort: 'medium', impact: 'high' }); } // Database recommendations if (results.database?.performance?.nPlusOneQueries?.length > 0) { recommendations.push({ title: 'Fix N+1 Query Problems', description: `${results.database.performance.nPlusOneQueries.length} potential N+1 queries found. Use eager loading or query optimization to improve performance.`, priority: 'high', effort: 'medium', impact: 'high' }); } // General recommendations if (Object.keys(results).length === 1 && results.api) { recommendations.push({ title: 'Perform Comprehensive Analysis', description: 'Only API analysis was performed. Consider running a full analysis to get insights into components, performance, and security.', priority: 'low', effort: 'low', impact: 'medium' }); } return recommendations.slice(0, 10); // Limit to top 10 recommendations } calculateMetrics(results, projectInfo) { const metrics = { totalFiles: projectInfo.metrics?.totalFiles || 0, codeFiles: projectInfo.metrics?.codeFiles || 0, totalLines: projectInfo.metrics?.totalLines || 0, analysisTypes: Object.keys(results).length }; // Calculate quality scores (0-100) let securityScore = 100; let performanceScore = 100; let maintainabilityScore = 100; // Security score if (results.api?.securityIssues) { const criticalIssues = results.api.securityIssues.filter(i => i.severity === 'critical').length; const highIssues = results.api.securityIssues.filter(i => i.severity === 'high').length; securityScore -= (criticalIssues * 20) + (highIssues * 10); } if (results.auth?.security?.vulnerabilities) { const criticalVulns = results.auth.security.vulnerabilities.filter(v => v.severity === 'critical').length; securityScore -= criticalVulns * 15; } // Performance score if (results.performance?.bundle?.largeDependencies?.length > 0) { performanceScore -= Math.min(results.performance.bundle.largeDependencies.length * 5, 30); } if (results.performance?.rendering?.heavyComponents?.length > 0) { performanceScore -= Math.min(results.performance.rendering.heavyComponents.length * 10, 40); } if (results.database?.performance?.nPlusOneQueries?.length > 0) { performanceScore -= Math.min(results.database.performance.nPlusOneQueries.length * 15, 50); } // Maintainability score if (results.components?.circularDependencies?.length > 0) { maintainabilityScore -= results.components.circularDependencies.length * 20; } if (results.components?.unusedComponents?.length > 0) { maintainabilityScore -= Math.min(results.components.unusedComponents.length * 5, 25); } if (results.api?.orphanedEndpoints?.length > 0) { maintainabilityScore -= Math.min(results.api.orphanedEndpoints.length * 3, 15); } // Ensure scores are between 0 and 100 securityScore = Math.max(0, Math.min(100, securityScore)); performanceScore = Math.max(0, Math.min(100, performanceScore)); maintainabilityScore = Math.max(0, Math.min(100, maintainabilityScore)); metrics.securityScore = securityScore; metrics.performanceScore = performanceScore; metrics.maintainabilityScore = maintainabilityScore; metrics.overallScore = Math.round((securityScore + performanceScore + maintainabilityScore) / 3); return metrics; } async updateIndex(report) { const indexPath = path.join(this.outputDir, 'index.json'); let index = []; try { const existing = await fs.readFile(indexPath, 'utf-8'); index = JSON.parse(existing); } catch (e) { // Index doesn't exist yet } index.unshift({ id: report.id, timestamp: report.timestamp, projectPath: report.projectInfo.path, framework: report.projectInfo.framework, language: report.projectInfo.language, summary: report.summary, overallScore: report.metrics.overallScore, analysisTypes: Object.keys(report.results) }); // Keep only last 50 reports index = index.slice(0, 50); await fs.writeFile(indexPath, JSON.stringify(index, null, 2)); } } module.exports = ReportBuilder;