docs: upload code files and config
This commit is contained in:
@@ -6,75 +6,179 @@ VSCode 代码审查与规范检查一体化工具。集成多语言静态分析
|
||||
|
||||
### 核心能力
|
||||
|
||||
- 多语言代码静态分析(ESLint、Ruff、Clippy 等)
|
||||
- AI 辅助深度审查与问题解释
|
||||
- 自定义规则检查
|
||||
- 自动修复建议与批量修复
|
||||
- 可视化审查报告面板
|
||||
- 多语言代码静态分析(ESLint、PMD、Stylelint、sql-lint 等)
|
||||
- AI 辅助深度审查与问题解释/翻译
|
||||
- 自定义规则检查(YAML 定义 + AI 语义评估)
|
||||
- 自动修复建议与批量修复(快照撤销)
|
||||
- Webview 审查报告面板
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构总览
|
||||
|
||||
插件分为三层:
|
||||
插件分为四层:
|
||||
|
||||
```
|
||||
UI 层 — TreeView 面板 / Inline Diagnostic / Code Action
|
||||
核心层 — Linter 管理器 + AI 审查引擎(均实现 Analyzer 接口)
|
||||
UI 层 — Webview 审查面板 / Inline Diagnostic / Code Action
|
||||
核心层 — Orchestrator(编排器)+ AI 审查引擎
|
||||
适配层 — Linter 适配器(统一 LinterAdapter 接口)
|
||||
基础层 — 配置管理 / 规则管理 / 报告导出
|
||||
```
|
||||
|
||||
所有 linter 和 AI 审查器统一实现 `Analyzer` 接口,结果聚合后通过 VSCode `DiagnosticCollection` 展示。
|
||||
所有 linter 统一实现 `LinterAdapter` 接口,通过 Orchestrator 调度,结果聚合后通过 Webview 面板展示。
|
||||
|
||||
---
|
||||
|
||||
## 3. Analyzer 接口
|
||||
## 3. 适配器层
|
||||
|
||||
### 3.1 设计决策
|
||||
|
||||
| 决策项 | 结论 | 说明 |
|
||||
|--------|------|------|
|
||||
| 支持语言 | Java / JS/TS / CSS / SQL / JSP | 不支持 Python、Go |
|
||||
| 注册方式 | 硬编码(方案 A) | 适配器数量少,无需过度设计 |
|
||||
| 审查粒度 | 单文件 | `check()` 接收单个 `TextDocument` |
|
||||
| 目标平台 | Windows | PMD classpath 分隔符使用 `;` |
|
||||
|
||||
### 3.2 统一接口
|
||||
|
||||
```typescript
|
||||
interface AnalyzerResult {
|
||||
file: string;
|
||||
line: number;
|
||||
column: number;
|
||||
severity: 'error' | 'warning' | 'info' | 'hint';
|
||||
interface LinterDiagnostic {
|
||||
severity: 'error' | 'warning' | 'info';
|
||||
ruleId: string; // 格式: "linter名:规则ID"
|
||||
message: string;
|
||||
ruleId: string;
|
||||
source: string;
|
||||
fix?: Fix;
|
||||
aiExplanation?: string;
|
||||
range: vscode.Range;
|
||||
suggestion?: string;
|
||||
}
|
||||
|
||||
interface Analyzer {
|
||||
readonly name: string;
|
||||
readonly language: string[];
|
||||
analyze(document: vscode.TextDocument): Promise<AnalyzerResult[]>;
|
||||
fix?(result: AnalyzerResult): vscode.TextEdit[];
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
### 内置 Analyzer
|
||||
**错误状态说明**:
|
||||
|
||||
| Analyzer | 语言 | 调用方式 |
|
||||
|----------|------|---------|
|
||||
| EslintAnalyzer | JS/TS/JSX/TSX | eslint CLI --format json |
|
||||
| RuffAnalyzer | Python | ruff check --output-format json |
|
||||
| ClippyAnalyzer | Rust | cargo clippy --message-format json |
|
||||
| AiAnalyzer | 通用 | LLM API |
|
||||
| 状态 | 含义 | 用户感知 |
|
||||
|------|------|---------|
|
||||
| `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. Linter 管理器
|
||||
## 4. Orchestrator(编排器)
|
||||
|
||||
### 自动检测
|
||||
### 注册方式
|
||||
|
||||
插件扫描工作区根目录,根据配置文件自动识别启用的 linter:
|
||||
|
||||
| Linter | 检测标志 |
|
||||
|--------|---------|
|
||||
| ESLint | `.eslintrc*` 或 `package.json` 中的 `eslintConfig` |
|
||||
| Ruff | `ruff.toml` 或 `pyproject.toml` 中的 `[tool.ruff]` |
|
||||
| Clippy | `Cargo.toml` 含 clippy 依赖 |
|
||||
|
||||
未检测到配置时可降级为默认配置运行。
|
||||
采用**硬编码注册**,适配器列表在 `orchestrator.ts` 中静态定义(见 §3.6)。
|
||||
|
||||
### 执行策略
|
||||
|
||||
@@ -82,36 +186,215 @@ interface Analyzer {
|
||||
- **手动触发**:命令面板、右键菜单、文件树右键
|
||||
- **进度反馈**:运行时显示 `withProgress`
|
||||
|
||||
### 调度逻辑
|
||||
|
||||
```
|
||||
1. 获取当前文档语言 ID
|
||||
2. 查询 linters.<language> 配置 → 确定使用的适配器
|
||||
3. 调用适配器 check(document, workingDir)
|
||||
4. 收集结果,区分 status
|
||||
5. 返回聚合后的 diagnostics 列表
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. AI 审查引擎
|
||||
|
||||
`AiAnalyzer` 作为特殊 Analyzer 注册,通过 LLM API 审查代码。
|
||||
### 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
|
||||
interface AiReviewConfig {
|
||||
provider: 'openai' | 'custom';
|
||||
apiKey: string;
|
||||
// providers/base.ts
|
||||
export interface ChatOptions {
|
||||
model: string;
|
||||
maxTokens: number;
|
||||
customEndpoint?: string;
|
||||
temperature: number;
|
||||
timeoutMs: number;
|
||||
}
|
||||
|
||||
export abstract class AIProvider {
|
||||
abstract id: string;
|
||||
abstract name: string;
|
||||
|
||||
constructor(
|
||||
protected apiKey: string,
|
||||
protected endpoint: string
|
||||
) {}
|
||||
|
||||
abstract chat(
|
||||
systemPrompt: string,
|
||||
userPrompt: string,
|
||||
options: ChatOptions
|
||||
): Promise<string>;
|
||||
}
|
||||
```
|
||||
|
||||
### 工作流
|
||||
**Provider 工厂**:
|
||||
|
||||
1. 用户触发 AI 审查(整个文件或选中代码)
|
||||
2. 收集代码 + 上下文 → 构建 Prompt
|
||||
3. 调用 LLM API → 解析 JSON 响应
|
||||
4. 结果转为 `AnalyzerResult[]` → 注入 Diagnostic
|
||||
```typescript
|
||||
// factory.ts
|
||||
const registry: Record<string, new (apiKey: string, endpoint: string) => AIProvider> = {
|
||||
deepseek: DeepSeekProvider,
|
||||
openai: OpenAIProvider,
|
||||
};
|
||||
|
||||
### Prompt 策略
|
||||
export function createProvider(providerId: string, apiKey: string, endpoint: string): AIProvider {
|
||||
const Cls = registry[providerId];
|
||||
if (!Cls) throw new Error(`Unknown provider: ${providerId}`);
|
||||
return new Cls(apiKey, endpoint);
|
||||
}
|
||||
```
|
||||
|
||||
- System Prompt:审查专家角色 + JSON 格式约束
|
||||
- 支持传入自定义规则列表
|
||||
- 响应强制 JSON 格式
|
||||
### 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. 失败信息在面板顶部以黄色/红色提示条展示
|
||||
|
||||
---
|
||||
|
||||
@@ -119,97 +402,422 @@ interface AiReviewConfig {
|
||||
|
||||
### Commands
|
||||
|
||||
| 命令 | 说明 |
|
||||
|------|------|
|
||||
| `codeReviewer.analyzeFile` | 审查当前文件 |
|
||||
| `codeReviewer.analyzeWorkspace` | 审查整个工作区 |
|
||||
| `codeReviewer.aiReview` | AI 深度审查 |
|
||||
| `codeReviewer.aiExplain` | AI 解释选中问题 |
|
||||
| `codeReviewer.fixAll` | 批量修复 |
|
||||
| `codeReviewer.exportReport` | 导出报告 |
|
||||
| 命令 | 功能 | 快捷键 |
|
||||
|------|------|--------|
|
||||
| `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
|
||||
|
||||
- `codeReviewProblems` — 审查问题树视图(按文件分组)
|
||||
- `codeReviewSummary` — 审查概况树视图(统计信息 + 导出/批量修复入口)
|
||||
- `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": "CodeGuard 代码审查",
|
||||
"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.endpoint` | string | `https://api.deepseek.com/v1` | API 端点 |
|
||||
| `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 设计决策
|
||||
|
||||
| 决策项 | 结论 | 说明 |
|
||||
|--------|------|------|
|
||||
| `codeReviewer.linters` | object | 启用的 linter 配置 |
|
||||
| `codeReviewer.ai.enabled` | boolean | 启用 AI 审查 |
|
||||
| `codeReviewer.ai.provider` | enum | AI 提供商 |
|
||||
| `codeReviewer.ai.apiKey` | string | API 密钥 |
|
||||
| `codeReviewer.ai.model` | string | 模型名 |
|
||||
| 上下文行数 | **动态调整** | 根据问题类型决定上下文范围,不固定 5 行 |
|
||||
| 修复匹配策略 | **行号 + 代码匹配** | 先按行号匹配原文,失败则在文件中搜索 |
|
||||
| 批量修复 | **预览后应用** | 先展示所有修改,用户确认后才执行 |
|
||||
| 修复后验证 | **不做验证** | 不重新跑 linter 或编译,信任 AI 输出 |
|
||||
| 撤销机制 | **快照 + 撤销按钮** | 修复前保存快照,面板底部提供"撤销上次修复" |
|
||||
| 修复范围限定 | **不限定** | 用户自行判断哪些问题可修复 |
|
||||
| Code Action | **复用 AI Provider** | 单条修复与批量修复共用 §5.2 的 Provider 层 |
|
||||
|
||||
---
|
||||
### 8.2 修复流程
|
||||
|
||||
## 7. 审查报告面板
|
||||
```
|
||||
用户触发修复(单条 / 批量)
|
||||
↓
|
||||
prepareContext() 获取代码上下文(动态行数,见 §8.3)
|
||||
↓
|
||||
generateFix() 调用 AI 生成修复方案(复用 §5.2 Provider)
|
||||
↓
|
||||
matchAndValidate() 匹配验证(行号 → 代码搜索,见 §8.4)
|
||||
↓
|
||||
applyFix() 应用修复到编辑器(单条 / 批量倒序)
|
||||
↓
|
||||
保存快照,更新撤销按钮状态
|
||||
```
|
||||
|
||||
### 问题列表视图(codeReviewProblems)
|
||||
### 8.3 动态上下文策略
|
||||
|
||||
- 按文件分组展示所有问题
|
||||
- 每个问题显示:严重级别图标、规则 ID、消息、行号、来源标记
|
||||
- 点击跳转到对应位置
|
||||
- 右键菜单:AI 解释 / 快速修复 / 忽略规则
|
||||
根据问题分类决定上下文的行数范围:
|
||||
|
||||
### 概况视图(codeReviewSummary)
|
||||
| 问题类型 | 上下文范围 | 说明 |
|
||||
|---------|-----------|------|
|
||||
| 命名问题(`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;
|
||||
|
||||
## 8. Code Action 与批量修复
|
||||
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));
|
||||
}
|
||||
|
||||
### Code Action 提供者
|
||||
const codeContext = extractLines(document, startLine, endLine);
|
||||
return { ...diagnostic, codeContext };
|
||||
}
|
||||
```
|
||||
|
||||
`CodeReviewCodeActionProvider` 为 diagnostic 提供:
|
||||
### 8.4 修复匹配策略
|
||||
|
||||
1. **快速修复** — 若 analyzer 提供了 `fix()` 方法
|
||||
2. **AI 解释** — 调用 AI 翻译/解释问题
|
||||
3. **添加忽略注释** — 自动插入 linter 忽略标记
|
||||
两阶段匹配,确保修复应用到正确位置:
|
||||
|
||||
### 批量修复
|
||||
```
|
||||
阶段 1 — 行号匹配
|
||||
提取问题行的原文 → 与 AI 返回的 originalText 首行对比
|
||||
✓ 匹配 → 验证完整原文是否一致
|
||||
✗ 不匹配 → 进入阶段 2
|
||||
|
||||
- 遍历所有含 `fix` 的问题,按文件分组
|
||||
- 使用 `WorkspaceEdit` 批量应用 TextEdit
|
||||
- 显示修复总结
|
||||
阶段 2 — 全文搜索
|
||||
在文件中搜索 originalText
|
||||
✓ 找到 → 通过 positionAt() 计算实际 range
|
||||
✗ 未找到 → 标记为匹配失败
|
||||
```
|
||||
|
||||
### 忽略机制
|
||||
```typescript
|
||||
interface CodeFix {
|
||||
startLine: number;
|
||||
endLine: number;
|
||||
originalText: string; // AI 认为要替换的原文
|
||||
newText: string; // 修复后的新代码
|
||||
}
|
||||
|
||||
- 右键"忽略此规则" → 写入 `.codereviewerignore`
|
||||
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. 文件结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── extension.ts
|
||||
├── activation/
|
||||
│ ├── registerCommands.ts
|
||||
│ ├── registerViews.ts
|
||||
│ └── registerCodeActions.ts
|
||||
├── analyzers/
|
||||
│ ├── analyzer.ts
|
||||
│ ├── eslintAnalyzer.ts
|
||||
│ ├── ruffAnalyzer.ts
|
||||
│ ├── clippyAnalyzer.ts
|
||||
│ └── aiAnalyzer.ts
|
||||
├── manager/
|
||||
│ └── linterManager.ts
|
||||
├── views/
|
||||
│ ├── problemTreeProvider.ts
|
||||
│ └── summaryTreeProvider.ts
|
||||
├── services/
|
||||
│ ├── aiService.ts
|
||||
│ └── configService.ts
|
||||
├── utils/
|
||||
│ ├── diagnosticHelper.ts
|
||||
│ └── linterDetector.ts
|
||||
└── types.ts
|
||||
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
|
||||
```
|
||||
|
||||
---
|
||||
@@ -218,8 +826,6 @@ src/
|
||||
|
||||
- 与 GitHub/GitLab PR Review API 集成
|
||||
- 多人协作审查工作流
|
||||
- WebView 可视化报告页面
|
||||
- 自定义规则 DSL
|
||||
|
||||
---
|
||||
|
||||
@@ -227,6 +833,516 @@ src/
|
||||
|
||||
- VSCode Extension API (^1.120.0)
|
||||
- TypeScript (ES2022, Node16 module)
|
||||
- ESLint + typescript-eslint
|
||||
- Mocha + @vscode/test-electron
|
||||
- LLM API(openai 兼容接口)
|
||||
- esbuild(打包构建)
|
||||
- ESLint + typescript-eslint(自检)
|
||||
- Mocha + @vscode/test-electron(测试)
|
||||
- LLM API(openai 兼容接口)
|
||||
- PMD 7.26.0(Java/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/endpoint/temperature/timeout/outputLanguage)
|
||||
├── linter.ts # Linter 配置(linters.<language>、PMD 路径)
|
||||
├── fixer.ts # 修复器配置(contextLines)
|
||||
└── secret.ts # API Key SecretStorage(get/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.endpoint` |
|
||||
| 输出语言 | 下拉框 | `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[];
|
||||
```
|
||||
Reference in New Issue
Block a user