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

45 KiB
Raw Blame History

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 统一接口

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 中维护适配器列表:

// 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 接口

// 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 工厂

// 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 需代码 + 静态分析结果,与翻译同上下文

执行流程

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 响应结构

// 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 — 设置面板(侧边栏视图)

菜单集成

{
  "menus": {
    "editor/context": [
      {
        "command": "vscode-code-reviewer.review",
        "group": "navigation"
      },
      {
        "command": "vscode-code-reviewer.reviewSelection",
        "when": "editorHasSelection"
      }
    ]
  }
}

视图容器

{
  "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 通信:

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 整个函数/方法 需完整逻辑块
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
  ✗ 未找到 → 标记为匹配失败
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 处)]                 │
└────────────────────────────────────────┘

应用顺序:按位置倒序执行(从文件末尾开始),避免行号偏移:

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 撤销机制

每次修复前保存当前文件内容的快照:

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

- 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

- 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

# 各规则文件的启用状态
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.、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[];
}

核心函数

function mergeResults(
  staticDiagnostics: LinterDiagnostic[],
  aiResponse: AIResponse,
  errors: string[],
  degraded: boolean,
  startTime: number,
  filePath: string,
  language: string,
  adapterIds: string[]
): MergedReport;

14.2 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 开发流程

# 安装依赖
npm install

# 编译
npm run compile

# 监听模式
npm run watch

# 运行测试
npm test

# 本地调试
# 在 VS Code 中按 F5 启动扩展开发宿主

15.3 生产打包

# 打包 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 运行时依赖

{
  "dependencies": {
    "eslint": "^9.39.3",
    "stylelint": "^17.14.0",
    "node-sql-parser": "^5.4.0"
  }
}

17.2 开发依赖

{
  "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. 关键接口签名

// 适配器
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[];