feat: 方法级代码审查 + 模板导入/预览增强 + SQLFluff 方言 + AI 空响应报错修复

- 方法级审查: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 + 行号排序
This commit is contained in:
范智鹏
2026-08-03 22:53:20 +08:00
parent 5848eaa82a
commit 247f19fd44
46 changed files with 6143 additions and 937 deletions
@@ -0,0 +1,1000 @@
# 方法级代码审查功能实施计划书
> 面向 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 2AI 引擎
| 步骤 | 文件 | 验证方式 |
|------|------|----------|
| 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 4i18n 与打磨
| 步骤 | 文件 | 验证方式 |
|------|------|----------|
| 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 维度深度审查