Skip to content

Commit d9da1b3

Browse files
✨ feat(utils): 添加代码审查报告生成器和优化工具类
-【新功能】 - 实现 CodeReviewReportGenerator 类,支持生成 Markdown 格式的代码审查报告 - 增强多个工具类的文档注释和类型定义 - 优化 DiffSplitter 和 DiffSimplifier 的差异处理逻辑 -【代码优化】 - 完善 LocalizationManager 的错误处理和资源加载机制 - 改进 DateUtils 的日期范围计算方法 - 增强 ProgressHandler 和 NotificationHandler 的类型安全性 -【功能增强】 - 增加 webview 相关工具函数 - 扩展差异文本处理的配置选项 - 优化本地化资源管理机制
1 parent 79801c4 commit d9da1b3

8 files changed

+286
-22
lines changed
+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { CodeReviewResult, CodeReviewIssue } from "../ai/types";
2+
3+
/**
4+
* 代码审查报告生成器,将代码审查结果转换为格式化的 Markdown 文档
5+
*/
6+
export class CodeReviewReportGenerator {
7+
/**
8+
* 不同严重程度对应的 emoji 图标
9+
* @private
10+
*/
11+
private static readonly severityEmoji = {
12+
NOTE: "💡", // 提示
13+
WARNING: "⚠️", // 警告
14+
ERROR: "🚨", // 错误
15+
};
16+
17+
/**
18+
* 生成完整的 Markdown 格式代码审查报告
19+
* @param {CodeReviewResult} review - 代码审查结果对象
20+
* @returns {string} 格式化的 Markdown 文本
21+
*/
22+
static generateMarkdownReport(review: CodeReviewResult): string {
23+
// 按文件分组整理问题
24+
const sections = this.groupIssuesByFile(review.issues);
25+
26+
// 生成报告各部分
27+
let markdown = this.generateHeader(review.summary);
28+
markdown += this.generateDetailedFindings(sections);
29+
30+
return markdown;
31+
}
32+
33+
/**
34+
* 将问题按文件路径分组
35+
* @private
36+
* @param {CodeReviewIssue[]} issues - 问题列表
37+
* @returns {Record<string, CodeReviewIssue[]>} 按文件分组的问题
38+
*/
39+
private static groupIssuesByFile(
40+
issues: CodeReviewIssue[]
41+
): Record<string, CodeReviewIssue[]> {
42+
return issues.reduce((acc, issue) => {
43+
if (!acc[issue.filePath]) {
44+
acc[issue.filePath] = [];
45+
}
46+
acc[issue.filePath].push(issue);
47+
return acc;
48+
}, {} as Record<string, CodeReviewIssue[]>);
49+
}
50+
51+
/**
52+
* 生成报告头部,包含总体摘要
53+
* @private
54+
* @param {string} summary - 审查总结
55+
* @returns {string} Markdown 格式的报告头部
56+
*/
57+
private static generateHeader(summary: string): string {
58+
return `# Code Review Report\n\n## Summary\n\n${summary}\n\n`;
59+
}
60+
61+
/**
62+
* 生成详细问题列表
63+
* @private
64+
* @param {Record<string, CodeReviewIssue[]>} sections - 按文件分组的问题
65+
* @returns {string} Markdown 格式的详细问题列表
66+
*/
67+
private static generateDetailedFindings(
68+
sections: Record<string, CodeReviewIssue[]>
69+
): string {
70+
let markdown = `## Detailed Findings\n\n`;
71+
72+
// 遍历每个文件的问题
73+
for (const [filePath, issues] of Object.entries(sections)) {
74+
markdown += `### ${filePath}\n\n`;
75+
76+
for (const issue of issues) {
77+
markdown += this.generateIssueSection(issue);
78+
}
79+
}
80+
81+
return markdown;
82+
}
83+
84+
/**
85+
* 生成单个问题的描述部分
86+
* @private
87+
* @param {CodeReviewIssue} issue - 单个问题对象
88+
* @returns {string} Markdown 格式的问题描述
89+
*/
90+
private static generateIssueSection(issue: CodeReviewIssue): string {
91+
// 生成问题标题,包含严重程度和行号
92+
let section = `#### ${this.severityEmoji[issue.severity]} ${
93+
issue.severity
94+
}: Line ${issue.startLine}${issue.endLine ? `-${issue.endLine}` : ""}\n\n`;
95+
96+
// 添加问题描述和建议
97+
section += `**Issue:** ${issue.description}\n\n`;
98+
section += `**Suggestion:** ${issue.suggestion}\n\n`;
99+
100+
// 如果有代码示例,添加代码块
101+
if (issue.code) {
102+
section += "```typescript\n" + issue.code + "\n```\n\n";
103+
}
104+
105+
// 如果有相关文档,添加链接
106+
if (issue.documentation) {
107+
section += `📚 [Documentation](${issue.documentation})\n\n`;
108+
}
109+
110+
section += `---\n\n`;
111+
return section;
112+
}
113+
}

