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
JavaScript
;
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