- 设置面板:三步引导 / AI 配置 / API Key / 规则管理 / 连接测试 - Provider 重构:统一 OpenAICompatibleProvider 基类,新增 Gemini/Claude/混元/智谱等 - 审查面板 Webview:三 Tab、统计卡片、问题列表、postMessage 通信 - esbuild 构建脚本 + 生产打包 + PMD 下载 - 保存文件自动静态分析(500ms debounce)
19 KiB
vscode-code-reviewer 实施拆分方案
决策汇总
| 决策项 | 选择 |
|---|---|
| 拆分策略 | 按层级自底向上 |
| 适配器优先级 | 按复杂度递增:ESLint → Stylelint → sql-lint → PMD → JSP |
| 核心层B顺序 | 按依赖链:Provider → AI引擎 → 规则解析 → 结果合并 → 自动修复 → 报告导出 |
| UI层顺序 | 按依赖顺序:命令注册 → 设置面板 → 审查面板 → Code Action |
| 构建与测试 | 集中在最后 Phase |
整体 Phase 划分
Phase 1: 基础层 Types + Config 管理
Phase 2: 适配层 5 个 Linter 适配器
Phase 3: 核心层 A 编排器 Orchestrator
Phase 4: 核心层 B AI引擎 + 规则 + 合并 + 修复 + 报告
Phase 5: UI 层 命令 + 设置面板 + 审查面板
Phase 6: 构建与测试 esbuild + 测试
依赖关系图
Phase 1 ────→ Phase 2 ────→ Phase 3 ────→ Phase 4 ────→ Phase 5 ────→ Phase 6
↑ ↑
│ │
(2.1 接口) (4.1 Provider)
(2.2~2.6 适配器) (4.2~4.6 其余)
Phase 2 内部必须按 2.1 → 2.2 → 2.3 → 2.4 → 2.5 → 2.6 顺序,JSP 适配器依赖 PMD/ESLint/Stylelint。
Phase 4 内部必须按 4.1 → 4.2 → 4.3 → 4.4 → 4.5 → 4.6 顺序,Fixer 依赖 Provider 和 Merger。
Phase 1: 基础层
依赖: 无
目标: 建立公共类型定义和配置管理,为所有上层模块提供基础能力
参考设计: §3.2, §6.4, §13.2
文件清单
| 文件 | 操作 | 内容 |
|---|---|---|
src/types.ts |
新建 | LinterDiagnostic、AdapterResult、LinterAdapter 等公共类型 |
src/config/index.ts |
新建 | 统一导出 |
src/config/ai.ts |
新建 | AI 配置 getter(provider/model/baseUrl/temperature/timeout/outputLanguage) |
src/config/linter.ts |
新建 | Linter 配置 getter(linters.xxx 语言-linter 映射、PMD 路径) |
src/config/fixer.ts |
新建 | 修复器配置(contextLines) |
src/config/secret.ts |
新建 | API Key SecretStorage(get/set/delete/isConfigured) |
关键类型
// src/types.ts
export type Severity = 'error' | 'warning' | 'info';
export type AdapterStatus = 'ok' | 'tool-unavailable' | 'execution-failed';
export interface LinterDiagnostic {
severity: Severity;
ruleId: string;
message: string;
range: vscode.Range;
suggestion?: string;
}
export interface AdapterResult {
diagnostics: LinterDiagnostic[];
status: AdapterStatus;
errorMessage?: string;
}
export interface LinterAdapter {
id: string;
supportedLanguages: string[];
check(document: vscode.TextDocument, workingDir: string): Promise<AdapterResult>;
isAvailable(): boolean;
}
配置 Key 映射
所有配置以 vscode-code-reviewer. 为前缀:
| 模块 | 配置项 | 类型 | 默认值 |
|---|---|---|---|
| ai | ai.provider |
enum | deepseek |
| ai | ai.model |
string | deepseek-chat |
| ai | ai.baseUrl |
string | https://api.deepseek.com/v1 |
| ai | ai.temperature |
number | 0.2 |
| ai | ai.timeout |
number | 300 |
| ai | ai.outputLanguage |
string | zh-CN |
| linter | linters.javascript |
enum | eslint |
| linter | linters.typescript |
enum | eslint |
| linter | linters.java |
enum | pmd |
| linter | linters.jsp |
enum | jsp |
| linter | linters.css |
enum | stylelint |
| linter | linters.sql |
enum | sql-lint |
| linter | linters.plsql |
enum | sql-lint |
| linter | pmd.jarPath |
string | "" |
| linter | pmd.rulesetPath |
string | "" |
| linter | pmd.jspRulesetPath |
string | "" |
| linter | sql-lint.configFile |
string | "" |
| fixer | fixer.contextLines |
number | 5 |
| secret | API Key | SecretStorage | vscode-code-reviewer.apiKey |
Phase 2: 适配层
依赖: Phase 1
目标: 实现 5 个语言适配器,统一 LinterAdapter 接口
参考设计: §3
Phase 2.1: 适配器接口
| 文件 | 操作 | 内容 |
|---|---|---|
src/adapters/adapter.ts |
新建 | 重新导出 LinterAdapter 接口(或在此处定义,取决于代码组织) |
Phase 2.2: ESLint 适配器
参考设计: §3.3
| 文件 | 操作 | 内容 |
|---|---|---|
src/adapters/eslint.ts |
新建 | ESLintAdapter implements LinterAdapter |
supportedLanguages: ['javascript', 'typescript'] |
||
check(): 使用 eslint npm 包 lintText() |
||
isAvailable(): 检测 eslint 是否已安装 |
npm 依赖: eslint ^9.39.3
Phase 2.3: Stylelint 适配器
参考设计: §3.3
| 文件 | 操作 | 内容 |
|---|---|---|
src/adapters/stylelint.ts |
新建 | StylelintAdapter implements LinterAdapter |
supportedLanguages: ['css'] |
||
check(): 使用 stylelint npm 包 lint({ code }) |
||
isAvailable(): 检测 stylelint 是否已安装 |
npm 依赖: stylelint ^17.14.0
Phase 2.4: sql-lint 适配器
参考设计: §3.3
| 文件 | 操作 | 内容 |
|---|---|---|
src/adapters/sql-lint.ts |
新建 | SqlLintAdapter implements LinterAdapter |
supportedLanguages: ['sql', 'plsql'] |
||
check(): CLI 子进程调用 sqlfluff |
||
isAvailable(): 检测 sqlfluff CLI 是否可用 |
||
| 方言映射:sql → ansi, plsql → postgres |
Phase 2.5: PMD 适配器
参考设计: §3.3, §3.4
| 文件 | 操作 | 内容 |
|---|---|---|
src/adapters/pmd.ts |
新建 | PmdAdapter implements LinterAdapter |
supportedLanguages: ['java'] |
||
check(): Java 子进程调用 PmdRunner |
||
| 虚拟文档 (untitled) 通过 stdin 传入代码 | ||
| 真实文件传文件路径 | ||
isAvailable(): 检测 Java 11+ 和 PMD JAR |
||
jars/pmd/PmdRunner.java |
新建 | PMD 包装器:stdin 支持 + JSON 渲染器 |
jars/pmd/pmd-java-ruleset.xml |
新建 | Java 规则集 |
jars/pmd/pmd-jsp-ruleset.xml |
新建 | JSP 规则集 |
PmdRunner.java 核心逻辑:
参数: filePath (传 "-" 表示从 stdin 读取), ruleset
→ 构建 PMDConfiguration
→ 配置 JSON 渲染器
→ 若 filePath 为 "-",从 stdin 读代码 → 写入临时文件
→ 执行 PMD 分析
→ 输出 JSON 到 stdout
→ 清理临时文件
PMD JAR 目录结构:
jars/pmd/
├── lib/ # PMD 依赖 JAR(需下载)
├── PmdRunner.java # 包装器(编译为 .class)
├── pmd-java-ruleset.xml
└── pmd-jsp-ruleset.xml
Phase 2.6: JSP 适配器
依赖: Phase 2.5, 2.2, 2.3(需要 PMD/ESLint/Stylelint 适配器)
参考设计: §3.5
| 文件 | 操作 | 内容 |
|---|---|---|
src/jsp/jsp-extractor.ts |
新建 | JSP 内嵌代码块提取器 |
src/adapters/jsp.ts |
新建 | JspAdapter implements LinterAdapter(组合适配器) |
supportedLanguages: ['jsp'] |
||
check(): 三步流程 |
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. 合并所有结果
提取器正则规则:
| 代码块类型 | 正则匹配 | 目标适配器 |
|---|---|---|
<script> 标签 |
/<script\b[^>]*>([\s\S]*?)<\/script\s*>/gi |
ESLint |
<style> 标签 |
/<style\b[^>]*>([\s\S]*?)<\/style\s*>/gi |
Stylelint |
<% %> scriptlet |
/<%=?([\s\S]*?)%>/g |
PMD |
Phase 3: 核心层 A — 编排器
依赖: Phase 2
参考设计: §3.6, §4
| 文件 | 操作 | 内容 |
|---|---|---|
src/orchestrator/orchestrator.ts |
新建 | Orchestrator 类 |
关键函数:
getAdapters()— 硬编码返回 5 个适配器实例runStaticAnalysis(document, workingDir)— 按语言选择适配器 → 调用check()→ 聚合- 保存监听 + debounce 500ms
调度逻辑:
1. 获取当前文档语言 ID
2. 查询 linters.<language> 配置 → 确定使用的适配器
3. 调用适配器 check(document, workingDir)
4. 收集结果,区分 status
5. 返回聚合后的 diagnostics 列表
Phase 4: 核心层 B
依赖: Phase 3
参考设计: §5, §8, §12, §14
Phase 4.1: AI Provider 基础设施
参考设计: §5.2
| 文件 | 操作 | 内容 |
|---|---|---|
src/ai/providers/base.ts |
新建 | AIProvider 抽象基类 + ChatOptions 接口 |
src/ai/providers/deepseek.ts |
新建 | DeepSeekProvider extends AIProvider |
src/ai/providers/openai.ts |
新建 | OpenAIProvider extends AIProvider |
src/ai/factory.ts |
新建 | createProvider(providerId, apiKey, baseUrl) 工厂函数 |
Provider 接口:
interface ChatOptions {
model: string;
temperature: number;
timeoutMs: number;
}
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>;
}
Phase 4.2: AI 引擎 + Schema
参考设计: §5.3, §5.4, §5.5, §5.6
| 文件 | 操作 | 内容 |
|---|---|---|
src/ai/schema.ts |
新建 | AIResponse, TranslatedDiagnostic, CustomRuleResult, AIFinding 接口 |
src/ai/engine.ts |
新建 | runAIReview() 主函数 |
引擎核心逻辑:
async function runAIReview(code, staticDiagnostics, customRules) {
const [resultA, resultB] = await Promise.allSettled([
callCustomRuleReview(code, customRules), // 请求 A
callTranslateAndDeepReview(code, staticDiagnostics), // 请求 B
]);
return {
customRuleResults: ...,
translatedDiagnostics: ...,
findings: ...,
degraded: resultA.status === 'rejected' || resultB.status === 'rejected',
error: ...,
};
}
两并行请求:
- 请求 A: 自定义规则评估(system prompt 注入规则 description)
- 请求 B: 静态分析翻译 + AI 深度审查
降级策略:
- 单请求失败不影响另一个请求(
Promise.allSettled) - 所有 AI 功能都失败时,纯静态分析结果仍然展示
- 失败信息在面板顶部以黄色/红色提示条展示
Phase 4.3: 自定义规则系统
参考设计: §12
| 文件 | 操作 | 内容 |
|---|---|---|
src/rules/yaml-parser.ts |
新建 | loadActiveRules(workspaceRoot) |
加载逻辑:
1. 扫描 .code-review/rules/*.yaml → 加载所有规则定义
2. 读取 .code-review/config.yaml
3. 按文件级 enabled 列表过滤
4. 按规则级 rules.<id>.enabled 覆盖
5. 返回激活的规则列表
CustomRule 类型:
interface CustomRule {
id: string; // 不含 custom: 前缀,运行时自动拼接
severity: Severity;
description: string; // AI 评估依据
message: string; // 触发时显示
languages?: string[];
}
Phase 4.4: 结果合并
参考设计: §14.1
| 文件 | 操作 | 内容 |
|---|---|---|
src/merger/merger.ts |
新建 | mergeResults() → MergedReport |
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[];
}
Phase 4.5: 自动修复
参考设计: §8
| 文件 | 操作 | 内容 |
|---|---|---|
src/fixer/fixer.ts |
新建 | generateFix(), applyFix(), applyBatchFixes(), undoLastFix() |
核心流程:
用户触发修复(单条 / 批量)
↓
prepareContext() 获取代码上下文(动态行数)
↓
generateFix() 调用 AI 生成修复方案(复用 Provider)
↓
matchAndValidate() 匹配验证(行号 → 代码搜索)
↓
applyFix() 应用修复到编辑器(单条 / 批量倒序)
↓
保存快照,更新撤销按钮状态
动态上下文策略:
| 问题类型 | 上下文范围 |
|---|---|
| 命名(naming) | 问题行 ± 2 行 |
| 代码风格(style) | 问题行 ± 5 行 |
| 逻辑/安全/性能(bug/security/performance) | 整个函数/方法 |
两阶段匹配:
- 阶段 1: 按行号匹配原文
- 阶段 2: 全文搜索 originalText
Phase 4.6: 报告导出
参考设计: §14.2
| 文件 | 操作 | 内容 |
|---|---|---|
src/utils/report.ts |
新建 | reportToMarkdown(report: MergedReport): string |
Markdown 格式: 文件信息 → 统计摘要 → 分来源列出问题(linter/自定义/AI)
Phase 5: UI 层
依赖: Phase 4
参考设计: §6, §7, §13
Phase 5.1: 命令注册 + extension.ts 更新
参考设计: §6.1
| 文件 | 操作 | 内容 |
|---|---|---|
src/activation/commands.ts |
新建 | 注册所有命令处理函数 |
package.json |
修改 | 替换 helloWorld 为正式命令、添加 viewsContainers/views/menus/configuration |
src/extension.ts |
修改 | activate 中注册命令、视图、监听保存事件 |
8 个命令:
| 命令 ID | 功能 | 快捷键 |
|---|---|---|
codeReviewer.review |
运行完整审查(静态分析 + AI) | Ctrl+Shift+R |
codeReviewer.reviewSelection |
审查选中代码 | — |
codeReviewer.openPanel |
显示审查报告面板 | — |
codeReviewer.exportReport |
导出 Markdown 报告 | — |
codeReviewer.addCustomRule |
添加自定义规则 | — |
codeReviewer.fixIssue |
修复单条问题 | — |
codeReviewer.fixAll |
批量修复 | — |
codeReviewer.openSetup |
打开设置面板(侧边栏) | — |
package.json 需添加的贡献点:
{
"viewsContainers": {
"activitybar": [{
"id": "code-reviewer",
"title": "CodeGuard 代码审查",
"icon": "images/icon.png"
}]
},
"views": {
"code-reviewer": [{
"type": "tree",
"id": "codeReviewer.setupView",
"name": "设置"
}]
},
"menus": {
"editor/context": [
{ "command": "codeReviewer.review", "group": "navigation" },
{ "command": "codeReviewer.reviewSelection", "when": "editorHasSelection" }
]
},
"configuration": {
// 见 Phase 1 配置 Key 映射
}
}
Phase 5.2: 设置面板(侧边栏 TreeView)
参考设计: §13
| 文件 | 操作 | 内容 |
|---|---|---|
src/views/setupView.ts |
新建 | SetupViewProvider implements vscode.TreeDataProvider |
面板区域:
| 区域 | 元素 | 数据源 |
|---|---|---|
| 快速开始 | 三步引导(①②③ 圆形序号,完成变紫色) | 实时状态 |
| 审核引擎 | 三个模块标签(紫/琥珀/绿圆点) | 静态 |
| AI 模型配置 | 提供商下拉框 + 模型下拉框 + 状态标签 | ai.provider / ai.model |
| API Key | 密码输入框 + Base URL + 状态标签 | SecretStorage / ai.baseUrl |
| 输出语言 | 下拉框 | ai.outputLanguage |
| 自定义规则 | 开关列表 + 添加行 | .code-review/config.yaml |
快速引导:
| 步骤 | 触发条件 | 效果 |
|---|---|---|
| ① 配置 AI 模型及 API Key | API Key 有值 | 序号变紫色 |
| ② 启用自定义规则 | 任意规则开关打开 | 序号变紫色 |
| ③ 保存并测试连接 | 前两步完成 + 测试成功 | 序号变紫色 |
Phase 5.3: 审查面板(Webview)
参考设计: §7
| 文件 | 操作 | 内容 |
|---|---|---|
src/panel/webview.ts |
新建 | ReviewPanel 类(Webview 管理) |
面板布局:
┌─────────────────────────────────────┐
│ 📋 代码审查报告 │
│ xxx.java · Java · 3.2s │
├─────────────────────────────────────┤
│ [总计:10] [错误:3] [警告:5] [建议:2] │
├─────────────────────────────────────┤
│ 🔧 静态分析 | 📋 自定义规则 | 🤖 AI │
├─────────────────────────────────────┤
│ 问题列表(可跳转、修复、忽略) │
├─────────────────────────────────────┤
│ [🔄 重新审查] [📄 导出] [⚙️ 设置] │
│ [↩ 撤销上次修复] │
└─────────────────────────────────────┘
消息协议:
// Webview → Extension
interface PanelMessage {
type: 'navigate' | 'rerun' | 'export' | 'settings' | 'fix' | 'fixAll';
line?: number;
ruleId?: string;
source?: 'linter' | 'custom' | 'ai';
}
// Extension → Webview
interface PanelUpdate {
report: MergedReport;
degraded: boolean;
errors: string[];
hasSnapshot: boolean;
}
Phase 6: 构建与测试
依赖: Phase 5
参考设计: §15, §16, §17
Phase 6.1: 构建脚本
| 文件 | 操作 | 内容 |
|---|---|---|
scripts/build.mjs |
新建 | esbuild 打包(bundle + minify + external vscode) |
package.json |
修改 | 更新 vscode:prepublish / 添加 build 脚本 |
esbuild 配置:
entryPoints: ['src/extension.ts'],
bundle: true,
outfile: 'out/extension.js',
external: ['vscode'],
format: 'cjs',
platform: 'node',
target: 'node22',
Phase 6.2: 工具脚本
| 文件 | 操作 | 内容 |
|---|---|---|
scripts/download-pmd.mjs |
新建 | 自动下载 PMD 7.26.0 JAR 依赖 |
scripts/package-prod.mjs |
新建 | 生产打包:esbuild + copy assets + vsce |
Phase 6.3: 测试
| 文件 | 操作 | 内容 |
|---|---|---|
src/test/fixtures/ |
新建 | 测试用代码样本目录 |
src/test/adapter.test.ts |
新建 | ESLint/Stylelint/sql-lint 输出解析测试 |
src/test/config.test.ts |
新建 | 配置读取正确性测试 |
src/test/merger.test.ts |
新建 | 多源结果合并 + 统计计算测试 |
src/test/pipeline.test.ts |
新建 | 全链路集成测试 |
src/test/extension.test.ts |
修改 | 替换占位测试 |
文件变更总数
| Phase | 新建 | 修改 | 合计 |
|---|---|---|---|
| Phase 1 | 6 | 0 | 6 |
| Phase 2 | 10 | 0 | 10 |
| Phase 3 | 1 | 0 | 1 |
| Phase 4 | 9 | 0 | 9 |
| Phase 5 | 3 | 2 | 5 |
| Phase 6 | 4 | 2 | 6 |
| 总计 | 33 | 4 | 37 |
npm 依赖(需在 Phase 2/4 时添加)
运行时依赖
{
"eslint": "^9.39.3",
"stylelint": "^17.14.0",
"node-sql-parser": "^5.4.0"
}
开发依赖(新增)
{
"esbuild": "^0.28.1",
"@vscode/vsce": "^3.9.2"
}
外部工具
| 工具 | 版本 | 用途 | 获取方式 |
|---|---|---|---|
| PMD | 7.26.0 | Java/JSP 静态分析 | 自带 jars/pmd/lib/ |
| Java | 11+ | PMD 运行环境 | 用户环境 |
| sqlfluff | — | SQL 静态分析 | 用户环境(pip install sqlfluff) |