src/utils/DateUtils.ts

+17-5
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,32 @@
1+
/**
2+
* 日期工具类,提供日期范围计算相关功能
3+
*/
14
export class DateUtils {
2-
static getDateRangeFromPeriod(period: string): { startDate: Date; endDate: Date } {
5+
/**
6+
* 根据给定的时间段计算日期范围
7+
* @param {string} period - 时间段描述,可选值: "1 week ago" | "2 weeks ago" | "3 weeks ago"
8+
* @returns {{startDate: Date, endDate: Date}} 计算得到的日期范围
9+
*/
10+
static getDateRangeFromPeriod(period: string): {
11+
startDate: Date;
12+
endDate: Date;
13+
} {
314
const now = new Date();
415
let startDate: Date;
516

17+
// 根据时间段计算起始日期
618
switch (period) {
719
case "1 week ago":
8-
startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
20+
startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); // 7天前
921
break;
1022
case "2 weeks ago":
11-
startDate = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1000);
23+
startDate = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1000); // 14天前
1224
break;
1325
case "3 weeks ago":
14-
startDate = new Date(now.getTime() - 21 * 24 * 60 * 60 * 1000);
26+
startDate = new Date(now.getTime() - 21 * 24 * 60 * 60 * 1000); // 21天前
1527
break;
1628
default:
17-
startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
29+
startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); // 默认7天前
1830
}
1931

2032
return { startDate, endDate: now };

src/utils/DiffSimplifier.ts

+36-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
11
import * as vscode from "vscode";
22

