Files
2026Technology-Competition/docs/superpowers/specs/2026-07-10-code-reviewer-design.md
T
范智鹏 a734cdf009 refactor: 品牌重命名 + maxTokens 支持 + 设置面板简化
- CodeGuard → Code Purifier / 净码特工(displayName、命令、配置标题)
- 新增 ai.maxTokens 配置项,所有 Provider 及 fixer 传入 maxTokens
- AI 引擎增强:repairJsonEscapes + JSON 解析 fallback + 详细错误信息
- 设置面板规则管理改为文件级(list/delete .yaml),addRule 改为 AI 从 Markdown 生成 YAML
- yaml-parser 简化:移除 config.yaml 的 enable/disable 过滤逻辑
- 审查报告面板:errorBanner 优先显示具体错误、lint 诊断显示 suggestion、移除 translatedDiagnostics 独立渲染
- merger 中 translatedDiagnostics 覆盖原始 lint 诊断 message/suggestion
- HTML linter 配置项、测试用例重写、typescript-eslint 移入 dependencies
2026-07-20 20:24:40 +08:00

1348 lines
45 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Code Reviewer — VSCode Extension Design Spec
## 1. 概述
VSCode 代码审查与规范检查一体化工具。集成多语言静态分析器与 AI 智能审查,为开发团队提供代码质量保障。
### 核心能力
- 多语言代码静态分析(ESLint、PMD、Stylelint、sql-lint 等)
- AI 辅助深度审查与问题解释/翻译
- 自定义规则检查(YAML 定义 + AI 语义评估)
- 自动修复建议与批量修复(快照撤销)
- Webview 审查报告面板
---
## 2. 架构总览
插件分为四层:
```
UI 层 — Webview 审查面板 / Inline Diagnostic / Code Action
核心层 — Orchestrator(编排器)+ AI 审查引擎
适配层 — Linter 适配器(统一 LinterAdapter 接口)
基础层 — 配置管理 / 规则管理 / 报告导出
```
所有 linter 统一实现 `LinterAdapter` 接口,通过 Orchestrator 调度,结果聚合后通过 Webview 面板展示。
---
## 3. 适配器层
### 3.1 设计决策
| 决策项 | 结论 | 说明 |
|--------|------|------|
| 支持语言 | Java / JS/TS / CSS / SQL / JSP | 不支持 Python、Go |
| 注册方式 | 硬编码(方案 A) | 适配器数量少,无需过度设计 |
| 审查粒度 | 单文件 | `check()` 接收单个 `TextDocument` |
| 目标平台 | Windows | PMD classpath 分隔符使用 `;` |
### 3.2 统一接口
```typescript
interface LinterDiagnostic {
severity: 'error' | 'warning' | 'info';
ruleId: string; // 格式: "linter名:规则ID"
message: string;
range: vscode.Range;
suggestion?: string;
}
interface AdapterResult {
diagnostics: LinterDiagnostic[];
status: 'ok' | 'tool-unavailable' | 'execution-failed';
errorMessage?: string;
}
interface LinterAdapter {
id: string;
supportedLanguages: string[];
check(document: vscode.TextDocument, workingDir: string): Promise<AdapterResult>;
isAvailable(): boolean;
}
```
**错误状态说明**
| 状态 | 含义 | 用户感知 |
|------|------|---------|
| `ok` | 检查成功 | 正常显示结果 |
| `tool-unavailable` | 工具未安装/未找到 | 提示用户安装对应工具 |
| `execution-failed` | 工具已安装但执行出错 | 显示错误信息,引导排查 |
### 3.3 适配器清单
| 适配器 | 语言 | 实现方式 | 特殊处理 |
|--------|------|----------|----------|
| ESLint | JS/TS | eslint npm 包 `lintText()` | 直接接收代码文本,支持虚拟文档 |
| PMD | Java | Java 子进程调用 | 支持 stdin 传入代码,支持虚拟文档 |
| Stylelint | CSS | stylelint npm 包 `lint({ code })` | 直接接收代码文本,支持虚拟文档 |
| sql-lint | SQL | sqlfluff CLI 调用 | 支持 SQL 和 PL/SQL,方言映射 |
| JSP | JSP | 组合适配器(PMD + ESLint + Stylelint | 提取内嵌代码块后分发检查 |
### 3.4 PMD 适配器特殊设计
PMD 是 Java 工具,需要特殊处理:
1. **JAR 文件管理**:插件自带 `jars/pmd/` 目录存放 PMD 依赖
2. **规则集配置**:支持自定义规则集 XML 文件
3. **Java 包装器**`PmdRunner.java` 简化调用,输出 JSON 格式
4. **虚拟文档支持**:通过 stdin 传入代码,无需真实文件
**PmdRunner.java 核心逻辑**`jars/pmd/PmdRunner.java`):
```
参数: filePath (传 "-" 表示从 stdin 读取), ruleset
→ 构建 PMDConfiguration
→ 配置 JSON 渲染器
→ 若 filePath 为 "-",从 stdin 读代码 → 写入临时文件
→ 执行 PMD 分析
→ 输出 JSON 到 stdout
→ 清理临时文件
```
**PmdAdapter.check() 虚拟文档处理**
```
if 虚拟文档 (uri.scheme === 'untitled')
→ java -cp "classpath;dist" PmdRunner "-" ruleset
→ stdin 传入 document.getText()
else
→ java -cp "classpath;dist" PmdRunner document.fileName ruleset
```
### 3.5 JSP 适配器设计
JSP 适配器是**组合适配器**,自身不做检查,而是将 JSP 文件拆分后交给其他适配器:
```
JSP 文件输入
├─ 1. 调用 PMD 检查 JSP 规范
│ → 使用 pmd-jsp-ruleset.xml
└─ 2. jsp-extractor 提取内嵌代码块
├─ <script> → JavaScript → ESLint
├─ <style> → CSS → Stylelint
└─ <% %> scriptlet → Java → PMD
└─ 行号偏移修正 → 合并结果
```
**提取器(jsp-extractor.ts**
| 代码块类型 | 正则匹配 | 目标适配器 |
|-----------|---------|-----------|
| `<script>` 标签 | `/<script\b[^>]*>([\s\S]*?)<\/script\s*>/gi` | ESLint |
| `<style>` 标签 | `/<style\b[^>]*>([\s\S]*?)<\/style\s*>/gi` | Stylelint |
| `<% %>` scriptlet | `/<%=?([\s\S]*?)%>/g` | PMD |
**JspAdapter.check() 流程**
```
1. 调用 PmdAdapter.check(document) → JSP 规范检查
2. extractJspSections(document.getText()) → 提取内嵌代码块
3. 对每个 section:
a. 按 language 选择对应适配器
b. 创建虚拟文档 (vscode.workspace.openTextDocument)
c. 调用 adapter.check(virtualDoc)
d. 修正行号偏移 (section.lineOffset)
4. 合并所有结果
```
### 3.6 适配器注册
采用**硬编码注册**,在 Orchestrator 中维护适配器列表:
```typescript
// orchestrator.ts
function getAdapters(): LinterAdapter[] {
return [
new ESLintAdapter(),
new PmdAdapter(),
new StylelintAdapter(),
new SqlLintAdapter(),
new JspAdapter(),
];
}
```
通过 `linters.<language>` 配置项控制每种语言使用的 linter(空字符串表示禁用)。
---
## 4. Orchestrator(编排器)
### 注册方式
采用**硬编码注册**,适配器列表在 `orchestrator.ts` 中静态定义(见 §3.6)。
### 执行策略
- **保存触发**:文件保存时自动运行,debounce 500ms(可配置)
- **手动触发**:命令面板、右键菜单、文件树右键
- **进度反馈**:运行时显示 `withProgress`
### 调度逻辑
```
1. 获取当前文档语言 ID
2. 查询 linters.<language> 配置 → 确定使用的适配器
3. 调用适配器 check(document, workingDir)
4. 收集结果,区分 status
5. 返回聚合后的 diagnostics 列表
```
---
## 5. AI 审查引擎
### 5.1 设计决策
| 决策项 | 结论 | 说明 |
|--------|------|------|
| 任务拆分 | **两并行请求** | 请求 A(自定义规则评估)+ 请求 B(翻译 + 深度审查) |
| 代码长度限制 | **暂不限制** | 后续可添加大文件截断 |
| 模型支持 | **Provider 层抽象** | 支持 DeepSeek / OpenAI,统一接口 |
| 成本控制 | **暂不考虑** | 不做 Token 上限、调用次数限制 |
| 修复复用 | **复用 Provider** | 单条修复和批量修复共用 Provider 层 |
| 错误感知 | **区分提示** | 不同错误类型显示不同提示信息 |
| Provider 与面板共用 | **ai.provider 通用** | 面板下拉框直接读写 `ai.provider` |
### 5.2 Provider 层架构
采用**策略模式**,将不同模型的 API 调用封装为独立 Provider,上层只依赖统一接口。
```
src/ai/
├── providers/
│ ├── base.ts # Provider 接口/抽象基类
│ ├── deepseek.ts # DeepSeek 实现
│ └── openai.ts # OpenAI 实现
├── factory.ts # Provider 工厂
├── engine.ts # 审查引擎(业务逻辑)
└── schema.ts # 响应结构定义
```
**Provider 接口**
```typescript
// providers/base.ts
export interface ChatOptions {
model: string;
temperature: number;
timeoutMs: number;
}
export abstract class AIProvider {
abstract id: string;
abstract name: string;
constructor(
protected apiKey: string,
protected baseUrl: string
) {}
abstract chat(
systemPrompt: string,
userPrompt: string,
options: ChatOptions
): Promise<string>;
}
```
**Provider 工厂**
```typescript
// factory.ts
const registry: Record<string, new (apiKey: string, baseUrl: string) => AIProvider> = {
deepseek: DeepSeekProvider,
openai: OpenAIProvider,
};
export function createProvider(providerId: string, apiKey: string, baseUrl: string): AIProvider {
const Cls = registry[providerId];
if (!Cls) throw new Error(`Unknown provider: ${providerId}`);
return new Cls(apiKey, baseUrl);
}
```
### 5.3 两并行请求方案
```
请求 A(规则审查) 请求 B(翻译 + 深度审查)
┌──────────────────┐ ┌─────────────────────────┐
│ 自定义规则评估 │ │ 静态分析结果翻译 │
│ (需要完整代码) │ │ (需静态分析结果) │
└──────────────────┘ │ AI 深度审查 │
│ (需代码 + 静态分析) │
└─────────────────────────┘
```
**请求分配理由**
| 任务 | 请求 | 理由 |
|------|------|------|
| 自定义规则评估 | A | 需完整代码,与深度审查关注点不同 |
| 静态分析翻译 | B | 与深度审查共享静态分析上下文,放一起省 Token |
| AI 深度审查 | B | 需代码 + 静态分析结果,与翻译同上下文 |
**执行流程**
```typescript
async function runAIReview(code: string, staticDiagnostics: LinterDiagnostic[], customRules: CustomRule[]) {
const [resultA, resultB] = await Promise.allSettled([
callCustomRuleReview(code, customRules), // 请求 A
callTranslateAndDeepReview(code, staticDiagnostics), // 请求 B
]);
return {
customRuleResults: resultA.status === 'fulfilled' ? resultA.value : [],
translatedDiagnostics: resultB.status === 'fulfilled' ? resultB.value.translated : [],
findings: resultB.status === 'fulfilled' ? resultB.value.findings : [],
degraded: resultA.status === 'rejected' || resultB.status === 'rejected',
error: collectErrors(resultA, resultB),
};
}
```
### 5.4 Prompt 设计
**请求 A — 自定义规则评估**
```
System: 你是代码规则审查员,只评估以下自定义规则是否被违反。
理解语义而非文本匹配。
仅输出 JSON,格式:{ customRuleResults: [{ ruleId, line, severity, message }] }
User: ## 自定义规则
<规则列表>
## 代码(带行号)
<代码内容>
```
**请求 B — 翻译 + 深度审查**
```
System: 你是资深代码审查专家,完成两个任务:
1. 将英文静态分析结果翻译为中文,并补充修复建议
2. 深度审查代码,发现静态分析未覆盖的问题
重点:安全漏洞、逻辑错误、性能问题、设计缺陷
不要重复静态分析已报告的问题
输出语言:<language>
仅输出 JSON
User: ## 代码(带行号)
<代码内容>
## 静态分析结果(英文)
<诊断列表>
```
### 5.5 AI 响应结构
```typescript
// ai/schema.ts
interface AIResponse {
translatedDiagnostics: TranslatedDiagnostic[];
customRuleResults: CustomRuleResult[];
findings: AIFinding[];
}
interface TranslatedDiagnostic {
originalRuleId: string;
translatedMessage: string;
translatedSuggestion: string;
codeDiff?: string;
}
interface CustomRuleResult {
ruleId: string; // 格式: "custom:规则id"
line: number;
severity: 'error' | 'warning' | 'info';
message: string;
}
interface AIFinding {
ruleId: string; // kebab-case,如 no-hardcoded-secret
severity: 'error' | 'warning' | 'info';
category: 'bug' | 'performance' | 'security' | 'style' | 'design';
title: string;
description: string;
suggestion: string;
codeDiff?: string; // unified diff 格式
line: number;
}
```
### 5.6 错误处理与用户感知
| 错误类型 | 用户感知 | 处理方式 |
|---------|---------|---------|
| 未配置 API Key | 面板显示「请先设置 API Key」+ 设置按钮 | 降级,静态分析结果正常显示 |
| API Key 无效 | 面板显示「API Key 无效,请重新设置」+ 设置按钮 | 降级,弹错误提示 |
| 网络超时 | 面板显示「AI 请求超时,可重试」+ 重试按钮 | 降级,静态分析结果正常显示 |
| 模型服务不可用 | 面板显示具体错误信息 | 降级 |
| JSON 解析失败 | 面板显示「AI 响应格式异常」+ 查看原始响应 | 降级,记录原始响应 |
| 部分请求失败 | 面板显示「部分 AI 功能不可用」+ 详情 | 部分降级 |
**降级策略**
1. 单请求失败不影响另一个请求(`Promise.allSettled`
2. 所有 AI 功能都失败时,纯静态分析结果仍然展示
3. 失败信息在面板顶部以黄色/红色提示条展示
---
## 6. 插件贡献点
### Commands
| 命令 | 功能 | 快捷键 |
|------|------|--------|
| `vscode-code-reviewer.review` | 运行完整审查(静态分析 + AI | Ctrl+Shift+R |
| `vscode-code-reviewer.reviewSelection` | 审查选中代码 | — |
| `vscode-code-reviewer.openPanel` | 显示审查报告面板 | — |
| `vscode-code-reviewer.exportReport` | 导出 Markdown 报告 | — |
| `vscode-code-reviewer.addCustomRule` | 添加自定义规则 | — |
| `vscode-code-reviewer.fixIssue` | 修复单条问题 | — |
| `vscode-code-reviewer.fixAll` | 批量修复 | — |
| `vscode-code-reviewer.openSetup` | 打开设置面板(侧边栏) | — |
### Views
- `codeReviewer.setupView` — 设置面板(侧边栏视图)
### 菜单集成
```json
{
"menus": {
"editor/context": [
{
"command": "vscode-code-reviewer.review",
"group": "navigation"
},
{
"command": "vscode-code-reviewer.reviewSelection",
"when": "editorHasSelection"
}
]
}
}
```
### 视图容器
```json
{
"viewsContainers": {
"activitybar": [
{
"id": "code-reviewer",
"title": "净码特工",
"icon": "images/icon.png"
}
]
},
"views": {
"code-reviewer": [
{
"type": "tree",
"id": "codeReviewer.setupView",
"name": "设置"
}
]
}
}
```
### Configuration
以下配置项通过 VS Code 原生设置界面(`Ctrl+,`)修改。AI 基础配置、API Key 和自定义规则的启用/禁用则在侧边栏设置面板中管理。
**AI 配置**
| 配置项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `vscode-code-reviewer.ai.provider` | enum | `deepseek` | 模型提供商:`deepseek` / `openai` |
| `vscode-code-reviewer.ai.model` | string | `deepseek-chat` | 模型名称 |
| `vscode-code-reviewer.ai.baseUrl` | string | `https://api.deepseek.com/v1` | API Base URL |
| `vscode-code-reviewer.ai.temperature` | number | 0.2 | AI 温度参数(建议 0.1-0.3 |
| `vscode-code-reviewer.ai.timeout` | number | 300 | AI 请求超时(秒) |
| `vscode-code-reviewer.ai.outputLanguage` | string | `zh-CN` | 输出语言:`zh-CN` / `en` / `ja` |
**Linter 配置**(每种语言单选,空字符串=禁用):
| 配置项 | 类型 | 默认值 | enum |
|--------|------|--------|------|
| `vscode-code-reviewer.linters.javascript` | enum | `eslint` | `""` / `eslint` |
| `vscode-code-reviewer.linters.typescript` | enum | `eslint` | `""` / `eslint` |
| `vscode-code-reviewer.linters.java` | enum | `pmd` | `""` / `pmd` |
| `vscode-code-reviewer.linters.jsp` | enum | `jsp` | `""` / `jsp` |
| `vscode-code-reviewer.linters.css` | enum | `stylelint` | `""` / `stylelint` |
| `vscode-code-reviewer.linters.sql` | enum | `sql-lint` | `""` / `sql-lint` |
| `vscode-code-reviewer.linters.plsql` | enum | `sql-lint` | `""` / `sql-lint` |
**PMD 配置**
| 配置项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `vscode-code-reviewer.pmd.jarPath` | string | `""` | PMD jar 路径(空=使用插件内置) |
| `vscode-code-reviewer.pmd.rulesetPath` | string | `""` | Java 规则集 XML 路径(空=使用内置) |
| `vscode-code-reviewer.pmd.jspRulesetPath` | string | `""` | JSP 规则集 XML 路径(空=使用内置) |
**其他**
| 配置项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `vscode-code-reviewer.sql-lint.configFile` | string | `""` | sqlfluff 配置文件路径 |
| `vscode-code-reviewer.fixer.contextLines` | number | 5 | AI 修复时提取的上下文行数 |
> API Key 不在此配置,通过 VS Code `SecretStorage` 存储,在设置面板中管理。
---
## 7. 审查面板(Webview
### 7.1 面板功能
采用 Webview 实现,分 Tab 展示三类审查结果:
| Tab | 来源 | 内容 |
|-----|------|------|
| 🔧 静态分析 | Linter 适配器 | ESLint / PMD / Stylelint / sql-lint / JSP 的问题列表 |
| 📋 自定义规则 | AI 请求 A | `custom:*` 规则评估结果 |
| 🤖 AI 审查 | AI 请求 B | 翻译后的诊断 + 深度审查发现 |
每条问题支持:点击跳转到代码位置、单条修复、添加忽略注释。
### 7.2 交互设计
```
┌─────────────────────────────────────┐
│ 📋 代码审查报告 │
│ xxx.java · Java · 3.2s │
├─────────────────────────────────────┤
│ [总计:10] [错误:3] [警告:5] [建议:2] │
├─────────────────────────────────────┤
│ 🔧 静态分析 | 📋 自定义规则 | 🤖 AI │
├─────────────────────────────────────┤
│ ┌─────────────────────────────────┐ │
│ │ 🔴 PMD L23 [修复] [忽略]│ │
│ │ 避免重复字符串常量 │ │
│ │ NoScriptlets.md │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ 🟡 custom L45 [修复] [忽略]│ │
│ │ SQL 拼接风险 │ │
│ └─────────────────────────────────┘ │
├─────────────────────────────────────┤
│ [🔄 重新审查] [📄 导出] [⚙️ 设置] │
│ [↩ 撤销上次修复] │
└─────────────────────────────────────┘
```
**降级提示**:当 AI 请求部分/全部失败时,面板顶部显示黄色/红色提示条,静态分析结果正常展示。
### 7.3 消息通信
Webview 与扩展通过 `postMessage` 通信:
```typescript
interface PanelMessage {
type: 'navigate' | 'rerun' | 'export' | 'settings' | 'fix' | 'fixAll';
line?: number;
ruleId?: string;
source?: 'linter' | 'custom' | 'ai';
}
// 扩展 → Webview:推送审查结果
interface PanelUpdate {
report: MergedReport;
degraded: boolean;
errors: string[];
hasSnapshot: boolean; // 撤销按钮状态
}
```
### 7.4 与结果合并器的关系
```
Linter 诊断 ─┐
AI 翻译 ─┤
自定义规则 ─┤→ Merger.mergeResults() → MergedReport → Webview 渲染
AI 深度审查 ─┤
执行错误 ─┘
用户操作 → PanelMessage → extension → 执行对应命令 → 更新面板
```
---
## 8. 自动修复模块
### 8.1 设计决策
| 决策项 | 结论 | 说明 |
|--------|------|------|
| 上下文行数 | **动态调整** | 根据问题类型决定上下文范围,不固定 5 行 |
| 修复匹配策略 | **行号 + 代码匹配** | 先按行号匹配原文,失败则在文件中搜索 |
| 批量修复 | **预览后应用** | 先展示所有修改,用户确认后才执行 |
| 修复后验证 | **不做验证** | 不重新跑 linter 或编译,信任 AI 输出 |
| 撤销机制 | **快照 + 撤销按钮** | 修复前保存快照,面板底部提供"撤销上次修复" |
| 修复范围限定 | **不限定** | 用户自行判断哪些问题可修复 |
| Code Action | **复用 AI Provider** | 单条修复与批量修复共用 §5.2 的 Provider 层 |
### 8.2 修复流程
```
用户触发修复(单条 / 批量)
prepareContext() 获取代码上下文(动态行数,见 §8.3)
generateFix() 调用 AI 生成修复方案(复用 §5.2 Provider
matchAndValidate() 匹配验证(行号 → 代码搜索,见 §8.4)
applyFix() 应用修复到编辑器(单条 / 批量倒序)
保存快照,更新撤销按钮状态
```
### 8.3 动态上下文策略
根据问题分类决定上下文的行数范围:
| 问题类型 | 上下文范围 | 说明 |
|---------|-----------|------|
| 命名问题(`naming`) | 问题行 ± 2 行 | 只需那一行 |
| 代码风格(`style`) | 问题行 ± 5 行 | 需要少量上下文 |
| 逻辑错误(`bug`) | 整个函数/方法 | 需完整逻辑块 |
| 安全漏洞(`security`) | 整个函数/方法 | 需完整逻辑块 |
| 性能问题(`performance`) | 整个函数/方法 | 需完整逻辑块 |
```typescript
interface FixableDiagnostic {
ruleId: string;
message: string;
line: number;
severity: string;
codeContext: string; // 带行号的代码片段
source: 'linter' | 'custom';
category: 'naming' | 'style' | 'bug' | 'security' | 'performance';
}
function prepareContext(document: vscode.TextDocument, diagnostic: LinterDiagnostic): FixableDiagnostic | null {
let startLine: number, endLine: number;
switch (diagnostic.category) {
case 'naming':
({ startLine, endLine } = rangeAround(diagnostic.line, 2)); break;
case 'style':
({ startLine, endLine } = rangeAround(diagnostic.line, 5)); break;
case 'bug':
case 'security':
case 'performance':
const funcRange = findEnclosingFunction(document, diagnostic.line);
startLine = funcRange?.start.line ?? diagnostic.line - 10;
endLine = funcRange?.end.line ?? diagnostic.line + 10;
break;
default:
({ startLine, endLine } = rangeAround(diagnostic.line, 5));
}
const codeContext = extractLines(document, startLine, endLine);
return { ...diagnostic, codeContext };
}
```
### 8.4 修复匹配策略
两阶段匹配,确保修复应用到正确位置:
```
阶段 1 — 行号匹配
提取问题行的原文 → 与 AI 返回的 originalText 首行对比
✓ 匹配 → 验证完整原文是否一致
✗ 不匹配 → 进入阶段 2
阶段 2 — 全文搜索
在文件中搜索 originalText
✓ 找到 → 通过 positionAt() 计算实际 range
✗ 未找到 → 标记为匹配失败
```
```typescript
interface CodeFix {
startLine: number;
endLine: number;
originalText: string; // AI 认为要替换的原文
newText: string; // 修复后的新代码
}
function matchAndValidate(document: vscode.TextDocument, fix: CodeFix): { matched: boolean; actualRange?: vscode.Range } {
// 阶段 1:按行号匹配
const lineContent = document.lineAt(fix.startLine).text;
if (lineContent === fix.originalText.split('\n')[0]) {
const range = new vscode.Range(fix.startLine, 0, fix.endLine, document.lineAt(fix.endLine).text.length);
if (document.getText(range) === fix.originalText) {
return { matched: true, actualRange: range };
}
}
// 阶段 2:全文搜索
const index = document.getText().indexOf(fix.originalText);
if (index !== -1) {
return {
matched: true,
actualRange: new vscode.Range(
document.positionAt(index),
document.positionAt(index + fix.originalText.length)
),
};
}
return { matched: false };
}
```
**失败处理**
- 单条修复失败 → 提示"代码已变更,无法定位问题位置"
- 批量修复部分失败 → 只应用成功匹配的修复,失败的在预览面板中标记
### 8.5 批量修复预览
批量修复不直接应用,先展示预览面板:
```
┌────────────────────────────────────────┐
│ 🔧 批量修复预览 │
├────────────────────────────────────────┤
│ 共 5 处修改,请确认后应用 │
│ │
│ ✓ L23 eslint:no-unused-vars │
│ - var unused = 1; │
│ + let count = 0; │
│ │
│ ✗ L67 custom:no-console │
│ 代码已变更,无法定位 │
│ │
│ [取消] [应用全部 (4 处)] │
└────────────────────────────────────────┘
```
**应用顺序**:按位置**倒序**执行(从文件末尾开始),避免行号偏移:
```typescript
function applyBatchFixes(document: vscode.TextDocument, fixes: CodeFix[]): number {
const validFixes = fixes.filter(f => f.matched);
const sorted = [...validFixes].sort((a, b) => b.startLine - a.startLine);
let applied = 0;
for (const fix of sorted) {
if (applySingleFix(document, fix)) applied++;
}
return applied;
}
```
### 8.6 撤销机制
每次修复前保存当前文件内容的快照:
```typescript
const snapshotStack: Map<string, string[]> = new Map();
function saveSnapshot(document: vscode.TextDocument): void {
const filePath = document.uri.fsPath;
if (!snapshotStack.has(filePath)) snapshotStack.set(filePath, []);
snapshotStack.get(filePath)!.push(document.getText());
}
function undoLastFix(document: vscode.TextDocument): boolean {
const stack = snapshotStack.get(document.uri.fsPath);
if (!stack || stack.length === 0) return false;
const previousContent = stack.pop()!;
const edit = new vscode.WorkspaceEdit();
edit.replace(document.uri, new vscode.Range(0, 0, document.lineCount, 0), previousContent);
return vscode.workspace.applyEdit(edit);
}
```
面板底部提供撤销按钮,上次修复成功时启用,无快照时禁用(灰色)。
---
## 9. 文件结构
```
vscode-code-reviewer/
├── src/
│ ├── extension.ts # 扩展入口
│ ├── orchestrator/
│ │ └── orchestrator.ts # 多 linter 调度与结果聚合
│ ├── adapters/ # 语言适配器(策略模式)
│ │ ├── adapter.ts # LinterAdapter 统一接口定义
│ │ ├── eslint.ts # JavaScript / TypeScript
│ │ ├── pmd.ts # Java
│ │ ├── stylelint.ts # CSS
│ │ ├── sql-lint.ts # SQL / PL/SQL
│ │ └── jsp.ts # JSP 组合适配器
│ ├── jsp/
│ │ └── jsp-extractor.ts # JSP 内嵌代码块提取器
│ ├── ai/ # AI 审查引擎(后续细化)
│ ├── fixer/ # 自动修复模块(§8)
│ ├── merger/ # 结果合并与报告(§14)
│ ├── rules/ # 自定义规则管理(后续细化)
│ ├── config/ # 配置管理(§13 设置面板)
│ ├── panel/ # UI 面板(§7 Webview 审查面板)
│ └── util/ # 工具函数
├── jars/ # 外部工具(PMD)
│ ├── pmd/lib/ # PMD 依赖库
│ ├── PmdRunner.java # PMD 调用包装器(支持 stdin
│ ├── pmd-java-ruleset.xml # Java 规则集
│ └── pmd-jsp-ruleset.xml # JSP 规则集
├── images/ # 图标资源
├── scripts/ # 构建脚本
│ ├── build.mjs # esbuild 打包
│ ├── download-pmd.mjs # PMD 下载脚本
│ └── package-prod.mjs # 生产打包
├── .code-review/ # 自定义规则目录(后续细化)
│ ├── rules/ # 规则定义文件 (*.yaml)
│ └── config.yaml # 规则启用状态配置
├── package.json
├── tsconfig.json
└── eslint.config.mjs
```
---
## 10. 排除范围(非本期实现)
- 与 GitHub/GitLab PR Review API 集成
- 多人协作审查工作流
---
## 11. 技术栈
- VSCode Extension API (^1.120.0)
- TypeScript (ES2022, Node16 module)
- esbuild(打包构建)
- ESLint + typescript-eslint(自检)
- Mocha + @vscode/test-electron(测试)
- LLM APIopenai 兼容接口)
- PMD 7.26.0Java/JSP 静态分析)
- Java 11+PMD 运行环境)
---
## 12. 自定义规则系统
### 12.1 设计决策
| 决策项 | 结论 | 说明 |
|--------|------|------|
| 规则定义方式 | 纯描述(自然语言) | 不依赖正则,AI 理解语义后判断是否违反 |
| 规则执行方式 | 纯 AI 评估 | 规则随代码发给 AI,由 AI 判断是否触发 |
| 规则 ID 前缀 | 统一 `custom:` | UI 展示为 `custom:no-console`,区别于 linter 规则 |
| 规则文件组织 | 多文件 + 文件夹 | `.code-review/rules/*.yaml`,按类别拆分 |
| 规则定位 | 项目级团队约定 | 与静态分析、AI 深度审查三者互补 |
| 严重程度 | 必须标注 | `error` / `warning` / `info` |
### 12.2 规则文件格式
自定义规则以 YAML 文件存储在工作区根目录 `.code-review/rules/` 下,按类别拆分:
```
.code-review/
├── rules/
│ ├── security-rules.yaml # 安全类规则
│ ├── coding-conventions.yaml # 编码规范类规则
│ └── naming-conventions.yaml # 命名约定类规则(可选)
└── config.yaml # 启用状态配置
```
**规则 YAML 字段说明**
| 字段 | 必填 | 类型 | 说明 |
|------|------|------|------|
| `id` | 是 | string | 规则唯一标识(不含 `custom:` 前缀,运行时自动拼接) |
| `severity` | 是 | string | `error` / `warning` / `info` |
| `description` | 是 | string | 规则的自然语言描述(AI 评估依据) |
| `message` | 是 | string | 触发时显示给用户的信息 |
| `languages` | 否 | string[] | 生效的语言列表,为空表示对所有语言生效 |
**模板文件**`security-rules.yaml`
```yaml
- id: no-hardcoded-secret
severity: error
description: 禁止在代码中硬编码 API Key、密码等敏感信息
message: 检测到硬编码密钥,请使用环境变量或密钥管理工具
languages: [java, javascript, typescript]
- id: no-sql-injection
severity: error
description: 禁止使用字符串拼接的方式构造 SQL 语句
message: 使用参数化查询(PreparedStatement)替代字符串拼接
languages: [java]
```
**模板文件**`coding-conventions.yaml`
```yaml
- id: no-console-log
severity: warning
description: 生产代码不应保留 console.log 调试语句
message: 请使用日志框架替代 console.log
languages: [javascript, typescript]
- id: no-magic-numbers
severity: info
description: 禁止在代码中使用未命名的魔术数字
message: 请将魔法数字提取为命名常量
languages: [java, javascript, typescript]
```
### 12.3 启用/禁用配置
**`config.yaml`**
```yaml
# 各规则文件的启用状态
enabled:
- security-rules.yaml
- coding-conventions.yaml
# 各规则的单独启用/禁用(覆盖文件级设置)
rules:
no-magic-numbers:
enabled: false
```
**加载逻辑**`rules/yaml-parser.ts`):
```
1. 扫描 .code-review/rules/*.yaml → 加载所有规则定义
2. 读取 .code-review/config.yaml
3. 按文件级 enabled 列表过滤
4. 按规则级 rules.<id>.enabled 覆盖
5. 返回激活的规则列表
```
### 12.4 与各组件的交互
```
自定义规则
├── 定义阶段 ── YAML 解析器 (rules/yaml-parser.ts)
│ → 解析 .code-review/rules/*.yaml
│ → 合并 config.yaml 启用状态
└── 执行阶段 ── AI 引擎 (ai/engine.ts 请求 A)
→ 将规则 description 注入 System Prompt
→ AI 评估代码是否违反规则
→ 返回 custom:规则ID 格式的结果
→ Merger 合并入最终报告
---
## 13. 设置面板
### 13.1 设计决策
| 决策项 | 结论 | 说明 |
|--------|------|------|
| 配置组织方式 | 按模块分组 | `aiConfig` / `linterConfig` / `fixerConfig` 三个命名空间 |
| API Key 存储 | 全局一个 SecretStorage | 切换 provider 时由用户更新 Key |
| 配置变更监听 | 不监听 | 下次审查时读取最新配置即可 |
| 配置验证 | 不做 | 依赖 `package.json` 的 enum/minimum 等约束 |
| 多根工作区 | 暂不考虑 | 取第一个工作区 |
| 设置面板形态 | 仅侧边栏视图 | 不另开独立面板 |
| provider 配置 | ai.provider 与面板共用 | 面板下拉框直接读写 `ai.provider` |
| linter 配置 | 单选 enum | `linters.<language>` 值只能是预设 linter 或空字符串(禁用) |
| 快速引导 | 三步渐进 | 步骤自动标记完成,引导用户完成初始配置 |
| 主按钮色彩 | 紫色 | 保存并测试连接按钮 `#7c3aed`,区别于绿色确认按钮 |
### 13.2 配置模块结构
```
src/config/
├── index.ts # 统一导出
├── ai.ts # AI 配置(provider/model/baseUrl/temperature/timeout/outputLanguage
├── linter.ts # Linter 配置(linters.<language>、PMD 路径)
├── fixer.ts # 修复器配置(contextLines
└── secret.ts # API Key SecretStorageget/set/delete/isConfigured
```
**配置读取方式**:每个模块导出 getter,运行时从 `vscode.workspace.getConfiguration` 读取,不做缓存。
**API Key 存储**:使用 VS Code `SecretStorage` 存储一个全局 Key`vscode-code-reviewer.apiKey`),切换 provider 时由用户更新。
### 13.3 面板布局
侧边栏视图 `codeReviewer.setupView`
```
┌──────────────────────────────────┐
│ ⚙ 代码审查 · 设置 │
├──────────────────────────────────┤
│ 快速开始 │
│ ┌─① 配置 AI 模型及 API Key │
│ │ ② 启用自定义规则 │
│ │ ③ 保存并测试连接 Ctrl+R │
│ └─ │
│ │
│ 审核引擎 │
│ 🔵 共通规则 — Linter 静态分析 │
│ 🟡 自定义规则 — 团队编码规范 │
│ 🟢 AI 审核 — 深度代码审查 │
│ │
│ AI 模型配置 │
│ ┌──────────────────────────┐ │
│ │ 模型提供商 [已配置] │ │
│ │ [DeepSeek ▼] │ │
│ │ 模型名称 │ │
│ │ [deepseek-chat ▼] │ │
│ │ 建议使用结构化输出模型 │ │
│ └──────────────────────────┘ │
│ │
│ API Key │
│ ┌──────────────────────────┐ │
│ │ API Key [已配置] │ │
│ │ [•••••••••••••••••] │ │
│ │ Base URL │ │
│ │ [https://api.de...] │ │
│ │ Key 仅本地安全存储 │ │
│ └──────────────────────────┘ │
│ │
│ 输出语言 │
│ AI 审查结果输出语言 │
│ [中文(简体) ▼] │
│ │
│ 自定义规则 │
│ ┌──────────────────────────┐ │
│ │ 规则列表 [3 条启用] │ │
│ │ [开关] no-console-in... ×│ │
│ │ [开关] max-function-l... ×│ │
│ │ [开关] require-javad... ×│ │
│ │ [开关] no-any-type ×│ │
│ │ [输入规则名称...] [+ 添加]│ │
│ └──────────────────────────┘ │
│ │
│ [重置] [保存并测试连接] │
└──────────────────────────────────┘
```
**各区域说明**
| 区域 | 元素 | 存储 |
|------|------|------|
| 快速开始 | 三步引导(圆形序号,完成变紫色) | — |
| 审核引擎 | 三个模块标签(紫/琥珀/绿圆点) | — |
| AI 模型配置 | 卡片:提供商下拉框 + 模型下拉框 + 状态标签 | `ai.provider` / `ai.model` |
| API Key | 卡片:密码输入框 + Base URL + 状态标签 | SecretStorage / `ai.baseUrl` |
| 输出语言 | 下拉框 | `ai.outputLanguage` |
| 自定义规则 | 卡片:开关列表 + 添加行 | `.code-review/config.yaml` |
**状态标签**:`已配置`(绿色药丸) / `未配置`(灰色药丸),实时反映配置完成状态。
### 13.4 快速引导流程
面板顶部提供三步渐进引导,步骤自动标记:
| 步骤 | 触发完成条件 | 行为 |
|------|------------|------|
| ① 配置 AI 模型及 API Key | API Key 输入框有值 | 圆形序号 `①` 变紫色 |
| ② 启用自定义规则 | 任意规则开关打开 | 圆形序号 `②` 变紫色 |
| ③ 保存并测试连接 | 前两步完成 + 测试成功 | 圆形序号 `③` 变紫色,底部显示快捷键 `Ctrl+Shift+R` |
### 13.5 保存与测试连接
| 阶段 | 按钮状态 | 显示 |
|------|---------|------|
| 待测试 | 正常 | `保存并测试连接`(紫色) |
| 测试中 | 禁用 + spinner | `<spinner> 测试中...` |
| 成功 | 可用 | `✓ 已连接`(绿色),Toast 绿色提示 5 秒 |
| 失败 | 可用 | `✗ 重试`(紫色),Toast 红色提示 5 秒 |
### 13.6 自定义规则文件管理
面板中规则列表展示所有已加载的规则,每条规则显示:
- 开关(紫色滑块)
- 规则名称(等宽字体)
- 删除按钮 `×`
**用户操作**
| 操作 | 行为 |
|------|------|
| 点击开关 | 即时切换,在 `config.yaml` 写入 `rules.<id>.enabled` 覆盖项 |
| 点击添加 | 打开规则向导 → 保存到 `common-rules.yaml` |
| 点击删除 | 在 `config.yaml` 写入 `enabled: false` |
> 不修改规则 YAML 文件本身,仅通过 `config.yaml` 的覆盖项控制启用状态,避免破坏团队规则定义。
---
## 14. 结果合并与报告
### 14.1 合并逻辑
来自适配器、AI 引擎、自定义规则三路结果通过 `Merger` 合并为一个报告:
```typescript
interface MergedReport {
linterDiagnostics: LinterDiagnostic[];
customRuleDiagnostics: LinterDiagnostic[];
translatedDiagnostics: TranslatedDiagnostic[];
aiFindings: AIFinding[];
linterCount: number;
customRuleCount: number;
aiCount: number;
errors: string[];
degraded: boolean;
duration: number;
filePath: string;
language: string;
adapterNames: string[];
fixableLinterIndices: number[];
fixableCustomIndices: number[];
}
```
**核心函数**
```typescript
function mergeResults(
staticDiagnostics: LinterDiagnostic[],
aiResponse: AIResponse,
errors: string[],
degraded: boolean,
startTime: number,
filePath: string,
language: string,
adapterIds: string[]
): MergedReport;
```
### 14.2 Markdown 导出格式
```markdown
# 代码审查报告
**文件:** `xxx.java`
**语言:** Java
**耗时:** 3.2s
## 🔧 PMD · 5 个问题
- 🔴 `pmd:AvoidDuplicateLiterals` L23
避免重复字符串常量...
## 📋 自定义规则 · 2 个问题
- 🔴 `no-sql-injection` L45
检测到 SQL 拼接...
## 🤖 AI 审查 · 3 条建议
- 🟡 [AI] [security] `hardcoded-secret` L12
发现硬编码密码...
建议: 使用环境变量...
```diff
- String password = "admin123";
+ String password = System.getenv("DB_PASSWORD");
```
```
---
## 15. 构建与打包
### 15.1 构建脚本
```javascript
// scripts/build.mjs
import * as esbuild from 'esbuild';
await esbuild.build({
entryPoints: ['src/extension.ts'],
bundle: true,
outfile: 'out/extension.js',
external: ['vscode'],
format: 'cjs',
platform: 'node',
target: 'node22',
minify: true,
sourcemap: false,
});
```
### 15.2 开发流程
```bash
# 安装依赖
npm install
# 编译
npm run compile
# 监听模式
npm run watch
# 运行测试
npm test
# 本地调试
# 在 VS Code 中按 F5 启动扩展开发宿主
```
### 15.3 生产打包
```bash
# 打包 vsix
vsce package
```
---
## 16. 测试策略
### 16.1 测试文件结构
```
src/test/
├── fixtures/ # 测试数据
│ ├── admin-system/ # Java 项目示例
│ │ ├── src/main/java/...
│ │ ├── src/main/webapp/...
│ │ └── pom.xml
│ └── UserService.java # 单文件测试
├── adapter.test.ts # 适配器测试
├── ai-engine.test.ts # AI 引擎测试
├── config.test.ts # 配置测试
├── merger.test.ts # 合并逻辑测试
├── pipeline.test.ts # 完整流程测试
└── extension.test.ts # 扩展入口测试
```
### 16.2 关键测试场景
| 场景 | 说明 |
|------|------|
| 适配器测试 | 验证各语言 linter 输出解析正确性 |
| AI 引擎测试 | 验证 Prompt 构建、JSON 解析、错误处理 |
| 合并测试 | 验证多源结果合并、统计计算 |
| 完整流程测试 | 模拟从代码输入到报告输出全链路 |
---
## 17. 依赖管理
### 17.1 运行时依赖
```json
{
"dependencies": {
"eslint": "^9.39.3",
"stylelint": "^17.14.0",
"node-sql-parser": "^5.4.0"
}
}
```
### 17.2 开发依赖
```json
{
"devDependencies": {
"@types/vscode": "^1.120.0",
"@types/node": "22.x",
"typescript": "^5.9.3",
"esbuild": "^0.28.1",
"eslint": "^9.39.3",
"typescript-eslint": "^8.56.1",
"@vscode/test-cli": "^0.0.12",
"@vscode/test-electron": "^2.5.2",
"@vscode/vsce": "^3.9.2"
}
}
```
### 17.3 外部工具
| 工具 | 版本 | 用途 | 获取方式 |
|------|------|------|----------|
| PMD | 7.26.0 | Java/JSP 静态分析 | 自带 `jars/pmd/` |
| Java | 11+ | PMD 运行环境 | 用户环境 |
---
## 18. 附录
### A. 文件清单
| 文件 | 说明 |
|------|------|
| `src/extension.ts` | 扩展入口,命令注册、流程编排 |
| `src/adapters/adapter.ts` | LinterAdapter 接口 + AdapterResult |
| `src/adapters/eslint.ts` | JS/TS 适配器 |
| `src/adapters/pmd.ts` | Java 适配器(支持虚拟文档) |
| `src/adapters/stylelint.ts` | CSS 适配器 |
| `src/adapters/sql-lint.ts` | SQL/PLSQL 适配器 |
| `src/adapters/jsp.ts` | JSP 组合适配器 |
| `src/jsp/jsp-extractor.ts` | JSP 内嵌代码块提取器 |
| `src/orchestrator/orchestrator.ts` | 编排调度 |
| `src/ai/engine.ts` | AI 审查引擎 |
| `src/ai/schema.ts` | AI 响应结构定义 |
| `src/ai/providers/base.ts` | AIProvider 抽象基类 |
| `src/ai/providers/deepseek.ts` | DeepSeek 实现 |
| `src/ai/providers/openai.ts` | OpenAI 实现 |
| `src/ai/factory.ts` | Provider 工厂函数 |
| `src/fixer/fixer.ts` | 自动修复逻辑 |
| `src/merger/merger.ts` | 结果合并 |
| `src/rules/yaml-parser.ts` | YAML 规则解析 |
| `src/config/ai.ts` | AI 配置 |
| `src/config/linter.ts` | Linter 配置 |
| `src/config/fixer.ts` | 修复器配置 |
| `src/config/secret.ts` | API Key SecretStorage |
| `src/config/index.ts` | 配置统一导出 |
| `src/panel/webview.ts` | 审查报告面板 |
| `src/**/setupView.ts` | 侧边栏设置视图 |
| `jars/pmd/PmdRunner.java` | PMD Java 包装器(支持 stdin |
| `jars/pmd-java-ruleset.xml` | Java 规则集 |
| `jars/pmd-jsp-ruleset.xml` | JSP 规则集 |
| `scripts/build.mjs` | esbuild 构建脚本 |
| `.code-review/rules/*.yaml` | 自定义规则定义 |
| `.code-review/config.yaml` | 规则启用状态 |
### B. 关键接口签名
```typescript
// 适配器
function getAdapters(): LinterAdapter[];
// 静态分析编排
function runStaticAnalysis(documents: vscode.TextDocument[], workingDir: string): Promise<StaticAnalysisResult>;
// AI 审查
function runAIReview(context: vscode.ExtensionContext, code: string, diagnostics: LinterDiagnostic[], customRules: CustomRule[]): Promise<AIEngineResult>;
// 结果合并
function mergeResults(staticDiagnostics: LinterDiagnostic[], aiResponse: AIResponse, errors: string[], degraded: boolean, startTime: number, filePath: string, language: string, adapterIds: string[]): MergedReport;
// 自动修复
function generateFix(context: vscode.ExtensionContext, diagnostic: FixableDiagnostic): Promise<CodeFix | null>;
function applyFix(fix: CodeFix): Promise<boolean>;
// 报告导出
function reportToMarkdown(report: MergedReport): string;
// 自定义规则
function loadActiveRules(workspaceRoot: string): CustomRule[];
```