UNPKG

sca-tool

Version:

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

324 lines (319 loc) 15.2 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 API cost analysis */ class ReportGenerator { /** * Generate report in specified format */ static generateReport(analysis, format = 'text') { switch (format) { case 'json': return this.generateJsonReport(analysis); case 'markdown': return this.generateMarkdownReport(analysis); case 'html': return this.generateHtmlReport(analysis); case 'text': default: return this.generateTextReport(analysis); } } /** * Generate project report for multiple files */ static generateProjectReport(files, format = 'text') { const summary = this.calculateProjectSummary(files); switch (format) { case 'json': return JSON.stringify({ files, summary }, null, 2); case 'markdown': return this.generateProjectMarkdownReport(files, summary); case 'html': return this.generateProjectHtmlReport(files, summary); case 'text': default: return this.generateProjectTextReport(files, summary); } } 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`; // Summary report += '📊 SUMMARY\n'; report += '========================================\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`; // Methods analysis.methods.forEach((method, index) => { report += `📋 METHOD ${index + 1}: ${method.className ? method.className + '.' : ''}${method.methodName}\n`; report += '----------------------------------------\n'; report += `Total Paths: ${method.totalPaths}\n`; report += `Cost Range: ${method.summary.minCost} - ${method.summary.maxCost} requests\n\n`; method.paths.forEach((path, pathIndex) => { report += ` 🌳 Path ${pathIndex + 1}: ${path.name}\n`; report += ` Cost: ${path.totalCost} requests\n`; if (path.conditions.length > 0) { report += ` Conditions: ${path.conditions.join(', ')}\n`; } report += ` Operations:\n`; path.operations.forEach(op => { report += ` Line ${op.line}: ${op.method} (Cost: ${op.cost})\n`; }); report += '\n'; }); if (method.recommendations.length > 0) { report += ' 🎯 Recommendations:\n'; method.recommendations.forEach(rec => { report += ` • ${rec}\n`; }); report += '\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`; // Summary 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`; // Methods md += `## 📋 Methods Analysis\n\n`; analysis.methods.forEach((method, index) => { const methodName = method.className ? `${method.className}.${method.methodName}` : method.methodName; md += `### ${index + 1}. ${methodName}\n\n`; md += `- **Total Paths:** ${method.totalPaths}\n`; md += `- **Cost Range:** ${method.summary.minCost} - ${method.summary.maxCost} requests\n\n`; md += `#### 🌳 Execution Paths\n\n`; method.paths.forEach((path, pathIndex) => { md += `**${pathIndex + 1}. ${path.name}** (Cost: ${path.totalCost})\n\n`; if (path.conditions.length > 0) { md += `*Conditions:* ${path.conditions.join(', ')}\n\n`; } md += `Operations:\n`; path.operations.forEach(op => { md += `- Line ${op.line}: \`${op.method}\` (Cost: ${op.cost})\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) { const methodsHtml = analysis.methods.map(method => { const methodName = method.className ? `${method.className}.${method.methodName}` : method.methodName; const costClass = method.summary.maxCost > 5 ? 'cost-high' : method.summary.maxCost > 2 ? 'cost-medium' : 'cost-low'; const pathsHtml = method.paths.map((path, index) => { const operationsHtml = path.operations.map(op => `<div class="operation">Line ${op.line}: ${op.method} (Cost: ${op.cost})</div>`).join(''); return ` <div class="path"> <strong>${index + 1}. ${path.name}</strong> (Cost: ${path.totalCost}) ${path.conditions.length > 0 ? `<br><em>Conditions: ${path.conditions.join(', ')}</em>` : ''} <div style="margin-top: 10px;"> ${operationsHtml} </div> </div> `; }).join(''); const recommendationsHtml = method.recommendations.length > 0 ? ` <div class="recommendations"> <h4>🎯 Recommendations</h4> <ul> ${method.recommendations.map(rec => `<li>${rec}</li>`).join('')} </ul> </div> ` : ''; return ` <div class="method"> <h3>${methodName}</h3> <p><strong>Paths:</strong> ${method.totalPaths} | <strong>Cost:</strong> <span class="${costClass}">${method.summary.minCost} - ${method.summary.maxCost}</span></p> <h4>🌳 Execution Paths</h4> ${pathsHtml} ${recommendationsHtml} </div> `; }).join(''); return `<!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 - ${analysis.fileName}</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; } .container { max-width: 1200px; margin: 0 auto; } .header { background: white; padding: 30px; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); margin-bottom: 20px; } .summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin: 20px 0; } .card { background: white; padding: 20px; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .method { background: white; padding: 20px; margin: 20px 0; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .path { background: #f8f9fa; padding: 15px; margin: 10px 0; border-radius: 8px; border-left: 4px solid #007bff; } .operation { background: #e9ecef; padding: 10px; margin: 5px 0; border-radius: 6px; font-family: 'Monaco', 'Menlo', monospace; font-size: 14px; } .recommendations { background: #d4edda; padding: 15px; border-radius: 8px; border-left: 4px solid #28a745; margin-top: 15px; } h1 { color: #333; margin: 0; font-size: 2.5em; } h2 { color: #555; border-bottom: 2px solid #007bff; padding-bottom: 10px; } h3 { color: #666; } .cost-high { color: #dc3545; font-weight: bold; } .cost-medium { color: #ffc107; font-weight: bold; } .cost-low { color: #28a745; font-weight: bold; } </style> </head> <body> <div class="container"> <div class="header"> <h1>🔍 API Cost Analysis Report</h1> <p><strong>File:</strong> ${analysis.fileName}</p> <p><strong>Generated:</strong> ${new Date().toLocaleString()}</p> </div> <div class="summary"> <div class="card"> <h3>📋 Methods</h3> <div style="font-size: 2em; font-weight: bold; color: #007bff;">${analysis.summary.totalMethods}</div> </div> <div class="card"> <h3>🌳 Execution Paths</h3> <div style="font-size: 2em; font-weight: bold; color: #28a745;">${analysis.summary.totalPaths}</div> </div> <div class="card"> <h3>💰 Cost Range</h3> <div style="font-size: 1.5em; font-weight: bold; color: #ffc107;">${analysis.summary.costRange.min} - ${analysis.summary.costRange.max}</div> </div> <div class="card"> <h3>📊 Average Cost</h3> <div style="font-size: 1.5em; font-weight: bold; color: #6f42c1;">${analysis.summary.costRange.average}</div> </div> </div> <h2>📋 Methods Analysis</h2> ${methodsHtml} <div style="text-align: center; margin-top: 40px; color: #666; padding: 20px;"> Generated by <strong>SCA-Tool</strong> - Static Code Analyzer for API Cost Analysis </div> </div> </body> </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 += '========================================\n'; report += `Files Analyzed: ${files.length}\n`; report += `Methods Analyzed: ${summary.totalMethods}\n`; report += `Total Execution Paths: ${summary.totalPaths}\n\n`; // File breakdown report += '📁 FILE BREAKDOWN\n'; report += '========================================\n'; files.forEach(file => { const status = file.summary.costRange.max <= 2 ? '✅' : '⚠️'; report += `${status} ${file.fileName}: ${file.summary.totalMethods} methods, ${file.summary.totalPaths} paths, cost range: ${file.summary.costRange.min}-${file.summary.costRange.max}\n`; }); return report; } static generateProjectMarkdownReport(files, summary) { let md = '# 🔍 Project API Cost Analysis Report\n\n'; md += `**Generated:** ${new Date().toLocaleString()}\n\n`; md += '## 📊 Project Summary\n\n'; md += `- **Files Analyzed:** ${files.length}\n`; md += `- **Methods Analyzed:** ${summary.totalMethods}\n`; md += `- **Total Execution Paths:** ${summary.totalPaths}\n\n`; md += '## 📁 File Breakdown\n\n'; files.forEach(file => { const status = file.summary.costRange.max <= 2 ? '✅' : '⚠️'; md += `${status} **${file.fileName}:** ${file.summary.totalMethods} methods, ${file.summary.totalPaths} paths, cost range: ${file.summary.costRange.min}-${file.summary.costRange.max}\n`; }); return md; } static generateProjectHtmlReport(files, summary) { // Simplified project HTML report return this.generateHtmlReport(files[0]); // For now, just show first file } static calculateProjectSummary(files) { const allMethods = files.flatMap(f => f.methods); return { totalMethods: allMethods.length, totalPaths: allMethods.reduce((sum, m) => sum + m.totalPaths, 0) }; } /** * Save report to file */ static saveReport(content, filePath) { try { const dir = path.dirname(filePath); // Create directory if it doesn't exist if (dir && dir !== '.' && dir !== filePath) { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } } fs.writeFileSync(filePath, content, 'utf8'); console.log(`✅ Report saved to: ${filePath}`); } catch (error) { throw new Error(`Failed to save report to ${filePath}: ${error instanceof Error ? error.message : String(error)}`); } } } exports.ReportGenerator = ReportGenerator; //# sourceMappingURL=reports.js.map