import type {
CodeQualityMetrics,
DependencyReport,
LintResult,
ScrapedData,
APIDiscoveryResult,
AnalysisReport,
} from '../types/index.js';
export class Formatters {
/**
* Format code quality metrics as markdown
*/
static formatCodeQualityMetrics(metrics: CodeQualityMetrics): string {
let output = '# Code Quality Metrics\n\n';
output += `**Complexity:** ${metrics.complexity}\n`;
output += `**Maintainability Index:** ${metrics.maintainabilityIndex}\n`;
output += `**Cyclomatic Complexity:** ${metrics.cyclomaticComplexity ?? 'N/A'}\n`;
output += `**Lines of Code:** ${metrics.linesOfCode}\n`;
output += `**Technical Debt:** ${metrics.technicalDebt}\n\n`;
if (metrics.codeSmells.length > 0) {
output += '## Code Smells\n\n';
for (const smell of metrics.codeSmells) {
output += `- **${smell.type}** (${smell.severity}): ${smell.description}\n`;
output += ` - Location: ${smell.location}${smell.line ? `:${smell.line}` : ''}\n`;
if (smell.suggestion) {
output += ` - Suggestion: ${smell.suggestion}\n`;
}
}
output += '\n';
}
if (metrics.duplications.length > 0) {
output += '## Code Duplications\n\n';
for (const dup of metrics.duplications) {
output += `- ${dup.lines} lines duplicated between:\n`;
output += ` - ${dup.firstFile} (${dup.startLine1}-${dup.endLine1})\n`;
output += ` - ${dup.secondFile} (${dup.startLine2}-${dup.endLine2})\n`;
}
}
return output;
}
/**
* Format dependency report as markdown
*/
static formatDependencyReport(report: DependencyReport): string {
let output = '# Dependency Analysis Report\n\n';
output += `**Total Dependencies:** ${report.totalDependencies}\n\n`;
if (report.unused.length > 0) {
output += '## Unused Dependencies\n\n';
for (const dep of report.unused) {
output += `- ${dep}\n`;
}
output += '\n';
}
if (report.outdated.length > 0) {
output += '## Outdated Packages\n\n';
for (const pkg of report.outdated) {
output += `- **${pkg.name}**: ${pkg.current} → ${pkg.latest} (wanted: ${pkg.wanted})\n`;
output += ` - Location: ${pkg.location}\n`;
}
output += '\n';
}
if (report.vulnerabilities.length > 0) {
output += '## Security Vulnerabilities\n\n';
for (const vuln of report.vulnerabilities) {
output += `- **${vuln.name}** (${vuln.severity}): ${vuln.title}\n`;
output += ` - URL: ${vuln.url}\n`;
if (vuln.dependencyOf) {
output += ` - Dependency of: ${vuln.dependencyOf}\n`;
}
if (vuln.fixAvailable) {
output += ` - Fix available: Yes\n`;
}
}
output += '\n';
}
if (report.bundleSize) {
output += `## Bundle Size\n\n`;
output += `**Estimated Bundle Size:** ${this.formatBytes(report.bundleSize)}\n`;
}
return output;
}
/**
* Format lint results as markdown
*/
static formatLintResults(results: LintResult[]): string {
let output = '# Linting Results\n\n';
const totalErrors = results.reduce((sum, r) => sum + r.errorCount, 0);
const totalWarnings = results.reduce((sum, r) => sum + r.warningCount, 0);
output += `**Total Errors:** ${totalErrors}\n`;
output += `**Total Warnings:** ${totalWarnings}\n\n`;
for (const result of results) {
if (result.messages.length === 0) continue;
output += `## ${result.file}\n\n`;
output += `Errors: ${result.errorCount}, Warnings: ${result.warningCount}\n\n`;
for (const message of result.messages) {
const severity = message.severity === 2 ? '❌ Error' : message.severity === 1 ? '⚠️ Warning' : 'ℹ️ Info';
output += `- ${severity} (${message.line}:${message.column})`;
if (message.ruleId) {
output += ` [${message.ruleId}]`;
}
output += `: ${message.message}\n`;
}
output += '\n';
}
return output;
}
/**
* Format scraped data as markdown
*/
static formatScrapedData(data: ScrapedData): string {
let output = `# Scraped Data: ${data.url}\n\n`;
output += `**Scraped At:** ${data.scrapedAt.toISOString()}\n\n`;
if (data.title) {
output += `**Title:** ${data.title}\n\n`;
}
if (data.text) {
output += `## Text Content\n\n${data.text.substring(0, 1000)}${data.text.length > 1000 ? '...' : ''}\n\n`;
}
if (data.links && data.links.length > 0) {
output += `## Links (${data.links.length})\n\n`;
for (const link of data.links.slice(0, 20)) {
output += `- ${link}\n`;
}
if (data.links.length > 20) {
output += `\n... and ${data.links.length - 20} more links\n`;
}
output += '\n';
}
if (data.images && data.images.length > 0) {
output += `## Images (${data.images.length})\n\n`;
for (const img of data.images.slice(0, 10)) {
output += `- ${img}\n`;
}
if (data.images.length > 10) {
output += `\n... and ${data.images.length - 10} more images\n`;
}
output += '\n';
}
if (data.tables && data.tables.length > 0) {
output += `## Tables (${data.tables.length})\n\n`;
for (const table of data.tables) {
if (table.caption) {
output += `### ${table.caption}\n\n`;
}
if (table.headers.length > 0) {
output += '| ' + table.headers.join(' | ') + ' |\n';
output += '|' + table.headers.map(() => '---').join('|') + '|\n';
for (const row of table.rows.slice(0, 10)) {
output += '| ' + row.join(' | ') + ' |\n';
}
if (table.rows.length > 10) {
output += `\n... and ${table.rows.length - 10} more rows\n`;
}
}
output += '\n';
}
}
return output;
}
/**
* Format API discovery results as markdown
*/
static formatAPIDiscoveryResults(result: APIDiscoveryResult): string {
let output = '# API Discovery Results\n\n';
if (result.baseUrl) {
output += `**Base URL:** ${result.baseUrl}\n\n`;
}
if (result.authentication) {
output += `## Authentication\n\n`;
output += `- Type: ${result.authentication.type}\n`;
if (result.authentication.location) {
output += `- Location: ${result.authentication.location}\n`;
}
if (result.authentication.name) {
output += `- Name: ${result.authentication.name}\n`;
}
output += '\n';
}
if (result.endpoints.length > 0) {
output += `## Endpoints (${result.endpoints.length})\n\n`;
for (const endpoint of result.endpoints) {
output += `### ${endpoint.method} ${endpoint.path}\n\n`;
output += `- **Full URL:** ${endpoint.fullUrl}\n`;
if (endpoint.statusCode) {
output += `- **Status:** ${endpoint.statusCode}\n`;
}
if (endpoint.parameters && endpoint.parameters.length > 0) {
output += `- **Parameters:**\n`;
for (const param of endpoint.parameters) {
output += ` - ${param.name} (${param.type}, ${param.required ? 'required' : 'optional'}) - ${param.location}\n`;
}
}
output += '\n';
}
}
return output;
}
/**
* Format analysis report
*/
static formatAnalysisReport(report: AnalysisReport): string {
let output = `# ${report.type.charAt(0).toUpperCase() + report.type.slice(1)} Analysis Report\n\n`;
output += `**Generated At:** ${report.generatedAt.toISOString()}\n\n`;
output += `## Summary\n\n`;
output += `- Total Files: ${report.summary.totalFiles}\n`;
output += `- Total Issues: ${report.summary.totalIssues}\n`;
output += `- Errors: ${report.summary.errors}\n`;
output += `- Warnings: ${report.summary.warnings}\n`;
if (report.summary.score !== undefined) {
output += `- Score: ${report.summary.score}/100\n`;
}
output += '\n';
// Format details based on type
if (report.type === 'code_quality') {
output += this.formatCodeQualityMetrics(report.details as CodeQualityMetrics);
} else if (report.type === 'dependency') {
output += this.formatDependencyReport(report.details as DependencyReport);
}
return output;
}
/**
* Format bytes to human readable format
*/
static formatBytes(bytes: number): string {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
}
/**
* Format JSON with indentation
*/
static formatJSON(data: unknown): string {
return JSON.stringify(data, null, 2);
}
}