prprompts-flutter-generator
Version:
AI-powered Flutter development with full automation + official extension support - Generate 32 security-audited guides & auto-implement in 2-3 hours. NEW v5.1: Official Claude Code plugin with hooks, Gemini TOML commands, Qwen MCP settings. Features: Comp
516 lines (465 loc) • 16.8 kB
JavaScript
/**
* ReportGenerator - Generates validation reports in multiple formats
*
* Formats:
* - JSON: Machine-readable format
* - HTML: Interactive web view with charts and styling
* - Markdown: Documentation format for README/docs
*
* Features:
* - Severity breakdown charts
* - Actionable fix suggestions
* - Before/after examples
* - Color-coded issues
*/
const { createModuleLogger } = require('../utils/logger');
const logger = createModuleLogger('ReportGenerator');
class ReportGenerator {
constructor(config = {}) {
this.config = {
includeCharts: true,
includeCodeSnippets: true,
groupByValidator: true,
...config
};
}
/**
* Generate HTML report with styling and charts
*/
generateHTML(results) {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Validation Report - ${this._getProjectName(results.projectPath)}</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
line-height: 1.6;
color: #333;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 { color: #2c3e50; margin-bottom: 10px; }
h2 { color: #34495e; margin: 30px 0 15px; border-bottom: 2px solid #3498db; padding-bottom: 5px; }
h3 { color: #555; margin: 20px 0 10px; }
.header { text-align: center; margin-bottom: 40px; }
.score {
font-size: 72px;
font-weight: bold;
margin: 20px 0;
color: ${this._getScoreColor(results.overallScore)};
}
.grade {
font-size: 48px;
font-weight: bold;
color: ${this._getScoreColor(results.overallScore)};
}
.status {
padding: 10px 20px;
border-radius: 20px;
display: inline-block;
font-weight: bold;
background: ${results.passed ? '#27ae60' : '#e74c3c'};
color: white;
margin-top: 10px;
}
.summary {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin: 30px 0;
}
.metric {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
text-align: center;
border-left: 4px solid #3498db;
}
.metric-value {
font-size: 36px;
font-weight: bold;
color: #2c3e50;
}
.metric-label {
color: #7f8c8d;
margin-top: 5px;
font-size: 14px;
}
.validators {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
margin: 20px 0;
}
.validator {
background: #fff;
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
}
.validator-name {
font-weight: bold;
color: #2c3e50;
margin-bottom: 10px;
}
.validator-score {
font-size: 32px;
font-weight: bold;
margin: 10px 0;
}
.score-bar {
height: 8px;
background: #ecf0f1;
border-radius: 4px;
overflow: hidden;
margin-top: 10px;
}
.score-fill {
height: 100%;
transition: width 0.3s;
}
.issues {
margin: 20px 0;
}
.issue {
background: white;
border-left: 4px solid #95a5a6;
padding: 15px;
margin: 10px 0;
border-radius: 4px;
}
.issue.error { border-left-color: #e74c3c; background: #fff5f5; }
.issue.warning { border-left-color: #f39c12; background: #fffbf0; }
.issue.info { border-left-color: #3498db; background: #f0f8ff; }
.issue-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.severity-badge {
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
text-transform: uppercase;
}
.severity-badge.error { background: #e74c3c; color: white; }
.severity-badge.warning { background: #f39c12; color: white; }
.severity-badge.info { background: #3498db; color: white; }
.issue-message {
font-weight: 500;
margin-bottom: 8px;
}
.issue-suggestion {
color: #27ae60;
font-size: 14px;
margin-top: 8px;
}
.issue-location {
font-size: 12px;
color: #7f8c8d;
margin-top: 5px;
}
code {
background: #f4f4f4;
padding: 2px 6px;
border-radius: 3px;
font-family: 'Courier New', monospace;
font-size: 13px;
}
.recommendations {
background: #fff;
border: 2px solid #3498db;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
}
.recommendation {
padding: 15px;
margin: 10px 0;
border-radius: 4px;
background: #f8f9fa;
}
.recommendation.critical { background: #ffe5e5; border-left: 4px solid #e74c3c; }
.recommendation.high { background: #fff5e5; border-left: 4px solid #f39c12; }
.recommendation.medium { background: #e5f5ff; border-left: 4px solid #3498db; }
.recommendation.low { background: #f0f0f0; border-left: 4px solid #95a5a6; }
.footer {
text-align: center;
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid #ddd;
color: #7f8c8d;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Validation Report</h1>
<p>${results.projectPath}</p>
<div class="score">${results.overallScore}</div>
<div class="grade">Grade: ${results.grade}</div>
<div class="status">${results.passed ? 'PASSED ✓' : 'FAILED ✗'}</div>
<p style="margin-top: 10px; color: #7f8c8d;">Generated: ${results.timestamp}</p>
</div>
<div class="summary">
<div class="metric">
<div class="metric-value">${results.summary.totalErrors}</div>
<div class="metric-label">Errors</div>
</div>
<div class="metric">
<div class="metric-value">${results.summary.totalWarnings}</div>
<div class="metric-label">Warnings</div>
</div>
<div class="metric">
<div class="metric-value">${results.summary.totalInfo}</div>
<div class="metric-label">Info</div>
</div>
<div class="metric">
<div class="metric-value">${results.summary.filesValidated || 'N/A'}</div>
<div class="metric-label">Files Validated</div>
</div>
</div>
<h2>Validator Scores</h2>
<div class="validators">
${this._generateValidatorCards(results.validators)}
</div>
${results.recommendations && results.recommendations.length > 0 ? this._generateRecommendationsHTML(results.recommendations) : ''}
<h2>Issues (${results.issues.length})</h2>
<div class="issues">
${this._generateIssuesHTML(results.issues)}
</div>
<div class="footer">
<p>Generated by PRPROMPTS Flutter Generator - Validation System</p>
<p>Duration: ${results.duration || 'N/A'}</p>
</div>
</div>
</body>
</html>`;
}
/**
* Generate Markdown report
*/
generateMarkdown(results) {
const lines = [];
// Header
lines.push(`# Validation Report`);
lines.push(`**Project:** ${results.projectPath}`);
lines.push(`**Generated:** ${results.timestamp}`);
lines.push(`**Duration:** ${results.duration || 'N/A'}`);
lines.push('');
// Overall Score
lines.push(`## Overall Score: ${results.overallScore}/100 (Grade: ${results.grade})`);
lines.push(`**Status:** ${results.passed ? '✅ PASSED' : '❌ FAILED'}`);
lines.push('');
// Summary
lines.push(`## Summary`);
lines.push(`- **Errors:** ${results.summary.totalErrors}`);
lines.push(`- **Warnings:** ${results.summary.totalWarnings}`);
lines.push(`- **Info:** ${results.summary.totalInfo}`);
lines.push(`- **Files Validated:** ${results.summary.filesValidated || 'N/A'}`);
if (results.summary.criticalIssues > 0) {
lines.push(`- **Critical Issues:** ${results.summary.criticalIssues} ⚠️`);
}
lines.push('');
// Validator Scores
lines.push(`## Validator Scores`);
lines.push('');
for (const [name, validator] of Object.entries(results.validators)) {
if (validator && validator.score !== undefined) {
const emoji = this._getScoreEmoji(validator.score);
lines.push(`- **${this._capitalize(name)}:** ${validator.score}/100 ${emoji}`);
lines.push(` - Errors: ${validator.errors.length}`);
lines.push(` - Warnings: ${validator.warnings.length}`);
lines.push(` - Info: ${validator.info.length}`);
}
}
lines.push('');
// Recommendations
if (results.recommendations && results.recommendations.length > 0) {
lines.push(`## Recommendations`);
lines.push('');
for (const rec of results.recommendations) {
const emoji = rec.priority === 'critical' ? '🚨' :
rec.priority === 'high' ? '⚠️' :
rec.priority === 'medium' ? '💡' : 'ℹ️';
lines.push(`### ${emoji} ${rec.category} (${rec.priority.toUpperCase()})`);
lines.push(`**Issue:** ${rec.message}`);
lines.push(`**Action:** ${rec.action}`);
lines.push('');
}
}
// Issues by Severity
const errorIssues = results.issues.filter(i => i.severity === 'error');
const warningIssues = results.issues.filter(i => i.severity === 'warning');
const infoIssues = results.issues.filter(i => i.severity === 'info');
if (errorIssues.length > 0) {
lines.push(`## ❌ Errors (${errorIssues.length})`);
lines.push('');
for (const issue of errorIssues.slice(0, 20)) {
lines.push(`### ${issue.message}`);
if (issue.path) lines.push(`**File:** \`${issue.path}\``);
if (issue.line) lines.push(`**Line:** ${issue.line}`);
if (issue.suggestion) lines.push(`**Fix:** ${issue.suggestion}`);
if (issue.code) lines.push(`\`\`\`dart\n${issue.code}\n\`\`\``);
lines.push('');
}
if (errorIssues.length > 20) {
lines.push(`*... and ${errorIssues.length - 20} more errors*`);
lines.push('');
}
}
if (warningIssues.length > 0) {
lines.push(`## ⚠️ Warnings (${warningIssues.length})`);
lines.push('');
for (const issue of warningIssues.slice(0, 15)) {
lines.push(`- **${issue.message}**`);
if (issue.suggestion) lines.push(` - Fix: ${issue.suggestion}`);
if (issue.path) lines.push(` - File: \`${issue.path}\``);
}
if (warningIssues.length > 15) {
lines.push(`- *... and ${warningIssues.length - 15} more warnings*`);
}
lines.push('');
}
if (infoIssues.length > 0) {
lines.push(`## ℹ️ Info (${infoIssues.length})`);
lines.push('');
lines.push(`<details>`);
lines.push(`<summary>Show info messages</summary>`);
lines.push('');
for (const issue of infoIssues) {
lines.push(`- ${issue.message}`);
if (issue.suggestion) lines.push(` - ${issue.suggestion}`);
}
lines.push('');
lines.push(`</details>`);
lines.push('');
}
// Footer
lines.push(`---`);
lines.push(`*Generated by PRPROMPTS Flutter Generator - Validation System*`);
return lines.join('\n');
}
/**
* Helper methods
*/
_getProjectName(projectPath) {
return projectPath.split(/[/\\]/).pop() || 'Unknown Project';
}
_getScoreColor(score) {
if (score >= 90) return '#27ae60';
if (score >= 80) return '#2ecc71';
if (score >= 70) return '#f39c12';
if (score >= 60) return '#e67e22';
return '#e74c3c';
}
_getScoreEmoji(score) {
if (score >= 90) return '🎯';
if (score >= 80) return '✅';
if (score >= 70) return '⚠️';
if (score >= 60) return '❌';
return '🚨';
}
_capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
_generateValidatorCards(validators) {
const cards = [];
for (const [name, validator] of Object.entries(validators)) {
if (!validator || validator.score === undefined) continue;
const score = validator.score;
const color = this._getScoreColor(score);
cards.push(`
<div class="validator">
<div class="validator-name">${this._capitalize(name)}</div>
<div class="validator-score" style="color: ${color};">${score}</div>
<div class="score-bar">
<div class="score-fill" style="width: ${score}%; background: ${color};"></div>
</div>
<div style="margin-top: 10px; font-size: 14px; color: #7f8c8d;">
${validator.errors.length} errors, ${validator.warnings.length} warnings
</div>
</div>
`);
}
return cards.join('');
}
_generateRecommendationsHTML(recommendations) {
let html = '<h2>🎯 Recommendations</h2><div class="recommendations">';
for (const rec of recommendations) {
html += `
<div class="recommendation ${rec.priority}">
<h3>${rec.category}</h3>
<p><strong>Issue:</strong> ${rec.message}</p>
<p><strong>Action:</strong> ${rec.action}</p>
</div>
`;
}
html += '</div>';
return html;
}
_generateIssuesHTML(issues) {
if (issues.length === 0) {
return '<p style="text-align: center; color: #27ae60; padding: 40px;">No issues found! 🎉</p>';
}
// Group by validator
const groupedIssues = {};
for (const issue of issues) {
const validator = issue.validator || 'Unknown';
if (!groupedIssues[validator]) {
groupedIssues[validator] = [];
}
groupedIssues[validator].push(issue);
}
let html = '';
for (const [validator, validatorIssues] of Object.entries(groupedIssues)) {
html += `<h3>${validator}</h3>`;
for (const issue of validatorIssues) {
html += `
<div class="issue ${issue.severity}">
<div class="issue-header">
<span class="issue-message">${issue.message}</span>
<span class="severity-badge ${issue.severity}">${issue.severity}</span>
</div>
${issue.suggestion ? `<div class="issue-suggestion">💡 ${issue.suggestion}</div>` : ''}
${issue.path || issue.line ? `
<div class="issue-location">
${issue.path ? `📁 ${issue.path}` : ''}
${issue.line ? ` (line ${issue.line})` : ''}
</div>
` : ''}
${issue.code ? `<pre><code>${this._escapeHtml(issue.code)}</code></pre>` : ''}
</div>
`;
}
}
return html;
}
_escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, m => map[m]);
}
}
module.exports = ReportGenerator;