- 方法级审查:CodeLens 触发 + 单次 AI 调用(规则匹配 + 6 维度深度审查),新增 method-extractor / status-cache / codeLensProvider - 模板导入:severity 保留原始值 + 占位 id、去重对照统一 known-rules、重复提示条双语翻译、箭头展开/折叠 UI、520 条静态规则补 zh/ja 翻译 - SQL:sql-lint 重命名 sqlfluff + sqlfluff.dialect 方言可配置 + 默认方言调整 - ESLint:v9 flat config 接线修复(overrideConfigFile)+ legacy 迁移提示 - AI:空响应 EmptyContentError + 重试一次 + max_tokens 截断专用报错 - JSP:整文件检查走 PMD JSP 规则集 + scriptlet 包装解析 + 行号映射 - 诊断按 severity + 行号排序
1001 lines
35 KiB
Markdown
1001 lines
35 KiB
Markdown
# 方法级代码审查功能实施计划书
|
||
|
||
> 面向 AI 编码 agent 的技术实施文档。本文档包含完整的架构设计、文件清单、代码模板和验收标准,可直接据此编码。
|
||
|
||
## 项目上下文
|
||
|
||
- 仓库:`sdjndaq/2026-ai-b3`(Gitee 私有仓库)
|
||
- 分支:`vscode-code-reviewer`
|
||
- 项目名:**代码审查官 · Code Purifier**(`vscode-code-reviewer`)
|
||
- 版本:1.2.0
|
||
- 类型:VS Code 扩展插件(TypeScript)
|
||
- 技术栈:TypeScript + VS Code Extension API + DeepSeek AI
|
||
|
||
## 功能目标
|
||
|
||
为现有代码审查插件新增**方法级触发**能力。用户在每个函数声明行上方看到 CodeLens 按钮,点击后对该方法执行**自定义规则审查 + AI 深度审查**。方法级审查不执行静态分析(IDE 已实时提供 linter 诊断),但加载自定义规则与 AI 协同工作:AI 先按自定义规则做匹配,再以 6 维度增强策略做深度审查,单次调用同时产出两类结果。
|
||
|
||
### 核心设计决策
|
||
|
||
| 决策 | 选择 | 理由 |
|
||
|------|------|------|
|
||
| 审查流程 | 自定义规则 + AI(不走静态分析) | IDE 已实时提供 linter 诊断,静态分析冗余;自定义规则保留是因为团队规则仍需在方法级生效 |
|
||
| AI 调用模式 | 单次调用合并规则匹配 + 深度审查 | 方法代码短,token 预算充足,合并为单次调用减少延迟 |
|
||
| 触发方式 | CodeLens 行内按钮 | 最直观的交互,用户无需记忆命令或快捷键 |
|
||
| 方法提取 | VS Code Symbol API | 原生支持,覆盖 TS/JS/Java/Python 等主流语言 |
|
||
| 调用链 | 同文件粗匹配 | 首版限制在同文件内,控制实现复杂度 |
|
||
| AI 审查范围 | 6 维度增强 | AI 同时承担规则匹配和深度审查,维度覆盖比全文件更广 |
|
||
|
||
## 现有架构摘要
|
||
|
||
编码前必须理解以下现有代码结构,所有新代码需与这些模式保持一致。
|
||
|
||
### 入口与注册
|
||
|
||
`src/extension.ts` 的 `activate(context)` 函数完成三件事:
|
||
|
||
1. 创建 `Orchestrator` 实例
|
||
2. 注册 `SetupViewProvider`(Webview View)
|
||
3. 调用 `registerCommands(context, orchestrator)` 注册所有命令
|
||
4. 监听 `onDidSaveTextDocument` 执行防抖静态分析
|
||
5. 监听 `onDidChangeConfiguration` 切换 i18n 语言
|
||
|
||
### 命令注册
|
||
|
||
`src/activation/commands.ts` 的 `registerCommands(context, orchestrator)` 注册命令。现有命令通过 `vscode.window.withProgress` 包裹执行,使用 `t()` 函数做 i18n。核心命令 `codeReviewer.review` 的执行流程:
|
||
|
||
```
|
||
获取 document + workingDir
|
||
→ withProgress:
|
||
→ orchestrator.runStaticAnalysis(document, workingDir)
|
||
→ loadActiveRules(workspaceRoot) + filterAndSummarize
|
||
→ runAIReview(context, code, staticDiagnostics, relevantRules)
|
||
→ mergeResults(...)
|
||
→ ReviewPanel.createOrShow + panel.update(currentReport)
|
||
```
|
||
|
||
### AI 引擎
|
||
|
||
`src/ai/engine.ts` 导出 `runAIReview(context, code, staticDiagnostics, customRules)` 函数。内部流程:
|
||
|
||
1. `getApiKey(context)` 获取 API Key
|
||
2. `createProvider(providerId, apiKey, baseUrl, extensionUri)` 创建 AI Provider
|
||
3. 并行发起两个请求:
|
||
- `requestA`:自定义规则匹配(`buildCustomRuleSystemPrompt` + `buildUserPromptCustomRules`)
|
||
- `requestB`:深度审查(`buildDeepReviewSystemPrompt` + `buildUserPromptDeepReview`)
|
||
4. `parseJsonResponse` 解析 JSON
|
||
5. 返回 `AIEngineResult`
|
||
|
||
现有 `buildDeepReviewSystemPrompt()` 的定位是"补充静态分析的盲区",任务包含翻译静态诊断和发现额外问题。方法级审查不复用这个 prompt。
|
||
|
||
### 类型定义
|
||
|
||
`src/types.ts` 定义核心类型:
|
||
|
||
```typescript
|
||
interface CustomRule {
|
||
id: string;
|
||
severity: Severity;
|
||
description: string;
|
||
message: string;
|
||
languages?: string[];
|
||
excludeLanguages?: string[];
|
||
}
|
||
|
||
interface LinterDiagnostic {
|
||
severity: Severity;
|
||
ruleId: string;
|
||
message: string;
|
||
range: vscode.Range;
|
||
suggestion?: string;
|
||
}
|
||
|
||
interface LinterAdapter {
|
||
id: string;
|
||
supportedLanguages: string[];
|
||
check(document: vscode.TextDocument, workingDir: string): Promise<AdapterResult>;
|
||
isAvailable(): boolean;
|
||
}
|
||
```
|
||
|
||
`src/ai/schema.ts` 定义 AI 输出类型:
|
||
|
||
```typescript
|
||
interface AIFinding {
|
||
ruleId: string;
|
||
severity: 'error' | 'warning' | 'info';
|
||
category: 'bug' | 'performance' | 'security' | 'style' | 'design';
|
||
title: string;
|
||
description: string;
|
||
suggestion: string;
|
||
codeDiff?: string;
|
||
line: number;
|
||
}
|
||
|
||
interface AIEngineResult {
|
||
customRuleResults: CustomRuleResult[];
|
||
translatedDiagnostics: TranslatedDiagnostic[];
|
||
findings: AIFinding[];
|
||
degraded: boolean;
|
||
error?: string;
|
||
}
|
||
```
|
||
|
||
### 结果合并与展示
|
||
|
||
`src/merger/merger.ts` 的 `mergeResults(...)` 将静态诊断、AI 发现等聚合成 `MergedReport`。`src/panel/webview.ts` 的 `ReviewPanel` 类负责 Webview 面板的创建和更新。
|
||
|
||
### i18n
|
||
|
||
`src/i18n/messages.ts` 导出 `t(key, params?)` 和 `setLanguage(lang)` 函数。所有用户可见文案必须通过 `t()` 获取。
|
||
|
||
## 改动总览
|
||
|
||
### 新增文件(3 个)
|
||
|
||
| 文件路径 | 职责 |
|
||
|----------|------|
|
||
| `src/scope/method-extractor.ts` | 从文档中提取方法符号、代码、签名、调用链 |
|
||
| `src/views/codeLensProvider.ts` | CodeLens Provider,在函数声明上方渲染审查按钮 |
|
||
| `src/scope/status-cache.ts` | 方法审查状态缓存,驱动 CodeLens 按钮文案刷新 |
|
||
|
||
### 修改文件(4 个 + package.json)
|
||
|
||
| 文件路径 | 改动内容 |
|
||
|----------|----------|
|
||
| `src/activation/commands.ts` | 新增 `codeReviewer.reviewMethod` 命令 |
|
||
| `src/ai/engine.ts` | 新增 `runMethodReview` 函数 + 方法级 prompt builder |
|
||
| `src/ai/schema.ts` | 新增 `MethodFinding` 类型 + 扩展 category |
|
||
| `src/extension.ts` | 注册 CodeLensProvider |
|
||
| `package.json` | 新增 command、codelens 配置项 |
|
||
|
||
### 不修改的文件
|
||
|
||
`src/orchestrator/orchestrator.ts`、`src/merger/merger.ts`、`src/panel/webview.ts`、`src/ai/providers/` 目录、`src/i18n/` 目录均不需要修改。方法级审查的执行路径不经过 Orchestrator,直接从命令层调用 engine.ts。自定义规则的加载复用现有 `src/rules/rule-loader.ts` 的 `loadActiveRules` 函数,过滤复用 `src/rules/rule-filter.ts` 的 `filterForDocument` 函数,均无需修改。
|
||
|
||
---
|
||
|
||
## 新增文件详细设计
|
||
|
||
### 1. `src/scope/method-extractor.ts`
|
||
|
||
#### 职责
|
||
|
||
从 VS Code 文档中提取方法级别的信息,包括方法代码、签名和同文件内的调用链。
|
||
|
||
#### 导出类型
|
||
|
||
```typescript
|
||
import * as vscode from 'vscode';
|
||
|
||
export interface MethodScope {
|
||
/** 方法名称 */
|
||
name: string;
|
||
/** 方法在文档中的完整范围 */
|
||
range: vscode.Range;
|
||
/** 方法代码文本(带行号) */
|
||
code: string;
|
||
/** 方法签名(一行摘要,如 "processOrder(order: Order): Promise<Result>") */
|
||
signature: string;
|
||
/** 调用者方法名列表(同文件内引用了本方法的其他方法) */
|
||
callers: string[];
|
||
/** 被调用方法名列表(本方法体内调用的其他方法) */
|
||
callees: string[];
|
||
/** 方法在业务流中的角色描述(由符号层级推断) */
|
||
role: string;
|
||
}
|
||
|
||
export interface MethodSymbol {
|
||
/** 方法名称 */
|
||
name: string;
|
||
/** 方法范围 */
|
||
range: vscode.Range;
|
||
/** 方法所在类/模块名(如果有) */
|
||
containerName?: string;
|
||
}
|
||
```
|
||
|
||
#### 导出函数
|
||
|
||
```typescript
|
||
/**
|
||
* 获取文档中所有方法/函数符号
|
||
* 使用 VS Code Symbol API,无 Provider 时回退到正则匹配
|
||
*/
|
||
export async function getMethodSymbols(
|
||
document: vscode.TextDocument
|
||
): Promise<MethodSymbol[]>;
|
||
|
||
/**
|
||
* 提取指定位置所在方法的完整 MethodScope
|
||
* @param document 文档
|
||
* @param position 光标位置或 Symbol 范围
|
||
*/
|
||
export async function extractMethodScope(
|
||
document: vscode.TextDocument,
|
||
range: vscode.Range
|
||
): Promise<MethodScope | null>;
|
||
```
|
||
|
||
#### 实现要点
|
||
|
||
**Symbol API 获取符号树:**
|
||
|
||
```typescript
|
||
const symbols = await vscode.commands.executeCommand<vscode.DocumentSymbol[]>(
|
||
'vscode.executeDocumentSymbolProvider',
|
||
document.uri
|
||
);
|
||
```
|
||
|
||
过滤 `SymbolKind.Function`、`SymbolKind.Method`、`SymbolKind.Constructor` 类型的节点。递归遍历子符号(类内的方法)。
|
||
|
||
**正则回退(无 Symbol Provider 时):**
|
||
|
||
针对 JavaScript/TypeScript 使用正则 `/(?:async\s+)?function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\(/g` 匹配函数声明。针对 Java 使用 `/((?:public|private|protected|static)\s+)*\w+(?:<[^>]+>)?\s+(\w+)\s*\(/g`。
|
||
|
||
**调用链粗匹配:**
|
||
|
||
获取同文件内所有方法符号后,对每个方法:
|
||
- **被调用者**:在本方法代码文本中搜索其他方法名是否出现(正则 `\b方法名\s*\(`)
|
||
- **调用者**:遍历其他所有方法的代码,检查本方法名是否出现
|
||
|
||
**签名提取:**
|
||
|
||
取方法代码的第一行(到 `{` 或 `)` 结束),去除注释和多余空白。
|
||
|
||
**role 字段推断:**
|
||
|
||
根据 `containerName` 和方法名粗略推断,例如:
|
||
- `containerName` 包含 "Controller" → "HTTP 请求处理入口"
|
||
- `containerName` 包含 "Service" → "业务逻辑处理"
|
||
- 方法名以 "get/set/is" 开头 → "属性访问器"
|
||
- 默认 → "通用方法"
|
||
|
||
### 2. `src/views/codeLensProvider.ts`
|
||
|
||
#### 职责
|
||
|
||
实现 `vscode.CodeLensProvider`,在每个函数声明行上方渲染审查按钮。
|
||
|
||
#### 类定义
|
||
|
||
```typescript
|
||
import * as vscode from 'vscode';
|
||
import { getMethodSymbols } from '../scope/method-extractor';
|
||
import { ReviewStatusCache } from '../scope/status-cache';
|
||
import { t } from '../i18n/messages';
|
||
|
||
export class MethodCodeLensProvider implements vscode.CodeLensProvider {
|
||
private _onDidChangeCodeLenses: vscode.EventEmitter<void> =
|
||
new vscode.EventEmitter<void>();
|
||
readonly onDidChangeCodeLenses: vscode.Event<void> =
|
||
this._onDidChangeCodeLenses.event;
|
||
|
||
constructor(private statusCache: ReviewStatusCache) {}
|
||
|
||
/** 触发 CodeLens 刷新 */
|
||
refresh(): void {
|
||
this._onDidChangeCodeLenses.fire();
|
||
}
|
||
|
||
async provideCodeLenses(
|
||
document: vscode.TextDocument,
|
||
token: vscode.CancellationToken
|
||
): Promise<vscode.CodeLens[]> {
|
||
// 1. 检查配置是否启用
|
||
const config = vscode.workspace.getConfiguration('vscode-code-reviewer');
|
||
const enabled = config.get<boolean>('codelens.enabled', true);
|
||
if (!enabled) return [];
|
||
|
||
// 2. 检查语言是否在允许列表内
|
||
const languages = config.get<string[]>('codelens.languages', [
|
||
'typescript', 'javascript', 'java', 'python'
|
||
]);
|
||
if (!languages.includes(document.languageId)) return [];
|
||
|
||
// 3. 获取方法符号
|
||
const symbols = await getMethodSymbols(document);
|
||
if (symbols.length === 0) return [];
|
||
|
||
// 4. 方法数量上限保护
|
||
if (symbols.length > 50) return [];
|
||
|
||
// 5. 为每个方法生成 CodeLens
|
||
const lenses: vscode.CodeLens[] = [];
|
||
for (const symbol of symbols) {
|
||
const status = this.statusCache.get(document.uri, symbol.name);
|
||
const title = this.buildLensTitle(status);
|
||
lenses.push(new vscode.CodeLens(symbol.range.start, {
|
||
command: 'codeReviewer.reviewMethod',
|
||
title: title,
|
||
arguments: [symbol.range],
|
||
}));
|
||
}
|
||
return lenses;
|
||
}
|
||
|
||
private buildLensTitle(status: ReviewStatus | null): string {
|
||
if (!status) {
|
||
return t('codelens.reviewMethod');
|
||
}
|
||
if (status.issueCount === 0) {
|
||
return t('codelens.reviewedClean');
|
||
}
|
||
return t('codelens.reviewedWithIssues', { 0: String(status.issueCount) });
|
||
}
|
||
}
|
||
```
|
||
|
||
#### i18n key 约定
|
||
|
||
需要在 `src/i18n/messages.ts` 中新增以下 key(中英文):
|
||
|
||
| key | 中文 | English |
|
||
|-----|------|---------|
|
||
| `codelens.reviewMethod` | `🔍 Code Purifier: 审查此方法` | `🔍 Code Purifier: Review This Method` |
|
||
| `codelens.reviewedClean` | `✓ Code Purifier: 已审查(无问题)` | `✓ Code Purifier: Reviewed (No Issues)` |
|
||
| `codelens.reviewedWithIssues` | `✓ Code Purifier: 已审查({0} 个问题)` | `✓ Code Purifier: Reviewed ({0} Issues)` |
|
||
| `methodReview.running` | `Code Purifier 正在审查方法:{0}` | `Code Purifier: Reviewing method: {0}` |
|
||
| `methodReview.noMethod` | `当前位置未检测到方法` | `No method detected at current position` |
|
||
| `methodReview.complete` | `方法审查完成,发现 {0} 个问题` | `Method review complete, {0} issues found` |
|
||
|
||
### 3. `src/scope/status-cache.ts`
|
||
|
||
#### 职责
|
||
|
||
内存缓存,记录每个方法最近一次的审查结果,驱动 CodeLens 按钮文案刷新。
|
||
|
||
#### 类定义
|
||
|
||
```typescript
|
||
import * as vscode from 'vscode';
|
||
|
||
export interface ReviewStatus {
|
||
/** 发现的问题数量 */
|
||
issueCount: number;
|
||
/** 审查时间戳 */
|
||
timestamp: number;
|
||
}
|
||
|
||
export class ReviewStatusCache {
|
||
/** key 格式:documentUri.toString() + '::' + methodName */
|
||
private cache = new Map<string, ReviewStatus>();
|
||
|
||
get(uri: vscode.Uri, methodName: string): ReviewStatus | null {
|
||
const key = this.buildKey(uri, methodName);
|
||
return this.cache.get(key) ?? null;
|
||
}
|
||
|
||
set(uri: vscode.Uri, methodName: string, issueCount: number): void {
|
||
const key = this.buildKey(uri, methodName);
|
||
this.cache.set(key, {
|
||
issueCount,
|
||
timestamp: Date.now(),
|
||
});
|
||
}
|
||
|
||
/** 清除指定文档的所有缓存(文档关闭时调用) */
|
||
clearDocument(uri: vscode.Uri): void {
|
||
const prefix = uri.toString() + '::';
|
||
for (const key of this.cache.keys()) {
|
||
if (key.startsWith(prefix)) {
|
||
this.cache.delete(key);
|
||
}
|
||
}
|
||
}
|
||
|
||
private buildKey(uri: vscode.Uri, methodName: string): string {
|
||
return uri.toString() + '::' + methodName;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 修改文件详细设计
|
||
|
||
### 4. `src/ai/schema.ts`
|
||
|
||
在现有类型基础上新增方法级审查的类型定义,不修改现有类型。
|
||
|
||
#### 新增内容
|
||
|
||
```typescript
|
||
/** 方法级审查的 category 类型(6 维度) */
|
||
export type MethodFindingCategory =
|
||
| 'correctness' // 正确性:分支覆盖、边界条件、异常路径
|
||
| 'security' // 安全性:输入校验、注入风险、权限检查
|
||
| 'design' // 设计:职责单一、参数合理性、调用链适配
|
||
| 'convention' // 规范:命名、复杂度、魔法数字、注释
|
||
| 'performance' // 性能:时间/空间复杂度、资源泄漏
|
||
| 'testability'; // 可测试性:副作用隔离、依赖可 Mock 性
|
||
|
||
/** 方法级审查的 finding,扩展 AIFinding */
|
||
export interface MethodFinding {
|
||
ruleId: string;
|
||
severity: 'error' | 'warning' | 'info';
|
||
category: MethodFindingCategory;
|
||
title: string;
|
||
description: string;
|
||
suggestion: string;
|
||
codeDiff?: string;
|
||
line: number;
|
||
/** 触发路径描述,如 "if(order == null) → NPE on .getId()" */
|
||
path?: string;
|
||
}
|
||
|
||
/** 方法级审查的 AI 返回结构 */
|
||
export interface MethodReviewResult {
|
||
/** 自定义规则匹配结果(AI 按规则做语义判断) */
|
||
customRuleResults: CustomRuleResult[];
|
||
/** AI 深度审查发现 */
|
||
findings: MethodFinding[];
|
||
degraded: boolean;
|
||
error?: string;
|
||
}
|
||
```
|
||
|
||
### 5. `src/ai/engine.ts`
|
||
|
||
新增方法级审查的入口函数和专用 prompt builder。不修改现有 `runAIReview` 及其相关函数。
|
||
|
||
#### 新增函数
|
||
|
||
```typescript
|
||
/**
|
||
* 方法级 AI 审查
|
||
* 不依赖静态诊断,但加载自定义规则与 AI 协同工作
|
||
* 单次 AI 调用同时完成规则匹配和 6 维度深度审查
|
||
*/
|
||
export async function runMethodReview(
|
||
context: vscode.ExtensionContext,
|
||
scope: MethodScope,
|
||
customRules: CustomRule[],
|
||
): Promise<MethodReviewResult>;
|
||
```
|
||
|
||
#### 内部实现流程
|
||
|
||
```typescript
|
||
export async function runMethodReview(
|
||
context: vscode.ExtensionContext,
|
||
scope: MethodScope,
|
||
customRules: CustomRule[],
|
||
): Promise<MethodReviewResult> {
|
||
// 1. 获取 API Key
|
||
const apiKey = await getApiKey(context);
|
||
if (!apiKey) {
|
||
return {
|
||
customRuleResults: [],
|
||
findings: [],
|
||
degraded: true,
|
||
error: t('adapter.noApiKey'),
|
||
};
|
||
}
|
||
|
||
// 2. 创建 Provider(复用现有工厂)
|
||
const providerId = getAIProvider();
|
||
const baseUrl = getAIBaseUrl();
|
||
let provider: AIProvider;
|
||
try {
|
||
provider = createProvider(providerId, apiKey, baseUrl, context.extensionUri);
|
||
} catch (err) {
|
||
return {
|
||
customRuleResults: [],
|
||
findings: [],
|
||
degraded: true,
|
||
error: t('adapter.createProviderFail', { 0: err instanceof Error ? err.message : String(err) }),
|
||
};
|
||
}
|
||
|
||
// 3. 构建请求
|
||
const options = {
|
||
model: getAIModel(),
|
||
temperature: getAITemperature(),
|
||
maxTokens: getAIMaxTokens(),
|
||
timeoutMs: getAITimeout() * 1000,
|
||
};
|
||
|
||
const numberedCode = addLineNumbers(scope.code);
|
||
|
||
// 4. 单次 AI 调用(规则匹配 + 深度审查合并为一次)
|
||
// 有规则时 prompt 包含规则匹配任务,无规则时只做深度审查
|
||
const hasRules = customRules.length > 0;
|
||
const result = await provider.chat(
|
||
buildMethodReviewSystemPrompt(hasRules),
|
||
buildMethodUserPrompt(scope, numberedCode, customRules),
|
||
options,
|
||
);
|
||
|
||
// 5. 解析结果
|
||
const errors: string[] = [];
|
||
let customRuleResults: CustomRuleResult[] = [];
|
||
let findings: MethodFinding[] = [];
|
||
|
||
if (result.status === 'fulfilled') {
|
||
try {
|
||
const parsed = parseJsonResponse(result.value) as {
|
||
customRuleResults?: CustomRuleResult[];
|
||
findings?: MethodFinding[];
|
||
};
|
||
customRuleResults = (parsed.customRuleResults ?? []).map(r => ({
|
||
...r,
|
||
ruleId: r.ruleId.startsWith('custom:') ? r.ruleId : `custom:${r.ruleId}`,
|
||
}));
|
||
findings = (parsed.findings ?? []).map(f => ({
|
||
...f,
|
||
ruleId: f.ruleId.startsWith('method:') ? f.ruleId : `method:${f.ruleId}`,
|
||
}));
|
||
} catch (e) {
|
||
errors.push(t('adapter.aiReviewParseFail', { 0: e instanceof Error ? e.message : String(e) }));
|
||
}
|
||
} else {
|
||
errors.push(t('adapter.aiReviewRequestFail', { 0: result.reason }));
|
||
}
|
||
|
||
return {
|
||
customRuleResults,
|
||
findings,
|
||
degraded: errors.length > 0,
|
||
error: errors.join('; ') || undefined,
|
||
};
|
||
}
|
||
```
|
||
|
||
#### 新增 prompt builder
|
||
|
||
```typescript
|
||
function buildMethodReviewSystemPrompt(hasRules: boolean): string {
|
||
const lang = getLanguage();
|
||
if (lang === 'en') {
|
||
const ruleSection = hasRules
|
||
? `## Task 1: Custom Rule Matching
|
||
Evaluate whether the method violates any of the provided custom rules.
|
||
Understand semantics, not text matching.
|
||
Report violations in "customRuleResults".\n\n`
|
||
: '';
|
||
const ruleOutput = hasRules
|
||
? ` "customRuleResults": [
|
||
{
|
||
"ruleId": "original rule id",
|
||
"line": line_number,
|
||
"severity": "error|warning|info",
|
||
"message": "violation description"
|
||
}
|
||
],\n`
|
||
: '';
|
||
return `You are a senior code review expert reviewing a single method.
|
||
There is no static analysis before you — you handle rule matching AND deep review.
|
||
|
||
${ruleSection}## Review Strategy: Path Enumeration
|
||
- Walk through every if/else/switch branch, note coverage and gaps
|
||
- Enumerate boundary values for every parameter (null, empty collection, extreme values, wrong types)
|
||
- Check every throw/catch path for proper fallback strategy
|
||
- Trace the method's role in its call chain
|
||
|
||
## Required Dimensions (do not skip any)
|
||
A. Correctness: branch coverage, boundary conditions, exception path completeness
|
||
B. Security: input validation, injection risk, permission check, sensitive data leakage
|
||
C. Design: single responsibility, parameter design, return value contract, call chain adaptation
|
||
D. Convention: naming, cyclomatic complexity, magic numbers, missing comments
|
||
E. Performance: time/space complexity, resource leaks, unnecessary computation
|
||
F. Testability: side effect isolation, dependency mockability, deterministic output
|
||
|
||
## Call Chain Analysis
|
||
- Check whether callers' arguments match this method's expectations
|
||
- Check whether this method's return value is correctly handled by callers
|
||
- Check whether exceptions are caught or declared by callers
|
||
|
||
Output JSON only. Double quotes in strings must be escaped with \\".
|
||
Format:
|
||
{
|
||
${ruleOutput} "findings": [
|
||
{
|
||
"ruleId": "method-boundary-null",
|
||
"severity": "error|warning|info",
|
||
"category": "correctness|security|design|convention|performance|testability",
|
||
"title": "issue title",
|
||
"description": "detailed description",
|
||
"suggestion": "fix suggestion",
|
||
"codeDiff": "optional fix diff",
|
||
"line": line_number,
|
||
"path": "trigger path description, e.g. if(order==null) -> NPE on .getId()"
|
||
}
|
||
]
|
||
}
|
||
If no issues found, return empty arrays.
|
||
|
||
Output language: en`;
|
||
}
|
||
// 中文 prompt(默认)
|
||
const ruleSection = hasRules
|
||
? `## 任务一:自定义规则匹配
|
||
评估方法是否违反了提供的自定义规则。
|
||
理解语义,而非文本匹配。
|
||
在 "customRuleResults" 中报告违规。\n\n`
|
||
: '';
|
||
const ruleOutput = hasRules
|
||
? ` "customRuleResults": [
|
||
{
|
||
"ruleId": "原始规则 ID",
|
||
"line": 行号,
|
||
"severity": "error|warning|info",
|
||
"message": "违规描述"
|
||
}
|
||
],\n`
|
||
: '';
|
||
return `你是资深代码审查专家,正在审查单个方法。
|
||
没有静态分析的前置过滤——你同时负责规则匹配和深度审查。
|
||
|
||
${ruleSection}## 审查策略:逐路径枚举
|
||
- 遍历每个 if/else/switch 分支,标注覆盖与遗漏
|
||
- 枚举每个入参的边界值(null、空集合、极值、错误类型)
|
||
- 检查每个 throw/catch 路径的降级策略
|
||
- 追踪方法在调用链中的角色
|
||
|
||
## 必须覆盖的维度(不可跳过)
|
||
A. 正确性:分支覆盖、边界条件、异常路径完整性
|
||
B. 安全性:输入校验、注入风险、权限检查、敏感信息泄露
|
||
C. 设计:职责单一性、参数设计合理性、返回值契约、调用链适配
|
||
D. 规范:命名、圈复杂度、魔法数字、注释缺失
|
||
E. 性能:时间/空间复杂度、资源泄漏、不必要的计算
|
||
F. 可测试性:副作用隔离、依赖可 Mock 性、确定性输出
|
||
|
||
## 调用链分析
|
||
- 检查调用者传入的参数是否符合本方法预期
|
||
- 检查本方法的返回值是否被调用者正确处理
|
||
- 检查异常是否被调用者捕获或声明
|
||
|
||
输出 JSON,字符串中的双引号必须用 \\" 转义。
|
||
格式:
|
||
{
|
||
${ruleOutput} "findings": [
|
||
{
|
||
"ruleId": "method-boundary-null",
|
||
"severity": "error|warning|info",
|
||
"category": "correctness|security|design|convention|performance|testability",
|
||
"title": "问题标题",
|
||
"description": "详细描述",
|
||
"suggestion": "修复建议",
|
||
"codeDiff": "可选的修复 diff",
|
||
"line": 行号,
|
||
"path": "触发路径描述,如 if(order==null) -> NPE on .getId()"
|
||
}
|
||
]
|
||
}
|
||
如果未发现问题,返回空数组。
|
||
|
||
输出语言:zh-CN`;
|
||
}
|
||
|
||
function buildMethodUserPrompt(
|
||
scope: MethodScope,
|
||
numberedCode: string,
|
||
customRules: CustomRule[],
|
||
): string {
|
||
const lang = getLanguage();
|
||
const codeLabel = lang === 'en' ? 'Method Code (with line numbers)' : '方法代码(带行号)';
|
||
const sigLabel = lang === 'en' ? 'Method Signature' : '方法签名';
|
||
const chainLabel = lang === 'en' ? 'Call Chain Context' : '调用链上下文';
|
||
const roleLabel = lang === 'en' ? 'Role in Business Flow' : '业务流中的角色';
|
||
const callersLabel = lang === 'en' ? 'Callers' : '调用者';
|
||
const calleesLabel = lang === 'en' ? 'Callees' : '被调用者';
|
||
const noneLabel = lang === 'en' ? '(none)' : '(无)';
|
||
|
||
// 自定义规则块:有规则时注入,无规则时省略
|
||
let ruleBlock = '';
|
||
if (customRules.length > 0) {
|
||
const ruleLabel = lang === 'en' ? 'Custom Rules to Match' : '需匹配的自定义规则';
|
||
const ruleLines = customRules
|
||
.map((r, i) => `${i + 1}. [${r.id}] (${r.severity}) ${r.description}\n ${r.message}`)
|
||
.join('\n');
|
||
ruleBlock = `\n## ${ruleLabel}\n${ruleLines}\n`;
|
||
}
|
||
|
||
return `## ${sigLabel}
|
||
${scope.signature}
|
||
|
||
## ${codeLabel}
|
||
${numberedCode}
|
||
${ruleBlock}
|
||
## ${chainLabel}
|
||
${roleLabel}: ${scope.role}
|
||
${callersLabel}: ${scope.callers.length > 0 ? scope.callers.join(', ') : noneLabel}
|
||
${calleesLabel}: ${scope.callees.length > 0 ? scope.callees.join(', ') : noneLabel}`;
|
||
}
|
||
```
|
||
|
||
#### 与现有 `runAIReview` 的关键差异
|
||
|
||
| 对比项 | 现有 `runAIReview` | 新增 `runMethodReview` |
|
||
|--------|--------------------|-----------------------|
|
||
| 输入参数 | `code, staticDiagnostics, customRules` | `scope: MethodScope, customRules` |
|
||
| AI 调用次数 | 2 次(requestA + requestB 并行) | 1 次(规则匹配 + 深度审查合并) |
|
||
| prompt 定位 | 补充静态分析盲区 | 唯一审查者,全覆盖 |
|
||
| 输出结构 | `customRuleResults + translatedDiagnostics + findings` | `customRuleResults + findings` |
|
||
| category | 5 类 | 6 类(新增 correctness、testability) |
|
||
| path 字段 | 无 | 有(触发路径描述) |
|
||
| 规则匹配方式 | 独立 prompt + 独立请求 | 与深度审查合并为单次 prompt |
|
||
|
||
### 6. `src/activation/commands.ts`
|
||
|
||
新增 `codeReviewer.reviewMethod` 命令。不修改现有命令。
|
||
|
||
#### 新增命令注册
|
||
|
||
在 `registerCommands` 函数内追加:
|
||
|
||
```typescript
|
||
context.subscriptions.push(
|
||
vscode.commands.registerCommand(
|
||
'codeReviewer.reviewMethod',
|
||
async (symbolRange: vscode.Range) => {
|
||
const editor = vscode.window.activeTextEditor;
|
||
if (!editor) return;
|
||
|
||
const document = editor.document;
|
||
const workspaceRoot = vscode.workspace.getWorkspaceFolder(document.uri)?.uri.fsPath;
|
||
|
||
// 1. 提取方法
|
||
const scope = await extractMethodScope(document, symbolRange);
|
||
if (!scope) {
|
||
vscode.window.showWarningMessage(t('methodReview.noMethod'));
|
||
return;
|
||
}
|
||
|
||
// 2. 加载并过滤自定义规则(复用现有 rule-filter)
|
||
let customRules: CustomRule[] = [];
|
||
if (workspaceRoot) {
|
||
const allRules = await loadActiveRules(workspaceRoot);
|
||
customRules = filterForDocument(allRules, document);
|
||
}
|
||
|
||
// 3. 执行 AI 审查(传入自定义规则)
|
||
await vscode.window.withProgress(
|
||
{
|
||
location: vscode.ProgressLocation.Notification,
|
||
title: t('methodReview.running', { 0: scope.name }),
|
||
cancellable: false,
|
||
},
|
||
async () => {
|
||
const result = await runMethodReview(context, scope, customRules);
|
||
|
||
// 4. 构造 MergedReport(复用现有 merger)
|
||
// customRuleResults 和 findings 均来自 AI 单次调用
|
||
const totalIssues = result.customRuleResults.length + result.findings.length;
|
||
currentReport = mergeResults({
|
||
staticDiagnostics: [],
|
||
customRuleResults: result.customRuleResults,
|
||
translatedDiagnostics: [],
|
||
aiFindings: result.findings, // MethodFinding[] 兼容 AIFinding[]
|
||
errors: result.error ? [result.error] : [],
|
||
degraded: result.degraded,
|
||
startTime: Date.now(),
|
||
filePath: document.uri.fsPath,
|
||
language: document.languageId,
|
||
adapterIds: [],
|
||
customRuleFilterInfo: undefined,
|
||
});
|
||
|
||
// 5. 更新状态缓存并刷新 CodeLens
|
||
statusCache.set(document.uri, scope.name, totalIssues);
|
||
codeLensProvider.refresh();
|
||
|
||
// 6. 面板展示
|
||
const panel = ReviewPanel.createOrShow(context.extensionUri);
|
||
panel.update(currentReport);
|
||
|
||
// 7. 提示
|
||
vscode.window.showInformationMessage(
|
||
t('methodReview.complete', { 0: String(totalIssues) })
|
||
);
|
||
}
|
||
);
|
||
}
|
||
)
|
||
);
|
||
```
|
||
|
||
#### 函数签名变更
|
||
|
||
`registerCommands` 需要新增两个参数:
|
||
|
||
```typescript
|
||
export function registerCommands(
|
||
context: vscode.ExtensionContext,
|
||
orchestrator: Orchestrator,
|
||
codeLensProvider: MethodCodeLensProvider, // 新增
|
||
statusCache: ReviewStatusCache, // 新增
|
||
): void {
|
||
```
|
||
|
||
#### 新增 import
|
||
|
||
`commands.ts` 顶部需追加以下 import(现有 import 不变):
|
||
|
||
```typescript
|
||
import { loadActiveRules } from '../rules/rule-loader';
|
||
import { filterForDocument } from '../rules/rule-filter';
|
||
import { CustomRule } from '../types';
|
||
```
|
||
|
||
### 7. `src/extension.ts`
|
||
|
||
注册 CodeLensProvider 和 StatusCache。
|
||
|
||
#### 修改内容
|
||
|
||
在 `activate` 函数中,`registerCommands` 调用之前新增:
|
||
|
||
```typescript
|
||
export function activate(context: vscode.ExtensionContext) {
|
||
const lang = getAIOutputLanguage() as Language;
|
||
setLanguage(lang);
|
||
console.log(t('extension.activated'));
|
||
|
||
orchestrator = new Orchestrator();
|
||
|
||
const setupProvider = new SetupViewProvider(context);
|
||
context.subscriptions.push(
|
||
vscode.window.registerWebviewViewProvider('codeReviewer.setupView', setupProvider)
|
||
);
|
||
|
||
// ===== 新增:方法级审查基础设施 =====
|
||
const statusCache = new ReviewStatusCache();
|
||
const codeLensProvider = new MethodCodeLensProvider(statusCache);
|
||
|
||
context.subscriptions.push(
|
||
vscode.languages.registerCodeLensProvider(
|
||
{ scheme: 'file' },
|
||
codeLensProvider
|
||
)
|
||
);
|
||
|
||
// 文档关闭时清理状态缓存
|
||
context.subscriptions.push(
|
||
vscode.workspace.onDidCloseTextDocument((document) => {
|
||
statusCache.clearDocument(document.uri);
|
||
})
|
||
);
|
||
// ===== 新增结束 =====
|
||
|
||
registerCommands(context, orchestrator, codeLensProvider, statusCache);
|
||
|
||
// ... 保留现有的 onDidSaveTextDocument 和 onDidChangeConfiguration 逻辑不变 ...
|
||
}
|
||
```
|
||
|
||
### 8. `package.json`
|
||
|
||
#### 新增 command
|
||
|
||
在 `contributes.commands` 数组中追加:
|
||
|
||
```json
|
||
{
|
||
"command": "codeReviewer.reviewMethod",
|
||
"title": "Code Purifier: 审查此方法"
|
||
}
|
||
```
|
||
|
||
#### 新增 configuration
|
||
|
||
在 `contributes.configuration.properties` 中追加:
|
||
|
||
```json
|
||
"vscode-code-reviewer.codelens.enabled": {
|
||
"type": "boolean",
|
||
"default": true,
|
||
"description": "在函数声明上方显示方法级审查按钮"
|
||
},
|
||
"vscode-code-reviewer.codelens.languages": {
|
||
"type": "array",
|
||
"default": ["typescript", "javascript", "java", "python"],
|
||
"description": "启用方法级 CodeLens 的语言列表"
|
||
}
|
||
```
|
||
|
||
#### 新增 menu(可选)
|
||
|
||
在 `contributes.menus` 中追加右键菜单入口(作为 CodeLens 的补充):
|
||
|
||
```json
|
||
"editor/context": [
|
||
{
|
||
"command": "codeReviewer.reviewMethod",
|
||
"when": "editorTextFocus",
|
||
"group": "navigation"
|
||
}
|
||
]
|
||
```
|
||
|
||
注意:右键菜单触发时 `symbolRange` 参数为 `undefined`,命令内需要处理此情况——使用 `editor.selection.active` 作为 fallback 位置传入 `extractMethodScope`。
|
||
|
||
---
|
||
|
||
## 实施顺序
|
||
|
||
按以下顺序实施,每步完成后可独立验证:
|
||
|
||
### Phase 1:基础设施
|
||
|
||
| 步骤 | 文件 | 验证方式 |
|
||
|------|------|----------|
|
||
| 1.1 | `src/scope/status-cache.ts` | 单元测试:set/get/clearDocument 行为正确 |
|
||
| 1.2 | `src/scope/method-extractor.ts` | 手动测试:在 TS 文件中调用 `getMethodSymbols` 和 `extractMethodScope`,确认能提取方法名、代码、调用链 |
|
||
| 1.3 | `src/ai/schema.ts` | TypeScript 编译通过,类型无冲突 |
|
||
|
||
### Phase 2:AI 引擎
|
||
|
||
| 步骤 | 文件 | 验证方式 |
|
||
|------|------|----------|
|
||
| 2.1 | `src/ai/engine.ts` 新增 `buildMethodReviewSystemPrompt` | 确认 prompt 文本包含 6 维度、逐路径枚举策略,且有规则时包含 Task 1 规则匹配任务 |
|
||
| 2.2 | `src/ai/engine.ts` 新增 `buildMethodUserPrompt` | 确认输出包含签名、代码、调用链,且传入 `customRules` 时包含规则列表块 |
|
||
| 2.3 | `src/ai/engine.ts` 新增 `runMethodReview` | 配置 API Key + 自定义规则后手动调用,确认返回 `customRuleResults` 和 `findings` 两类结果 |
|
||
|
||
### Phase 3:命令与集成
|
||
|
||
| 步骤 | 文件 | 验证方式 |
|
||
|------|------|----------|
|
||
| 3.1 | `src/activation/commands.ts` 新增 `reviewMethod` 命令 | 通过命令面板触发,确认加载自定义规则并传入 `runMethodReview`,面板展示审查结果 |
|
||
| 3.2 | `src/extension.ts` 注册 CodeLensProvider | 打开 TS 文件,确认函数上方出现 CodeLens 按钮 |
|
||
| 3.3 | `src/views/codeLensProvider.ts` | 点击 CodeLens 按钮,确认触发方法级审查 |
|
||
| 3.4 | `package.json` 新增配置项 | 在设置中修改 `codelens.enabled`,确认 CodeLens 消失/出现 |
|
||
|
||
### Phase 4:i18n 与打磨
|
||
|
||
| 步骤 | 文件 | 验证方式 |
|
||
|------|------|----------|
|
||
| 4.1 | `src/i18n/messages.ts` 新增 key | 切换输出语言为 en,确认 CodeLens 文案变为英文 |
|
||
| 4.2 | 右键菜单 fallback 处理 | 右键触发 `reviewMethod`,确认能用光标位置定位方法 |
|
||
| 4.3 | 状态缓存刷新 | 审查完成后确认 CodeLens 文案变为 `✓ 已审查(N 个问题)` |
|
||
|
||
---
|
||
|
||
## 验收标准
|
||
|
||
### 功能验收
|
||
|
||
1. 打开任意 `.ts` / `.js` / `.java` / `.py` 文件,每个函数声明上方出现 `🔍 Code Purifier: 审查此方法` CodeLens 按钮
|
||
2. 点击按钮后,右下角弹出进度通知 `Code Purifier 正在审查方法:xxx`
|
||
3. 审查完成后,CodeLens 按钮文案变为 `✓ Code Purifier: 已审查(N 个问题)` 或 `✓ Code Purifier: 已审查(无问题)`,其中 N = 自定义规则违规数 + AI 深度审查发现数
|
||
4. 审查面板自动打开,展示方法级的 AI 审查结果
|
||
5. 审查结果中,自定义规则违规的 `ruleId` 以 `custom:` 前缀标识,AI 深度审查发现的 `ruleId` 以 `method:` 前缀标识
|
||
6. 审查结果覆盖 6 个维度(correctness / security / design / convention / performance / testability)
|
||
7. 审查结果中的 `path` 字段描述了触发路径
|
||
8. 当工作区配置了自定义规则时,审查结果中包含 `customRuleResults`,且违规内容与规则语义相关(非文本匹配)
|
||
9. 当工作区无自定义规则时,审查结果中 `customRuleResults` 为空数组,AI 仅做 6 维度深度审查
|
||
|
||
### 边界验收
|
||
|
||
1. 在没有 Symbol Provider 的纯文本文件中,CodeLens 不出现(不报错)
|
||
2. 文件内方法数超过 50 个时,CodeLens 不出现(性能保护)
|
||
3. 在设置中关闭 `codelens.enabled` 后,CodeLens 消失
|
||
4. 在设置中将某语言从 `codelens.languages` 中移除后,该语言文件不显示 CodeLens
|
||
5. 未配置 API Key 时,提示需要 API Key(不崩溃)
|
||
6. AI 返回非 JSON 时,降级提示解析失败(不崩溃)
|
||
7. 文档关闭后重新打开,CodeLens 恢复为初始状态 `🔍 Code Purifier: 审查此方法`(缓存已清理)
|
||
|
||
### 架构验收
|
||
|
||
1. `src/orchestrator/orchestrator.ts` 未被修改
|
||
2. `src/rules/rule-filter.ts` 未被修改
|
||
3. 现有 `codeReviewer.review` 命令行为不变(文档级三阶段审查正常工作)
|
||
4. 现有 `codeReviewer.reviewSelection` 命令行为不变
|
||
5. TypeScript 编译无错误(`npm run compile`)
|
||
6. ESLint 检查无错误(`npm run lint`)
|
||
|
||
---
|
||
|
||
## 注意事项
|
||
|
||
- 所有用户可见文案必须通过 `t()` 函数获取,支持中英文切换
|
||
- `runMethodReview` 内复用现有的 `getApiKey`、`createProvider`、`parseJsonResponse`、`addLineNumbers` 等工具函数,不重复实现
|
||
- `MethodFinding` 的字段与 `AIFinding` 高度重叠,`mergeResults` 可直接接收 `MethodFinding[]` 作为 `aiFindings` 参数(结构兼容)
|
||
- CodeLens 的 `refresh()` 通过 `_onDidChangeCodeLenses.fire()` 触发,VS Code 会重新调用 `provideCodeLenses`
|
||
- `package.json` 中的 `engines.vscode` 字段为 `^1.120.0`,CodeLens API 和 DocumentSymbol API 在该版本完全支持
|
||
- 自定义规则通过 `loadActiveRules` + `filterForDocument` 加载,与现有 `codeReviewer.review` 命令使用同一加载路径,保证规则一致性
|
||
- AI 对自定义规则做语义匹配而非文本匹配——prompt 中明确要求"理解语义,而非文本匹配",避免简单关键词命中导致的误报
|
||
- 当 `customRules` 为空数组时,system prompt 中的 Task 1 规则匹配段和 `customRuleResults` 输出段自动省略,AI 仅执行 6 维度深度审查
|