3+
/**
4+
* 用于简化和格式化差异文本的工具类
5+
* 可配置上下文行数和最大行长度,支持差异文本的精简化处理
6+
*/
37
export class DiffSimplifier {
8+
/**
9+
* 缓存的配置对象
10+
* @private
11+
* @property {boolean} enabled - 是否启用差异简化
12+
* @property {number} contextLines - 保留的上下文行数
13+
* @property {number} maxLineLength - 单行最大长度
14+
*/
415
private static configCache: {
516
enabled: boolean;
617
contextLines: number;
718
maxLineLength: number;
819
} | null = null;
920

21+
/**
22+
* 从 VSCode 配置中获取差异简化的相关设置
23+
* @private
24+
* @returns {Object} 包含差异简化配置的对象
25+
*/
1026
private static getConfig() {
1127
if (this.configCache) {
1228
return this.configCache;
@@ -23,17 +39,25 @@ export class DiffSimplifier {
2339
return this.configCache;
2440
}
2541

26-
// 添加配置更新方法
42+
/**
43+
* 清除配置缓存,用于配置更新时刷新设置
44+
*/
2745
static clearConfigCache() {
2846
this.configCache = null;
2947
}
3048

3149
private static readonly CONTEXT_LINES = 3;
3250
private static readonly MAX_LINE_LENGTH = 120;
3351

52+
/**
53+
* 简化差异文本,根据配置处理上下文行数和行长度
54+
* @param {string} diff - 原始差异文本
55+
* @returns {string} 简化后的差异文本
56+
*/
3457
static simplify(diff: string): string {
3558
const config = this.getConfig();
3659

60+
// 如果未启用简化,直接返回原文
3761
if (!config.enabled) {
3862
return diff;
3963
}
@@ -45,7 +69,7 @@ export class DiffSimplifier {
4569
for (let i = 0; i < lines.length; i++) {
4670
const line = lines[i];
4771

48-
// 保留diff头信息
72+
// 保留 diff 头信息(Index、===、---、+++)
4973
if (
5074
line.startsWith("Index:") ||
5175
line.startsWith("===") ||
@@ -56,28 +80,33 @@ export class DiffSimplifier {
5680
continue;
5781
}
5882

59-
// 处理修改行
83+
// 处理修改行(以 + 或 - 开头)
6084
if (line.startsWith("+") || line.startsWith("-")) {
61-
// 截断过长的行
6285
simplified.push(this.truncateLine(line, config.maxLineLength));
6386
skipCount = 0;
6487
continue;
6588
}
6689

67-
// 处理上下文行
90+
// 处理上下文行,保留配置的行数
6891
if (skipCount < config.contextLines) {
6992
simplified.push(this.truncateLine(line, config.maxLineLength));
7093
skipCount++;
7194
} else if (skipCount === config.contextLines) {
72-
simplified.push("...");
95+
simplified.push("..."); // 添加省略标记
7396
skipCount++;
7497
}
7598
}
7699

77100
return simplified.join("\n");
78101
}
79102

80-
// 优化 truncateLine 方法
103+
/**
104+
* 截断过长的行,添加省略号
105+
* @private
106+
* @param {string} line - 需要处理的行
107+
* @param {number} maxLength - 最大允许长度
108+
* @returns {string} 处理后的行
109+
*/
81110
private static truncateLine(line: string, maxLength: number): string {
82111
if (!line || line.length <= maxLength) {
83112
return line;

src/utils/DiffSplitter.ts

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,35 @@
1+
/**
2+
* 表示一个差异块的结构
3+
* @interface DiffChunk
4+
* @property {string} filename - 发生变更的文件名
5+
* @property {string} content - 包含变更内容的差异文本
6+
*/
17
export interface DiffChunk {
28
filename: string;
39
content: string;
410
}
511

12+
/**
13+
* 用于将 Git 和 SVN 的差异文本拆分为独立的文件差异块
14+
* @class DiffSplitter
15+
*/
616
export class DiffSplitter {
17+
/**
18+
* 将 Git diff 输出拆分为独立的文件差异块
19+
* @param {string} diff - 完整的 Git diff 文本输出
20+
* @returns {DiffChunk[]} 包含各文件差异信息的数组
21+
*/
722
static splitGitDiff(diff: string): DiffChunk[] {
823
const chunks: DiffChunk[] = [];
24+
// 按 Git diff 文件头部分割
925
const files = diff.split("diff --git");
1026

1127
for (const file of files) {
1228
if (!file.trim()) {
1329
continue;
1430
}
1531

16-
// 提取文件名
32+
// 使用正则表达式提取文件名(格式: "a/path/to/file b/path/to/file")
1733
const fileNameMatch = file.match(/a\/(.+?) b\//);
1834
if (!fileNameMatch) {
1935
continue;
@@ -28,15 +44,22 @@ export class DiffSplitter {
2844
return chunks;
2945
}
3046

47+
/**
48+
* 将 SVN diff 输出拆分为独立的文件差异块
49+
* @param {string} diff - 完整的 SVN diff 文本输出
50+
* @returns {DiffChunk[]} 包含各文件差异信息的数组
51+
*/
3152
static splitSvnDiff(diff: string): DiffChunk[] {
3253
const chunks: DiffChunk[] = [];
54+
// 按 SVN diff 文件索引标记分割
3355
const files = diff.split("Index: ");
3456

3557
for (const file of files) {
3658
if (!file.trim()) {
3759
continue;
3860
}
3961

62+
// SVN diff 中文件名在第一行
4063
const lines = file.split("\n");
4164
const filename = lines[0].trim();
4265

0 commit comments

Comments
 (0)