前言
💡 痛点:每次提交前都要请同事 Review?Code Review 排队耗时长?新来的同事不知道团队规范?
🎯 解决方案:AI 代码审查 VSCode 插件 --- 实时审查、自动修复、学习团队规范。
#mermaid-svg-au20ZioQEdrIwTqc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-au20ZioQEdrIwTqc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-au20ZioQEdrIwTqc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-au20ZioQEdrIwTqc .error-icon{fill:#552222;}#mermaid-svg-au20ZioQEdrIwTqc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-au20ZioQEdrIwTqc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-au20ZioQEdrIwTqc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-au20ZioQEdrIwTqc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-au20ZioQEdrIwTqc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-au20ZioQEdrIwTqc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-au20ZioQEdrIwTqc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-au20ZioQEdrIwTqc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-au20ZioQEdrIwTqc .marker.cross{stroke:#333333;}#mermaid-svg-au20ZioQEdrIwTqc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-au20ZioQEdrIwTqc p{margin:0;}#mermaid-svg-au20ZioQEdrIwTqc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-au20ZioQEdrIwTqc .cluster-label text{fill:#333;}#mermaid-svg-au20ZioQEdrIwTqc .cluster-label span{color:#333;}#mermaid-svg-au20ZioQEdrIwTqc .cluster-label span p{background-color:transparent;}#mermaid-svg-au20ZioQEdrIwTqc .label text,#mermaid-svg-au20ZioQEdrIwTqc span{fill:#333;color:#333;}#mermaid-svg-au20ZioQEdrIwTqc .node rect,#mermaid-svg-au20ZioQEdrIwTqc .node circle,#mermaid-svg-au20ZioQEdrIwTqc .node ellipse,#mermaid-svg-au20ZioQEdrIwTqc .node polygon,#mermaid-svg-au20ZioQEdrIwTqc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-au20ZioQEdrIwTqc .rough-node .label text,#mermaid-svg-au20ZioQEdrIwTqc .node .label text,#mermaid-svg-au20ZioQEdrIwTqc .image-shape .label,#mermaid-svg-au20ZioQEdrIwTqc .icon-shape .label{text-anchor:middle;}#mermaid-svg-au20ZioQEdrIwTqc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-au20ZioQEdrIwTqc .rough-node .label,#mermaid-svg-au20ZioQEdrIwTqc .node .label,#mermaid-svg-au20ZioQEdrIwTqc .image-shape .label,#mermaid-svg-au20ZioQEdrIwTqc .icon-shape .label{text-align:center;}#mermaid-svg-au20ZioQEdrIwTqc .node.clickable{cursor:pointer;}#mermaid-svg-au20ZioQEdrIwTqc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-au20ZioQEdrIwTqc .arrowheadPath{fill:#333333;}#mermaid-svg-au20ZioQEdrIwTqc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-au20ZioQEdrIwTqc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-au20ZioQEdrIwTqc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-au20ZioQEdrIwTqc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-au20ZioQEdrIwTqc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-au20ZioQEdrIwTqc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-au20ZioQEdrIwTqc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-au20ZioQEdrIwTqc .cluster text{fill:#333;}#mermaid-svg-au20ZioQEdrIwTqc .cluster span{color:#333;}#mermaid-svg-au20ZioQEdrIwTqc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-au20ZioQEdrIwTqc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-au20ZioQEdrIwTqc rect.text{fill:none;stroke-width:0;}#mermaid-svg-au20ZioQEdrIwTqc .icon-shape,#mermaid-svg-au20ZioQEdrIwTqc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-au20ZioQEdrIwTqc .icon-shape p,#mermaid-svg-au20ZioQEdrIwTqc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-au20ZioQEdrIwTqc .icon-shape .label rect,#mermaid-svg-au20ZioQEdrIwTqc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-au20ZioQEdrIwTqc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-au20ZioQEdrIwTqc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-au20ZioQEdrIwTqc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 后端
VSCode 插件
编辑器事件
语言服务
AI 审查引擎
诊断
代码建议
自动修复
编辑器 UI
LLM API
规则引擎
已有方案 vs AI 插件:
| 维度 | Code Review 平台 | ESLint/Pylint | AI VSCode 插件 |
|---|---|---|---|
| 审查时机 | 提交后 | 保存时 | 编辑时实时 |
| 语义理解 | ❌ | ❌ | ✅ 理解业务逻辑 |
| 团队规范 | 依赖 Reviewer | 规则配置 | 自动学习 |
| 修复建议 | 文字评论 | ✅ 自动修复 | ✅ 智能修复 |
| 学习成本 | 低 | 高(配置) | 零配置 |
一、插件架构设计
1.1 整体架构
typescript
// ===== VSCode 插件架构(TypeScript)=====
import * as vscode from 'vscode';
/**
* AI Code Review 插件架构
*
* 分层设计:
* 1. Extension Layer (入口层) - VSCode 生命周期管理
* 2. Service Layer (服务层) - AI 审查/规则引擎/配置管理
* 3. Provider Layer (提供者层) - 诊断/代码操作/Hover
* 4. LLM Layer (模型层) - 多模型适配/缓存/限流
*/
// ===== 入口层 =====
export class AIReviewExtension {
private context: vscode.ExtensionContext;
private configManager: ConfigManager;
private ruleEngine: RuleEngine;
private aiEngine: AIReviewEngine;
private diagnosticProvider: DiagnosticProvider;
private codeActionProvider: CodeActionProvider;
private hoverProvider: HoverProvider;
constructor(context: vscode.ExtensionContext) {
this.context = context;
this.configManager = new ConfigManager(context);
this.ruleEngine = new RuleEngine(this.configManager);
this.aiEngine = new AIReviewEngine(this.configManager);
this.diagnosticProvider = new DiagnosticProvider(
this.ruleEngine,
this.aiEngine
);
this.codeActionProvider = new CodeActionProvider(this.aiEngine);
this.hoverProvider = new HoverProvider(this.aiEngine);
}
activate() {
// 注册诊断
this.diagnosticProvider.register(this.context);
// 注册代码操作
this.codeActionProvider.register(this.context);
// 注册 Hover
this.hoverProvider.register(this.context);
// 注册命令
this.registerCommands();
// 监听配置变更
this.watchConfigChanges();
// 初始化状态栏
this.initStatusBar();
}
private registerCommands() {
const commands = [
['ai-review.reviewFile', () => this.reviewCurrentFile()],
['ai-review.reviewSelection', () => this.reviewSelection()],
['ai-review.reviewDiff', () => this.reviewGitDiff()],
['ai-review.explainCode', () => this.explainCode()],
['ai-review.generateTest', () => this.generateTests()],
['ai-review.optimizeCode', () => this.optimizeCode()],
['ai-review.fixAll', () => this.fixAllIssues()],
['ai-review.showHistory', () => this.showReviewHistory()],
['ai-review.configure', () => this.openSettings()],
['ai-review.toggle', () => this.toggleAutoReview()],
] as const;
for (const [id, handler] of commands) {
this.context.subscriptions.push(
vscode.commands.registerCommand(id, handler)
);
}
}
private async reviewCurrentFile() {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
const document = editor.document;
const language = document.languageId;
const code = document.getText();
await vscode.window.withProgress(
{ location: vscode.ProgressLocation.Notification, title: 'AI 审查中...' },
async () => {
const results = await this.aiEngine.reviewFile(code, language);
this.diagnosticProvider.showResults(document, results);
}
);
}
private async reviewSelection() {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
const selection = editor.selection;
const code = editor.document.getText(selection);
const language = editor.document.languageId;
const results = await this.aiEngine.reviewSelection(
code, language, selection.start.line
);
this.diagnosticProvider.showResults(editor.document, results);
}
private async reviewGitDiff() { /* 见第六章 */ }
private async explainCode() {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
const selection = editor.selection;
const code = editor.document.getText(selection);
const explanation = await this.aiEngine.explain(code);
vscode.window.showInformationMessage(explanation);
}
private async generateTests() { /* 生成测试 */ }
private async optimizeCode() { /* 优化代码 */ }
private async fixAllIssues() { /* 批量修复 */ }
private showReviewHistory() { /* 审查历史 */ }
private openSettings() {
vscode.commands.executeCommand(
'workbench.action.openSettings', 'ai-review'
);
}
private toggleAutoReview() {
const config = this.configManager.getConfig();
config.autoReview = !config.autoReview;
this.configManager.saveConfig(config);
}
private watchConfigChanges() {
this.context.subscriptions.push(
vscode.workspace.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('ai-review')) {
this.configManager.reload();
}
})
);
}
private initStatusBar() {
const statusItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right, 100
);
statusItem.text = '$(beaker) AI Review';
statusItem.command = 'ai-review.toggle';
statusItem.show();
this.context.subscriptions.push(statusItem);
}
}
二、AI 审查引擎
2.1 双引擎审查
typescript
// ===== 服务层:AI 审查引擎 =====
interface ReviewIssue {
id: string;
type: 'ai' | 'rule';
severity: 'error' | 'warning' | 'info' | 'hint';
category: ReviewCategory;
title: string;
description: string;
line: number;
column: number;
endLine?: number;
endColumn?: number;
fix?: CodeFix;
relatedRules?: string[];
confidence: number; // 0-1
}
type ReviewCategory =
| 'security' | 'performance' | 'bug_risk'
| 'code_style' | 'best_practice' | 'maintainability'
| 'error_handling' | 'naming' | 'complexity'
| 'dead_code' | 'type_safety';
interface CodeFix {
title: string;
edits: TextEdit[];
}
interface TextEdit {
range: { startLine: number; startCol: number; endLine: number; endCol: number };
newText: string;
}
class AIReviewEngine {
private ruleEngine: RuleEngine;
private llmClient: LLMClient;
private cache: Map<string, ReviewIssue[]>;
private rateLimiter: RateLimiter;
private config: AIReviewConfig;
constructor(configManager: ConfigManager) {
this.config = configManager.getConfig();
this.ruleEngine = new RuleEngine(configManager);
this.llmClient = new LLMClient(this.config.llm);
this.cache = new Map();
this.rateLimiter = new RateLimiter(
this.config.llm.maxRequestsPerMinute || 20
);
}
/**
* 双引擎审查:规则引擎(毫秒级)+ AI(秒级)
*/
async reviewFile(
code: string,
language: string
): Promise<ReviewIssue[]> {
const cacheKey = `${language}:${hashContent(code)}`;
// 检查缓存
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey)!;
}
// 1. 规则引擎(同步,毫秒级)
const ruleIssues = this.ruleEngine.check(code, language);
// 2. AI 引擎(异步,秒级)
let aiIssues: ReviewIssue[] = [];
try {
await this.rateLimiter.wait();
aiIssues = await this.llmClient.reviewCode(code, language);
} catch (error) {
console.error('AI review failed:', error);
}
// 3. 去重合并
const allIssues = this.deduplicate(ruleIssues, aiIssues);
// 缓存
this.cache.set(cacheKey, allIssues);
return allIssues;
}
async reviewSelection(
code: string,
language: string,
startLine: number
): Promise<ReviewIssue[]> {
await this.rateLimiter.wait();
const aiIssues = await this.llmClient.reviewSelection(code, language);
// 调整行号偏移
return aiIssues.map(issue => ({
...issue,
line: issue.line + startLine,
endLine: issue.endLine ? issue.endLine + startLine : undefined,
}));
}
/**
* 代码解释
*/
async explain(code: string): Promise<string> {
await this.rateLimiter.wait();
return this.llmClient.explainCode(code);
}
/**
* 优化建议
*/
async optimize(code: string, language: string): Promise<CodeFix[]> {
await this.rateLimiter.wait();
return this.llmClient.optimizeCode(code, language);
}
/**
* 去重:规则和 AI 可能发现同一问题
*/
private deduplicate(
ruleIssues: ReviewIssue[],
aiIssues: ReviewIssue[]
): ReviewIssue[] {
const merged = [...ruleIssues];
const existingLocations = new Set(
ruleIssues.map(i => `${i.line}:${i.column}`)
);
for (const aiIssue of aiIssues) {
const key = `${aiIssue.line}:${aiIssue.column}`;
if (!existingLocations.has(key)) {
merged.push(aiIssue);
existingLocations.add(key);
} else {
// 同位置:保留置信度更高的
const existing = merged.find(
i => `${i.line}:${i.column}` === key
);
if (existing && aiIssue.confidence > existing.confidence) {
Object.assign(existing, aiIssue, { type: 'ai' });
}
}
}
// 按严重程度排序
const severityOrder = { error: 0, warning: 1, info: 2, hint: 3 };
merged.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
return merged;
}
}
function hashContent(content: string): string {
// 简单哈希(生产环境用 crypto)
let hash = 0;
for (let i = 0; i < content.length; i++) {
const char = content.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return hash.toString(36);
}
三、规则引擎
3.1 可编程规则
typescript
// ===== 规则引擎 =====
interface Rule {
id: string;
name: string;
description: string;
category: ReviewCategory;
severity: ReviewIssue['severity'];
language: string | string[]; // 'all' 或具体语言
check: (code: string, ast?: any) => ReviewIssue[];
fix?: (code: string, issue: ReviewIssue) => string;
}
class RuleEngine {
private rules: Map<string, Rule> = new Map();
private enabledRules: Set<string> = new Set();
constructor(configManager: ConfigManager) {
this.loadBuiltinRules();
this.loadCustomRules(configManager);
}
/**
* 规则检查
*/
check(code: string, language: string): ReviewIssue[] {
const issues: ReviewIssue[] = [];
const lines = code.split('\n');
for (const rule of this.rules.values()) {
if (!this.enabledRules.has(rule.id)) continue;
const languages = Array.isArray(rule.language) ? rule.language : [rule.language];
if (!languages.includes('all') && !languages.includes(language)) continue;
try {
const ruleIssues = rule.check(code, lines);
issues.push(...ruleIssues);
} catch (error) {
console.error(`Rule ${rule.id} failed:`, error);
}
}
return issues;
}
private loadBuiltinRules() {
// 安全规则
this.addRule({
id: 'sec-hardcoded-password',
name: '硬编码密码',
description: '检测硬编码的密码和密钥',
category: 'security',
severity: 'error',
language: 'all',
check: (code) => {
const issues: ReviewIssue[] = [];
const patterns = [
/password\s*[:=]\s*['"][^'"]{4,}['"]/gi,
/secret\s*[:=]\s*['"][^'"]{4,}['"]/gi,
/api_key\s*[:=]\s*['"][^'"]{10,}['"]/gi,
/token\s*[:=]\s*['"][^'"]{10,}['"]/gi,
];
const lines = code.split('\n');
for (const pattern of patterns) {
for (let i = 0; i < lines.length; i++) {
const match = lines[i].match(pattern);
if (match) {
issues.push({
id: `sec-${i}`,
type: 'rule',
severity: 'error',
category: 'security',
title: '检测到硬编码的敏感信息',
description: `发现可能硬编码的 ${pattern.source.slice(0, 15)}...,建议使用环境变量或密钥管理服务。`,
line: i,
column: match.index || 0,
confidence: 0.9,
relatedRules: ['OWASP A02', 'CWE-798'],
});
}
}
}
return issues;
},
});
// 性能规则
this.addRule({
id: 'perf-n-plus-one',
name: 'N+1 查询',
description: '检测循环中的数据库/HTTP 请求',
category: 'performance',
severity: 'warning',
language: ['python', 'javascript', 'typescript'],
check: (code) => {
const issues: ReviewIssue[] = [];
const lines = code.split('\n');
let inLoop = false;
let loopStart = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (/\b(for|while)\b/.test(line) && !/\/\//.test(line)) {
inLoop = true;
loopStart = i;
}
if (inLoop) {
if (/\b(requests\.(get|post)|fetch\(|\.query\(|\.execute\(|await\s+\w+\.(find|get))/.test(line)) {
issues.push({
id: `perf-${i}`,
type: 'rule',
severity: 'warning',
category: 'performance',
title: '可能的 N+1 查询',
description: `循环内(第 ${loopStart + 1} 行)发现数据请求,可能导致 N+1 查询问题。建议批量查询后内存关联。`,
line: i,
column: 0,
confidence: 0.7,
fix: {
title: '建议改为批量查询',
edits: [],
},
});
}
}
}
return issues;
},
});
// 复杂度规则
this.addRule({
id: 'complex-high-cyclomatic',
name: '圈复杂度过高',
description: '函数圈复杂度超过阈值',
category: 'complexity',
severity: 'warning',
language: 'all',
check: (code) => {
const issues: ReviewIssue[] = [];
// 简化检测:统计 if/else/for/while/try/&&/||
const complexityPattern = /\b(if|else\s+if|for|while|case|catch|&&|\|\||\?)\b/g;
const lines = code.split('\n');
let currentFunction = '';
let funcStart = 0;
let complexity = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (/\b(def|function|func|fn)\s+/.test(line)) {
// 新函数开始
if (currentFunction && complexity > 10) {
issues.push({
id: `complex-${funcStart}`,
type: 'rule',
severity: 'warning',
category: 'complexity',
title: `函数 ${currentFunction} 圈复杂度过高`,
description: `圈复杂度为 ${complexity},建议不超过 10。考虑拆分子函数。`,
line: funcStart,
column: 0,
endLine: i,
confidence: 0.8,
});
}
const match = line.match(/(?:def|function|func|fn)\s+(\w+)/);
currentFunction = match ? match[1] : 'anonymous';
funcStart = i;
complexity = 1;
}
const matches = line.match(complexityPattern);
if (matches) {
complexity += matches.length;
}
}
// 检查最后一个函数
if (currentFunction && complexity > 10) {
issues.push({
id: `complex-${funcStart}`,
type: 'rule',
severity: 'warning',
category: 'complexity',
title: `函数 ${currentFunction} 圈复杂度过高`,
description: `圈复杂度为 ${complexity},建议不超过 10。`,
line: funcStart,
column: 0,
confidence: 0.8,
});
}
return issues;
},
});
// 错误处理
this.addRule({
id: 'err-bare-except',
name: '裸异常捕获',
description: 'except/pass 无操作的异常处理',
category: 'error_handling',
severity: 'warning',
language: ['python'],
check: (code) => {
const issues: ReviewIssue[] = [];
const lines = code.split('\n');
for (let i = 0; i < lines.length; i++) {
if (/^\s*except\s*:/.test(lines[i]) || /^\s*except\s+Exception\s*:/.test(lines[i])) {
issues.push({
id: `err-${i}`,
type: 'rule',
severity: 'warning',
category: 'error_handling',
title: '过于宽泛的异常捕获',
description: '建议捕获具体异常类型,避免隐藏真实错误。',
line: i,
column: lines[i].indexOf('except'),
confidence: 0.85,
});
}
}
return issues;
},
});
}
private loadCustomRules(configManager: ConfigManager) {
// 加载用户自定义规则(从配置文件)
const customRules = configManager.getCustomRules();
for (const rule of customRules) {
this.addRule(rule);
}
}
private addRule(rule: Rule) {
this.rules.set(rule.id, rule);
this.enabledRules.add(rule.id);
}
enableRule(id: string) { this.enabledRules.add(id); }
disableRule(id: string) { this.enabledRules.delete(id); }
}
四、LLM 客户端层
4.1 多模型适配
typescript
// ===== LLM 客户端层 =====
interface LLMConfig {
provider: 'openai' | 'anthropic' | 'local' | 'azure';
apiKey?: string;
baseUrl?: string;
model: string;
maxTokens: number;
temperature: number;
maxRequestsPerMinute: number;
timeout: number;
}
interface LLMResponse {
content: string;
usage: {
promptTokens: number;
completionTokens: number;
totalTokens: number;
};
model: string;
latencyMs: number;
}
abstract class LLMProvider {
abstract chat(messages: ChatMessage[], options?: ChatOptions): Promise<LLMResponse>;
abstract name(): string;
}
interface ChatMessage {
role: 'system' | 'user' | 'assistant';
content: string;
}
interface ChatOptions {
temperature?: number;
maxTokens?: number;
responseFormat?: 'text' | 'json';
}
class LLMClient {
private provider: LLMProvider;
private config: LLMConfig;
private promptTemplates: PromptTemplates;
constructor(config: LLMConfig) {
this.config = config;
this.provider = this.createProvider(config);
this.promptTemplates = new PromptTemplates();
}
private createProvider(config: LLMConfig): LLMProvider {
switch (config.provider) {
case 'openai': return new OpenAIProvider(config);
case 'anthropic': return new AnthropicProvider(config);
case 'local': return new LocalProvider(config);
case 'azure': return new AzureOpenAIProvider(config);
default: return new OpenAIProvider(config);
}
}
/**
* 审查代码(结构化输出)
*/
async reviewCode(code: string, language: string): Promise<ReviewIssue[]> {
const prompt = this.promptTemplates.codeReview(code, language);
const response = await this.provider.chat(
[
{ role: 'system', content: SYSTEM_PROMPT_CODE_REVIEW },
{ role: 'user', content: prompt },
],
{ responseFormat: 'json', temperature: 0.2 }
);
return this.parseReviewResponse(response.content, language);
}
async reviewSelection(code: string, language: string): Promise<ReviewIssue[]> {
const prompt = this.promptTemplates.selectionReview(code, language);
const response = await this.provider.chat(
[
{ role: 'system', content: SYSTEM_PROMPT_CODE_REVIEW },
{ role: 'user', content: prompt },
],
{ responseFormat: 'json', temperature: 0.2 }
);
return this.parseReviewResponse(response.content, language);
}
async explainCode(code: string): Promise<string> {
const response = await this.provider.chat([
{ role: 'system', content: '你是代码解释专家,用简洁清晰的语言解释代码逻辑。' },
{ role: 'user', content: `请解释以下代码:\n\n${code}` },
]);
return response.content;
}
async optimizeCode(code: string, language: string): Promise<CodeFix[]> {
const prompt = `请优化以下 ${language} 代码,给出修改建议。
代码:
${code}
请以 JSON 数组格式返回修复建议:
[
{
"title": "修复标题",
"description": "修复说明",
"original": "原始代码片段",
"fixed": "修复后代码片段"
}
]`;
const response = await this.provider.chat([
{ role: 'system', content: '你是代码优化专家。只输出 JSON。' },
{ role: 'user', content: prompt },
], { responseFormat: 'json' });
// 解析并转换为 CodeFix
try {
const fixes = JSON.parse(response.content);
return (Array.isArray(fixes) ? fixes : []).map((f: any) => ({
title: f.title,
edits: [{
range: { startLine: 0, startCol: 0, endLine: 0, endCol: 0 },
newText: f.fixed,
}],
}));
} catch {
return [];
}
}
private parseReviewResponse(content: string, language: string): ReviewIssue[] {
try {
const data = JSON.parse(content);
const issues: ReviewIssue[] = [];
const items = Array.isArray(data) ? data : data.issues || [];
for (const item of items) {
issues.push({
id: `ai-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
type: 'ai',
severity: item.severity || 'warning',
category: item.category || 'best_practice',
title: item.title,
description: item.description,
line: (item.line || 1) - 1, // 0-indexed
column: (item.column || 1) - 1,
confidence: item.confidence || 0.7,
fix: item.fix ? {
title: item.fix.title || 'AI 建议',
edits: item.fix.edits || [],
} : undefined,
});
}
return issues;
} catch {
console.error('Failed to parse AI review response');
return [];
}
}
}
// ===== 提示模板 =====
const SYSTEM_PROMPT_CODE_REVIEW = `你是资深代码审查专家。审查代码时关注:
1. 安全漏洞(注入、XSS、CSRF、硬编码密钥)
2. 性能问题(N+1查询、内存泄漏、阻塞调用)
3. Bug 风险(空指针、边界条件、竞态条件)
4. 错误处理(异常吞没、资源泄漏)
5. 最佳实践(命名、复杂度、可维护性)
严格以 JSON 格式输出审查结果,不要加额外文字。`;
class PromptTemplates {
codeReview(code: string, language: string): string {
return `审查以下 ${language} 代码,找出所有问题。
\`\`\`${language}
${code}
\`\`\`
输出 JSON 数组:
[
{
"title": "问题标题",
"severity": "error|warning|info",
"category": "security|performance|bug_risk|code_style|best_practice|...",
"description": "详细描述",
"line": 1,
"column": 1,
"confidence": 0.8,
"fix": {
"title": "修复建议",
"description": "如何修复"
}
}
]`;
}
selectionReview(code: string, language: string): string {
return `审查以下 ${language} 代码片段:
\`\`\`${language}
${code}
\`\`\`
关注:安全问题、性能问题、Bug 风险、最佳实践。
输出 JSON 格式。`;
}
}
五、编辑器集成
5.1 诊断与代码操作
typescript
// ===== 提供者层 =====
class DiagnosticProvider {
private ruleEngine: RuleEngine;
private aiEngine: AIReviewEngine;
private diagnosticCollection: vscode.DiagnosticCollection;
private config: AIReviewConfig;
constructor(ruleEngine: RuleEngine, aiEngine: AIReviewEngine) {
this.ruleEngine = ruleEngine;
this.aiEngine = aiEngine;
this.diagnosticCollection =
vscode.languages.createDiagnosticCollection('ai-review');
this.config = vscode.workspace.getConfiguration('ai-review') as any;
}
register(context: vscode.ExtensionContext) {
// 保存时审查
context.subscriptions.push(
vscode.workspace.onDidSaveTextDocument(doc => this.onSave(doc))
);
// 输入时实时审查(debounce)
let debounceTimer: NodeJS.Timeout;
context.subscriptions.push(
vscode.workspace.onDidChangeTextDocument(e => {
if (this.config.autoReview) {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => this.onSave(e.document), 2000);
}
})
);
// 打开文件时规则引擎审查
context.subscriptions.push(
vscode.workspace.onDidOpenTextDocument(doc => {
const ruleIssues = this.ruleEngine.check(doc.getText(), doc.languageId);
this.showResults(doc, ruleIssues);
})
);
}
private async onSave(document: vscode.TextDocument) {
// 1. 规则引擎即时
const ruleIssues = this.ruleEngine.check(
document.getText(), document.languageId
);
this.showResults(document, ruleIssues);
// 2. AI 异步审查
if (this.config.enableAI) {
const aiIssues = await this.aiEngine.reviewFile(
document.getText(), document.languageId
);
const allIssues = [...ruleIssues, ...aiIssues];
this.showResults(document, allIssues);
}
}
showResults(document: vscode.TextDocument, issues: ReviewIssue[]) {
const diagnostics: vscode.Diagnostic[] = issues.map(issue => {
const startLine = issue.line;
const startCol = issue.column;
const endLine = issue.endLine || startLine;
const endCol = issue.endColumn || startCol + 20;
const range = new vscode.Range(startLine, startCol, endLine, endCol);
const severityMap = {
error: vscode.DiagnosticSeverity.Error,
warning: vscode.DiagnosticSeverity.Warning,
info: vscode.DiagnosticSeverity.Information,
hint: vscode.DiagnosticSeverity.Hint,
};
const diagnostic = new vscode.Diagnostic(
range,
`[${issue.type.toUpperCase()}] ${issue.title}`,
severityMap[issue.severity]
);
diagnostic.source = `AI Review (${issue.category})`;
diagnostic.message = `${issue.description}\n\n置信度: ${(issue.confidence * 100).toFixed(0)}%`;
diagnostic.code = issue.id;
return diagnostic;
});
this.diagnosticCollection.set(document.uri, diagnostics);
}
clear(document: vscode.TextDocument) {
this.diagnosticCollection.delete(document.uri);
}
}
class CodeActionProvider {
private aiEngine: AIReviewEngine;
constructor(aiEngine: AIReviewEngine) {
this.aiEngine = aiEngine;
}
register(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.languages.registerCodeActionsProvider(
{ scheme: 'file' },
this,
{ providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] }
)
);
}
async provideCodeActions(
document: vscode.TextDocument,
range: vscode.Range,
context: vscode.CodeActionContext
): Promise<vscode.CodeAction[]> {
const actions: vscode.CodeAction[] = [];
for (const diagnostic of context.diagnostics) {
if (diagnostic.source?.startsWith('AI Review')) {
// AI 修复建议
const fixAction = new vscode.CodeAction(
`🤖 AI: ${diagnostic.message.split('\n')[0]}`,
vscode.CodeActionKind.QuickFix
);
fixAction.diagnostics = [diagnostic];
fixAction.command = {
title: 'AI Fix',
command: 'ai-review.applyFix',
arguments: [document, diagnostic],
};
actions.push(fixAction);
}
}
// 全文件 AI 审查
const reviewAll = new vscode.CodeAction(
'🔍 AI 审查全文件',
vscode.CodeActionKind.SourceFixAll.append('ai-review')
);
reviewAll.command = {
title: 'Review File',
command: 'ai-review.reviewFile',
};
actions.push(reviewAll);
return actions;
}
}
class HoverProvider {
private aiEngine: AIReviewEngine;
constructor(aiEngine: AIReviewEngine) {
this.aiEngine = aiEngine;
}
register(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.languages.registerHoverProvider(
{ scheme: 'file' },
this
)
);
}
async provideHover(
document: vscode.TextDocument,
position: vscode.Position,
_token: vscode.CancellationToken
): Promise<vscode.Hover | undefined> {
// 获取光标所在行的代码
const line = document.lineAt(position.line).text;
// 代码解释
const explanation = await this.aiEngine.explain(line);
return new vscode.Hover(
new vscode.MarkdownString(`### 🤖 AI 解释\n\n${explanation}`)
);
}
}
六、Git Diff 审查
6.1 审查变更
typescript
// ===== Git Diff 审查 =====
class GitDiffReviewer {
private aiEngine: AIReviewEngine;
constructor(aiEngine: AIReviewEngine) {
this.aiEngine = aiEngine;
}
/**
* 审查当前 Git Diff
*/
async reviewCurrentDiff(): Promise<ReviewIssue[]> {
const diff = await this.getGitDiff();
if (!diff) {
vscode.window.showInformationMessage('没有检测到变更');
return [];
}
const issues = await this.aiEngine.reviewFile(diff, 'diff');
return issues;
}
private async getGitDiff(): Promise<string | null> {
try {
const { execSync } = require('child_process');
return execSync('git diff HEAD', { encoding: 'utf-8' });
} catch {
return null;
}
}
/**
* 审查 PR 变更
*/
async reviewPullRequest(
repoUrl: string,
prNumber: number,
token: string
): Promise<ReviewIssue[]> {
const diff = await this.fetchPRDiff(repoUrl, prNumber, token);
if (!diff) return [];
return this.aiEngine.reviewFile(diff, 'diff');
}
private async fetchPRDiff(
repoUrl: string,
prNumber: number,
token: string
): Promise<string | null> {
try {
const { execSync } = require('child_process');
const url = `https://api.github.com/repos/${repoUrl}/pulls/${prNumber}`;
const cmd = `curl -s -H "Authorization: token ${token}" "${url}"`;
const result = JSON.parse(execSync(cmd, { encoding: 'utf-8' }));
return result.diff_url ? execSync(
`curl -s -H "Authorization: token ${token}" "${result.diff_url}"`,
{ encoding: 'utf-8' }
) : null;
} catch {
return null;
}
}
}
七、配置与团队规范
7.1 插件配置
typescript
// ===== 配置管理 =====
interface AIReviewConfig {
autoReview: boolean;
enableAI: boolean;
enableRules: boolean;
llm: LLMConfig;
languages: string[];
severityFilters: {
error: boolean;
warning: boolean;
info: boolean;
hint: boolean;
};
excludePatterns: string[];
maxFileSize: number;
debounceMs: number;
}
class ConfigManager {
private context: vscode.ExtensionContext;
constructor(context: vscode.ExtensionContext) {
this.context = context;
}
getConfig(): AIReviewConfig {
const config = vscode.workspace.getConfiguration('ai-review');
return {
autoReview: config.get('autoReview', false),
enableAI: config.get('enableAI', true),
enableRules: config.get('enableRules', true),
llm: {
provider: config.get('llm.provider', 'openai'),
apiKey: config.get('llm.apiKey', ''),
baseUrl: config.get('llm.baseUrl', ''),
model: config.get('llm.model', 'gpt-4'),
maxTokens: config.get('llm.maxTokens', 4096),
temperature: config.get('llm.temperature', 0.2),
maxRequestsPerMinute: config.get('llm.maxRequestsPerMinute', 20),
timeout: config.get('llm.timeout', 30000),
},
languages: config.get('languages', ['python', 'javascript', 'typescript']),
severityFilters: {
error: config.get('severityFilters.error', true),
warning: config.get('severityFilters.warning', true),
info: config.get('severityFilters.info', true),
hint: config.get('severityFilters.hint', false),
},
excludePatterns: config.get('excludePatterns', [
'**/node_modules/**',
'**/dist/**',
'**/.git/**',
'**/vendor/**',
]),
maxFileSize: config.get('maxFileSize', 100000),
debounceMs: config.get('debounceMs', 2000),
};
}
reload() {
// 配置变更时触发重新加载
}
saveConfig(config: AIReviewConfig) {
const vsConfig = vscode.workspace.getConfiguration('ai-review');
for (const [key, value] of Object.entries(config)) {
vsConfig.update(key, value, vscode.ConfigurationTarget.Global);
}
}
getCustomRules(): Rule[] {
// 从工作区 .ai-review-rules.json 加载自定义规则
return [];
}
}
7.2 package.json 配置
json
{
"name": "ai-code-review",
"displayName": "AI Code Review",
"description": "实时 AI 代码审查助手",
"version": "1.0.0",
"engines": { "vscode": "^1.80.0" },
"activationEvents": [
"onLanguage:python",
"onLanguage:javascript",
"onLanguage:typescript",
"onCommand:ai-review.reviewFile"
],
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "ai-review.reviewFile",
"title": "AI Review: 审查当前文件",
"category": "AI Review"
},
{
"command": "ai-review.reviewSelection",
"title": "AI Review: 审查选中代码",
"category": "AI Review"
},
{
"command": "ai-review.reviewDiff",
"title": "AI Review: 审查 Git 变更",
"category": "AI Review"
},
{
"command": "ai-review.explainCode",
"title": "AI Review: 解释代码",
"category": "AI Review"
},
{
"command": "ai-review.generateTest",
"title": "AI Review: 生成测试",
"category": "AI Review"
},
{
"command": "ai-review.optimizeCode",
"title": "AI Review: 优化代码",
"category": "AI Review"
},
{
"command": "ai-review.fixAll",
"title": "AI Review: 一键修复全部",
"category": "AI Review"
}
],
"configuration": {
"title": "AI Code Review",
"properties": {
"ai-review.autoReview": {
"type": "boolean",
"default": false,
"description": "编辑时自动审查"
},
"ai-review.enableAI": {
"type": "boolean",
"default": true,
"description": "启用 AI 审查"
},
"ai-review.enableRules": {
"type": "boolean",
"default": true,
"description": "启用规则引擎"
},
"ai-review.llm.provider": {
"type": "string",
"enum": ["openai", "anthropic", "local", "azure"],
"default": "openai",
"description": "LLM 提供商"
},
"ai-review.llm.model": {
"type": "string",
"default": "gpt-4",
"description": "模型名称"
},
"ai-review.llm.apiKey": {
"type": "string",
"description": "API Key"
},
"ai-review.debounceMs": {
"type": "number",
"default": 2000,
"description": "实时审查防抖(毫秒)"
}
}
},
"keybindings": [
{
"command": "ai-review.reviewFile",
"key": "ctrl+shift+r",
"mac": "cmd+shift+r"
},
{
"command": "ai-review.reviewSelection",
"key": "ctrl+shift+alt+r",
"mac": "cmd+shift+alt+r"
},
{
"command": "ai-review.explainCode",
"key": "ctrl+shift+e",
"mac": "cmd+shift+e"
}
]
}
}
八、总结
架构全景
| 层 | 组件 | 职责 |
|---|---|---|
| 入口层 | AIReviewExtension |
VSCode 生命周期、命令注册、状态栏 |
| 服务层 | AIReviewEngine |
双引擎审查、去重、缓存、限流 |
| 规则引擎 | RuleEngine |
可编程规则、安全/性能/复杂度检测 |
| LLM 层 | LLMClient |
多模型适配、结构化输出 |
| 提供者层 | DiagnosticProvider |
诊断显示、保存/输入触发 |
| 提供者层 | CodeActionProvider |
快速修复建议 |
| 提供者层 | HoverProvider |
代码解释 |
最佳实践
| 实践 | 说明 |
|---|---|
| 双引擎 | 规则引擎毫秒级 + AI 秒级,互补增强 |
| 去重合并 | 规则和 AI 可能发现同一问题,保留高置信度 |
| 限流控制 | 避免过度调用 LLM API |
| 缓存结果 | 相同代码不重复审查 |
| 团队规范 | 自定义规则支持,学习代码风格 |
| 零配置 | 开箱即用,按需深度配置 |
本文涵盖 AI 代码审查 VSCode 插件完整技术栈:架构设计、双引擎审查、可编程规则、多模型 LLM 适配、编辑器诊断集成、Git Diff 审查、配置管理、团队规范。