# Step 14 — Phase 4.6: 报告导出 **依赖**: Step 12(Merger) **参考设计**: §14.2 ## 目标 将 `MergedReport` 导出为 Markdown 格式报告。 ## 新建文件 | # | 文件 | 说明 | |---|------|------| | 1 | `src/utils/report.ts` | `reportToMarkdown()` | --- ## `src/utils/report.ts` ```typescript import { MergedReport } from '../merger/merger'; function severityEmoji(severity: string): string { switch (severity) { case 'error': return '🔴'; case 'warning': return '🟡'; case 'info': return '🔵'; default: return '⚪'; } } function formatLine(line: number): string { return `L${line + 1}`; } export function reportToMarkdown(report: MergedReport): string { const lines: string[] = []; lines.push('# 代码审查报告'); lines.push(''); lines.push(`**文件:** \`${report.filePath}\``); lines.push(`**语言:** ${report.language}`); lines.push(`**耗时:** ${(report.duration / 1000).toFixed(1)}s`); if (report.adapterNames.length > 0) { lines.push(`**分析工具:** ${report.adapterNames.join(', ')}`); } if (report.degraded) { lines.push(''); lines.push('> ⚠️ 部分 AI 功能不可用,报告已降级'); } if (report.errors.length > 0) { lines.push(''); lines.push('## 错误'); for (const err of report.errors) { lines.push(`- ${err}`); } } lines.push(''); lines.push('---'); lines.push(''); const total = report.linterCount + report.customRuleCount + report.aiCount; const errors = report.linterDiagnostics.filter(d => d.severity === 'error').length + report.customRuleDiagnostics.filter(d => d.severity === 'error').length + report.aiFindings.filter(f => f.severity === 'error').length; const warnings = report.linterDiagnostics.filter(d => d.severity === 'warning').length + report.customRuleDiagnostics.filter(d => d.severity === 'warning').length + report.aiFindings.filter(f => f.severity === 'warning').length; const infos = total - errors - warnings; lines.push(`**总计:** ${total} | **错误:** ${errors} | **警告:** ${warnings} | **建议:** ${infos}`); lines.push(''); if (report.linterDiagnostics.length > 0) { lines.push(`## 🔧 静态分析 · ${report.linterCount} 个问题`); lines.push(''); for (const diag of report.linterDiagnostics) { lines.push(`- ${severityEmoji(diag.severity)} \`${diag.ruleId}\` ${formatLine(diag.range.start.line)}`); lines.push(` ${diag.message}`); if (diag.suggestion) { lines.push(` 建议: ${diag.suggestion}`); } } lines.push(''); } if (report.customRuleDiagnostics.length > 0) { lines.push(`## 📋 自定义规则 · ${report.customRuleCount} 个问题`); lines.push(''); for (const diag of report.customRuleDiagnostics) { lines.push(`- ${severityEmoji(diag.severity)} \`${diag.ruleId}\` ${formatLine(diag.range.start.line)}`); lines.push(` ${diag.message}`); } lines.push(''); } if (report.aiFindings.length > 0) { lines.push(`## 🤖 AI 审查 · ${report.aiCount} 条建议`); lines.push(''); for (const finding of report.aiFindings) { lines.push(`- ${severityEmoji(finding.severity)} [AI] [${finding.category}] \`${finding.ruleId}\` ${formatLine(finding.line)}`); lines.push(` **${finding.title}**`); lines.push(` ${finding.description}`); if (finding.suggestion) { lines.push(` 建议: ${finding.suggestion}`); } if (finding.codeDiff) { lines.push(' ```diff'); lines.push(` ${finding.codeDiff.split('\n').join('\n ')}`); lines.push(' ```'); } } lines.push(''); } if (total === 0) { lines.push('✅ 未发现问题'); lines.push(''); } return lines.join('\n'); } ``` --- ## 关键逻辑 - 报告格式与设计 §14.2 一致 - 按来源分三个段:静态分析 / 自定义规则 / AI 审查 - 统计卡片:总计 + 按严重级别拆分 - AI 审查结果包含 category、codeDiff(diff 代码块) - 降级和错误信息在报告顶部单独显示 --- ## 验收 - [ ] 文件创建完成 - [ ] `npm run compile` 通过 - [ ] `npm run lint` 通过