UNPKG

static-code-analyzer

Version:

Universal static code analysis tool for calculating API operation costs across multiple frameworks (NestJS, Express, Fastify)

401 lines (396 loc) 18.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ReportGenerator = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); /** * Report generator for different output formats */ class ReportGenerator { /** * Generate a report for a single file analysis */ static generateFileReport(analysis, format = 'text') { switch (format) { case 'json': return this.generateJsonReport(analysis); case 'markdown': return this.generateMarkdownReport(analysis); case 'html': return this.generateHtmlReport(analysis); default: return this.generateTextReport(analysis); } } /** * Generate a project summary report */ static generateProjectReport(files, format = 'text') { const projectSummary = this.calculateProjectSummary(files); switch (format) { case 'json': return JSON.stringify({ files, summary: projectSummary }, null, 2); case 'markdown': return this.generateProjectMarkdownReport(files, projectSummary); case 'html': return this.generateProjectHtmlReport(files, projectSummary); default: return this.generateProjectTextReport(files, projectSummary); } } static generateTextReport(analysis) { let report = '\n' + '='.repeat(80) + '\n'; report += ' 🔍 API COST ANALYSIS REPORT\n'; report += '='.repeat(80) + '\n'; report += `📄 File: ${analysis.fileName}\n`; report += `📅 Generated: ${new Date().toLocaleString()}\n\n`; if (analysis.methods.length === 0) { report += '❌ No methods with database operations found.\n'; return report; } // File summary report += '📊 FILE SUMMARY\n'; report += '-'.repeat(40) + '\n'; report += `Methods Analyzed: ${analysis.summary.totalMethods}\n`; report += `Total Execution Paths: ${analysis.summary.totalPaths}\n`; report += `Cost Range: ${analysis.summary.costRange.min} - ${analysis.summary.costRange.max} requests\n`; report += `Average Cost: ${analysis.summary.costRange.average} requests\n\n`; // Method details analysis.methods.forEach(method => { report += this.generateMethodReport(method); report += '\n' + '-'.repeat(60) + '\n\n'; }); report += this.generateOptimizationGuide(); return report; } static generateMethodReport(method) { let report = `📋 ${method.className ? method.className + '.' : ''}${method.methodName}\n`; report += ` Total Execution Paths: ${method.totalPaths}\n`; report += ` Cost Range: ${method.summary.minCost} - ${method.summary.maxCost} requests\n`; report += ` Average Cost: ${method.summary.avgCost} requests\n\n`; report += '🌳 EXECUTION PATHS:\n'; method.paths.forEach((path, index) => { report += `\n ${index + 1}. ${path.name} 💰 Cost: ${path.totalCost}\n`; if (path.conditions.length > 0) { report += ` 🔀 Conditions: ${path.conditions.join(', ')}\n`; } report += ' 📊 Operations:\n'; path.parallelGroups.forEach((group, groupIndex) => { if (group.length > 1) { const maxCost = Math.max(...group.map(op => op.cost)); report += ` [PARALLEL GROUP ${groupIndex + 1}] (Max cost: ${maxCost})\n`; group.forEach(op => { report += ` ├─ Line ${op.line}: ${op.method} (${op.cost}) ${op.framework ? `[${op.framework}]` : ''} - ${op.expression}\n`; }); } else { const op = group[0]; const compositeText = op.isComposite ? ' [COMPOSITE]' : ''; const frameworkText = op.framework ? ` [${op.framework}]` : ''; report += ` ● Line ${op.line}: ${op.method} (${op.cost})${compositeText}${frameworkText} - ${op.expression}\n`; } }); }); if (method.recommendations.length > 0) { report += '\n 🎯 RECOMMENDATIONS:\n'; method.recommendations.forEach(rec => { report += ` ${rec}\n`; }); } return report; } static generateJsonReport(analysis) { return JSON.stringify(analysis, null, 2); } static generateMarkdownReport(analysis) { let md = `# API Cost Analysis Report\n\n`; md += `**File:** ${analysis.fileName}\n`; md += `**Generated:** ${new Date().toLocaleString()}\n\n`; if (analysis.methods.length === 0) { md += '❌ No methods with database operations found.\n'; return md; } // Summary table md += `## Summary\n\n`; md += `| Metric | Value |\n`; md += `|--------|-------|\n`; md += `| Methods Analyzed | ${analysis.summary.totalMethods} |\n`; md += `| Total Execution Paths | ${analysis.summary.totalPaths} |\n`; md += `| Cost Range | ${analysis.summary.costRange.min} - ${analysis.summary.costRange.max} requests |\n`; md += `| Average Cost | ${analysis.summary.costRange.average} requests |\n\n`; // Method details md += `## Method Analysis\n\n`; analysis.methods.forEach(method => { md += `### ${method.className ? method.className + '.' : ''}${method.methodName}\n\n`; md += `- **Execution Paths:** ${method.totalPaths}\n`; md += `- **Cost Range:** ${method.summary.minCost} - ${method.summary.maxCost} requests\n`; md += `- **Average Cost:** ${method.summary.avgCost} requests\n\n`; if (method.paths.length > 0) { md += `#### Execution Paths\n\n`; method.paths.forEach((path, index) => { md += `${index + 1}. **${path.name}** (Cost: ${path.totalCost})\n`; if (path.conditions.length > 0) { md += ` - Conditions: ${path.conditions.join(', ')}\n`; } md += ` - Operations: ${path.operations.length}\n`; md += `\n`; }); } if (method.recommendations.length > 0) { md += `#### Recommendations\n\n`; method.recommendations.forEach(rec => { md += `- ${rec}\n`; }); md += `\n`; } }); return md; } static generateHtmlReport(analysis) { let html = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>API Cost Analysis Report - ${analysis.fileName}</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 40px; background: #f5f5f5; } .container { max-width: 1200px; margin: 0 auto; background: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } h1 { color: #2c3e50; border-bottom: 3px solid #3498db; padding-bottom: 10px; } h2 { color: #34495e; margin-top: 30px; } h3 { color: #7f8c8d; } .summary { background: #ecf0f1; padding: 20px; border-radius: 5px; margin: 20px 0; } .method { border: 1px solid #bdc3c7; margin: 20px 0; padding: 20px; border-radius: 5px; } .path { background: #f8f9fa; margin: 10px 0; padding: 15px; border-left: 4px solid #3498db; } .operation { background: white; margin: 5px 0; padding: 10px; border-radius: 3px; font-family: 'Courier New', monospace; font-size: 14px; } .cost { color: #e74c3c; font-weight: bold; } .recommendations { background: #d5f4e6; padding: 15px; border-radius: 5px; border-left: 4px solid #27ae60; } table { width: 100%; border-collapse: collapse; margin: 20px 0; } th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; } th { background-color: #f2f2f2; } </style> </head> <body> <div class="container"> <h1>🔍 API Cost Analysis Report</h1> <p><strong>File:</strong> ${analysis.fileName}</p> <p><strong>Generated:</strong> ${new Date().toLocaleString()}</p> <div class="summary"> <h2>📊 Summary</h2> <table> <tr><th>Metric</th><th>Value</th></tr> <tr><td>Methods Analyzed</td><td>${analysis.summary.totalMethods}</td></tr> <tr><td>Total Execution Paths</td><td>${analysis.summary.totalPaths}</td></tr> <tr><td>Cost Range</td><td>${analysis.summary.costRange.min} - ${analysis.summary.costRange.max} requests</td></tr> <tr><td>Average Cost</td><td>${analysis.summary.costRange.average} requests</td></tr> </table> </div> `; analysis.methods.forEach(method => { html += ` <div class="method"> <h3>📋 ${method.className ? method.className + '.' : ''}${method.methodName}</h3> <p><strong>Execution Paths:</strong> ${method.totalPaths} | <strong>Cost Range:</strong> <span class="cost">${method.summary.minCost} - ${method.summary.maxCost}</span> requests</p> <h4>🌳 Execution Paths</h4> `; method.paths.forEach((path, index) => { html += ` <div class="path"> <strong>${index + 1}. ${path.name}</strong> <span class="cost">(Cost: ${path.totalCost})</span> ${path.conditions.length > 0 ? `<br><small>Conditions: ${path.conditions.join(', ')}</small>` : ''} <div style="margin-top: 10px;"> `; path.operations.forEach(op => { html += `<div class="operation">Line ${op.line}: ${op.method} (${op.cost}) ${op.framework ? `[${op.framework}]` : ''}</div>`; }); html += `</div></div>`; }); if (method.recommendations.length > 0) { html += ` <div class="recommendations"> <h4>🎯 Recommendations</h4> <ul> ${method.recommendations.map(rec => `<li>${rec}</li>`).join('')} </ul> </div> `; } html += `</div>`; }); html += ` </div> </body> </html>`; return html; } static generateProjectTextReport(files, summary) { let report = '\n' + '='.repeat(80) + '\n'; report += ' 🔍 PROJECT API COST ANALYSIS REPORT\n'; report += '='.repeat(80) + '\n'; report += `📅 Generated: ${new Date().toLocaleString()}\n\n`; // Project summary report += '📊 PROJECT SUMMARY\n'; report += '='.repeat(40) + '\n'; report += `Files Analyzed: ${summary.totalFiles}\n`; report += `Methods Analyzed: ${summary.totalMethods}\n`; report += `Total Execution Paths: ${summary.totalPaths}\n`; report += `Cost Distribution: Low (≤2): ${summary.costDistribution.low}, Medium (3-5): ${summary.costDistribution.medium}, High (>5): ${summary.costDistribution.high}\n\n`; // File breakdown report += '📁 FILE BREAKDOWN\n'; report += '='.repeat(40) + '\n'; files.forEach(file => { const efficiency = file.summary.costRange.max <= file.summary.costRange.min * 1.5 ? '✅' : '⚠️'; report += `${efficiency} ${file.fileName}: ${file.summary.totalMethods} methods, ${file.summary.totalPaths} paths, cost range: ${file.summary.costRange.min}-${file.summary.costRange.max}\n`; }); report += '\n'; // Optimization opportunities if (summary.optimizationOpportunities.length > 0) { report += '🚀 OPTIMIZATION OPPORTUNITIES\n'; report += '='.repeat(40) + '\n'; summary.optimizationOpportunities.forEach((opp) => { const priority = opp.priority === 'high' ? '🔴' : opp.priority === 'medium' ? '🟡' : '🟢'; report += `${priority} ${path.basename(opp.file)}${opp.method}: ${opp.description}\n`; if (opp.suggestion) { report += ` 💡 ${opp.suggestion}\n`; } }); } return report; } static generateProjectMarkdownReport(files, summary) { let md = `# Project API Cost Analysis Report\n\n`; md += `**Generated:** ${new Date().toLocaleString()}\n\n`; // Summary md += `## Summary\n\n`; md += `| Metric | Value |\n`; md += `|--------|-------|\n`; md += `| Files Analyzed | ${summary.totalFiles} |\n`; md += `| Methods Analyzed | ${summary.totalMethods} |\n`; md += `| Total Execution Paths | ${summary.totalPaths} |\n`; md += `| Low Cost Operations (≤2) | ${summary.costDistribution.low} |\n`; md += `| Medium Cost Operations (3-5) | ${summary.costDistribution.medium} |\n`; md += `| High Cost Operations (>5) | ${summary.costDistribution.high} |\n\n`; // File breakdown md += `## File Analysis\n\n`; files.forEach(file => { md += `### ${file.fileName}\n\n`; md += `- **Methods:** ${file.summary.totalMethods}\n`; md += `- **Execution Paths:** ${file.summary.totalPaths}\n`; md += `- **Cost Range:** ${file.summary.costRange.min} - ${file.summary.costRange.max} requests\n\n`; }); return md; } static generateProjectHtmlReport(files, summary) { // Similar to single file HTML but with project-wide data return this.generateHtmlReport(files[0]); // Simplified for now } static calculateProjectSummary(files) { const allMethods = files.flatMap(f => f.methods); const allCosts = allMethods.flatMap(m => m.paths.map(p => p.totalCost)); return { totalFiles: files.length, totalMethods: allMethods.length, totalPaths: allMethods.reduce((sum, m) => sum + m.totalPaths, 0), costDistribution: { low: allCosts.filter(c => c <= 2).length, medium: allCosts.filter(c => c > 2 && c <= 5).length, high: allCosts.filter(c => c > 5).length }, optimizationOpportunities: this.findOptimizationOpportunities(allMethods) }; } static findOptimizationOpportunities(methods) { const opportunities = []; methods.forEach(method => { const variance = method.summary.maxCost - method.summary.minCost; if (variance > 2) { opportunities.push({ file: method.filePath, method: method.methodName, type: 'high_variance', description: `Cost variance: ${method.summary.minCost}-${method.summary.maxCost}`, priority: variance > 5 ? 'high' : 'medium' }); } if (method.summary.maxCost > 5) { opportunities.push({ file: method.filePath, method: method.methodName, type: 'high_cost', description: `High maximum cost: ${method.summary.maxCost}`, priority: 'high' }); } }); return opportunities; } static generateOptimizationGuide() { return ` 🚀 OPTIMIZATION GUIDE ${'='.repeat(40)} 💡 Cost Reduction Strategies: 1. Use Promise.all() for independent operations 2. Implement caching for frequently accessed data 3. Use database indexes for faster queries 4. Consider pagination for large datasets 5. Batch multiple operations when possible 🎯 Best Practices: • Keep cost variance low across execution paths • Aim for maximum 3-5 requests per operation • Use parallel execution where possible • Monitor and optimize high-cost paths regularly 📚 Framework-Specific Tips: • Prisma: Use select() to limit returned fields • TypeORM: Leverage query builder for complex queries • Mongoose: Use populate() wisely to avoid N+1 queries • Supabase: Use RLS policies for efficient security `; } /** * Save report to file */ static saveReport(content, filePath) { const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(filePath, content, 'utf8'); } } exports.ReportGenerator = ReportGenerator; //# sourceMappingURL=reports.js.map