feat: 规则预过滤 + 自定义规则去重增强 + 导入预览编辑 + Word/PPT 导入 + UI 优化
- 规则预过滤:根据文件语言注入匹配的自定义规则,跳过不相关规则 - 导入去重:对比已有规则,重复项自动注释/标注 - 导入预览编辑:支持在导入前编辑规则字段(severity、description 等) - Word/PPT 导入:新增 DocxConverter、PptxConverter - UI:审查报告移除设置按钮;自定义规则标签显示注入比例
This commit is contained in:
@@ -2,6 +2,7 @@ import * as vscode from 'vscode';
|
||||
import { Orchestrator } from '../orchestrator/orchestrator';
|
||||
import { runAIReview } from '../ai/engine';
|
||||
import { loadActiveRules } from '../rules/yaml-parser';
|
||||
import { filterAndSummarize } from '../rules/rule-filter';
|
||||
import { mergeResults, MergedReport } from '../merger/merger';
|
||||
import { reportToMarkdown } from '../utils/report';
|
||||
import { getApiKey } from '../config';
|
||||
@@ -38,9 +39,10 @@ export function registerCommands(
|
||||
|
||||
progress.report({ message: '运行 AI 审查...' });
|
||||
|
||||
const customRules = loadActiveRules(workspaceRoot);
|
||||
const allRules = loadActiveRules(workspaceRoot);
|
||||
const filterResult = filterAndSummarize(allRules, document);
|
||||
const code = document.getText();
|
||||
const aiResult = await runAIReview(context, code, staticResult.diagnostics, customRules);
|
||||
const aiResult = await runAIReview(context, code, staticResult.diagnostics, filterResult.relevant);
|
||||
|
||||
currentReport = mergeResults({
|
||||
staticDiagnostics: staticResult.diagnostics,
|
||||
@@ -53,6 +55,12 @@ export function registerCommands(
|
||||
filePath: document.uri.fsPath,
|
||||
language: document.languageId,
|
||||
adapterIds: staticResult.adapterIds,
|
||||
customRuleFilterInfo: {
|
||||
totalActive: allRules.length,
|
||||
injected: filterResult.relevant.length,
|
||||
filteredOut: filterResult.filteredOut.length,
|
||||
skippedRequestA: filterResult.skippedRequestA,
|
||||
},
|
||||
});
|
||||
|
||||
const panel = ReviewPanel.createOrShow(context.extensionUri);
|
||||
|
||||
@@ -18,6 +18,12 @@ export interface MergedReport {
|
||||
adapterNames: string[];
|
||||
fixableLinterIndices: number[];
|
||||
fixableCustomIndices: number[];
|
||||
customRuleFilterInfo?: {
|
||||
totalActive: number;
|
||||
injected: number;
|
||||
filteredOut: number;
|
||||
skippedRequestA: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface MergeInput {
|
||||
@@ -31,6 +37,12 @@ interface MergeInput {
|
||||
filePath: string;
|
||||
language: string;
|
||||
adapterIds: string[];
|
||||
customRuleFilterInfo?: {
|
||||
totalActive: number;
|
||||
injected: number;
|
||||
filteredOut: number;
|
||||
skippedRequestA: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export function mergeResults(input: MergeInput): MergedReport {
|
||||
@@ -74,5 +86,6 @@ export function mergeResults(input: MergeInput): MergedReport {
|
||||
adapterNames: input.adapterIds,
|
||||
fixableLinterIndices,
|
||||
fixableCustomIndices,
|
||||
customRuleFilterInfo: input.customRuleFilterInfo,
|
||||
};
|
||||
}
|
||||
|
||||
+16
-7
@@ -2,7 +2,7 @@ import * as vscode from 'vscode';
|
||||
import { MergedReport } from '../merger/merger';
|
||||
|
||||
interface PanelMessage {
|
||||
type: 'navigate' | 'rerun' | 'export' | 'settings' | 'fix' | 'fixAll';
|
||||
type: 'navigate' | 'rerun' | 'export' | 'fix' | 'fixAll';
|
||||
line?: number;
|
||||
ruleId?: string;
|
||||
source?: 'linter' | 'custom' | 'ai';
|
||||
@@ -118,6 +118,11 @@ export class ReviewPanel {
|
||||
|
||||
const linterToolName = report.adapterNames.length > 0 ? report.adapterNames.join(' + ') : '静态分析';
|
||||
|
||||
const customFilterInfo = report.customRuleFilterInfo;
|
||||
const customFilterLabel = customFilterInfo
|
||||
? `(注入 ${customFilterInfo.injected}/${customFilterInfo.totalActive} 条)`
|
||||
: '';
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
@@ -235,7 +240,7 @@ ${errorBox}
|
||||
|
||||
<div class="tab-bar">
|
||||
<button class="tab active" data-tab="linter" onclick="switchTab('linter')">🔧 ${report.adapterNames.length > 0 ? report.adapterNames.join(' + ') : '静态分析'} ${tabCount(linterErrors, linterWarnings, linterInfos)}</button>
|
||||
<button class="tab" data-tab="custom" onclick="switchTab('custom')">📋 自定义规则 ${tabCount(customErrors, customWarnings, customInfos)}</button>
|
||||
<button class="tab" data-tab="custom" onclick="switchTab('custom')">📋 自定义规则 ${tabCount(customErrors, customWarnings, customInfos)} <span style="font-size:11px;color:var(--vscode-descriptionForeground);">${customFilterLabel}</span></button>
|
||||
<button class="tab" data-tab="ai" onclick="switchTab('ai')">🤖 AI 审查 ${tabCount(aiErrors, aiWarnings, aiInfos)}</button>
|
||||
</div>
|
||||
|
||||
@@ -252,7 +257,7 @@ ${errorBox}
|
||||
<div class="actions">
|
||||
<button class="btn btn-primary" onclick="send('rerun')">🔄 重新审查</button>
|
||||
<button class="btn" onclick="send('export')">📄 导出报告</button>
|
||||
<button class="btn" onclick="send('settings')">⚙️ 设置</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -287,11 +292,18 @@ ${errorBox}
|
||||
}
|
||||
|
||||
private buildCustomList(report: MergedReport, fixableSet: Set<number>): string {
|
||||
const filterInfo = report.customRuleFilterInfo;
|
||||
if (filterInfo?.skippedRequestA) {
|
||||
return '<div class="empty">当前文件语言无匹配的自定义规则,已跳过规则评估</div>';
|
||||
}
|
||||
if (report.customRuleDiagnostics.length === 0) {
|
||||
return '<div class="empty">未发现规则违规</div>';
|
||||
}
|
||||
const hasFixable = fixableSet.size > 0;
|
||||
return `<div class="section-header"><span class="section-header-title">自定义规则 · ${report.customRuleCount} 个问题</span>${hasFixable ? '<button class="btn" onclick="send(\'fixAll\')">全部修复</button>' : ''}</div>`
|
||||
const filterLabel = filterInfo
|
||||
? `(注入 ${filterInfo.injected}/${filterInfo.totalActive} 条规则)`
|
||||
: '';
|
||||
return `<div class="section-header"><span class="section-header-title">自定义规则 · ${report.customRuleCount} 个问题${filterLabel}</span>${hasFixable ? '<button class="btn" onclick="send(\'fixAll\')">全部修复</button>' : ''}</div>`
|
||||
+ report.customRuleDiagnostics.map((d, i) => this.buildIssueItem(d.severity, d.ruleId, d.message, d.range.start.line, 'custom', d.suggestion, fixableSet.has(i), undefined, false)).join('');
|
||||
}
|
||||
|
||||
@@ -377,9 +389,6 @@ ${errorBox}
|
||||
case 'export':
|
||||
vscode.commands.executeCommand('codeReviewer.exportReport');
|
||||
break;
|
||||
case 'settings':
|
||||
vscode.commands.executeCommand('codeReviewer.openSetup');
|
||||
break;
|
||||
case 'fix':
|
||||
vscode.commands.executeCommand('codeReviewer.fixIssue', message);
|
||||
break;
|
||||
|
||||
@@ -8,6 +8,7 @@ interface RuleYamlItem {
|
||||
description: string;
|
||||
message: string;
|
||||
languages?: string[];
|
||||
excludeLanguages?: string[];
|
||||
}
|
||||
|
||||
function parseYamlSimple(content: string): object[] {
|
||||
@@ -75,6 +76,7 @@ export function loadActiveRules(workspaceRoot: string): CustomRule[] {
|
||||
description: item.description,
|
||||
message: item.message,
|
||||
languages: item.languages,
|
||||
excludeLanguages: item.excludeLanguages,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ export interface CustomRule {
|
||||
description: string;
|
||||
message: string;
|
||||
languages?: string[];
|
||||
excludeLanguages?: string[];
|
||||
}
|
||||
|
||||
export type Severity = 'error' | 'warning' | 'info';
|
||||
|
||||
+31
-2
@@ -6,10 +6,13 @@ import { getApiKey, setApiKey } from '../config/secret';
|
||||
import { createProvider, getAllProviderMeta, getProviderModels } from '../ai/factory';
|
||||
import { listRuleFiles } from '../rules/yaml-parser';
|
||||
import { ImportService } from '../rules/import-service';
|
||||
import { showImportPreview } from '../rules/import-preview';
|
||||
import { YamlConverter } from '../rules/converters/yaml-converter';
|
||||
import { MdConverter } from '../rules/converters/md-converter';
|
||||
import { TxtConverter } from '../rules/converters/txt-converter';
|
||||
import { ExcelConverter } from '../rules/converters/excel-converter';
|
||||
import { DocxConverter } from '../rules/converters/docx-converter';
|
||||
import { PptxConverter } from '../rules/converters/pptx-converter';
|
||||
|
||||
const languageLabels: Record<string, string> = {
|
||||
'zh-CN': '中文(简体)',
|
||||
@@ -28,6 +31,8 @@ export class SetupViewProvider implements vscode.WebviewViewProvider {
|
||||
this.importService.registerConverter(new MdConverter());
|
||||
this.importService.registerConverter(new TxtConverter());
|
||||
this.importService.registerConverter(new ExcelConverter());
|
||||
this.importService.registerConverter(new DocxConverter());
|
||||
this.importService.registerConverter(new PptxConverter());
|
||||
}
|
||||
|
||||
resolveWebviewView(
|
||||
@@ -187,11 +192,12 @@ export class SetupViewProvider implements vscode.WebviewViewProvider {
|
||||
const result = await vscode.window.showOpenDialog({
|
||||
canSelectMany: false,
|
||||
openLabel: '选择规则文件',
|
||||
filters: { '规则文件': ['yaml', 'yml', 'md', 'txt', 'xlsx', 'xls'] },
|
||||
filters: { '规则文件': ['yaml', 'yml', 'md', 'txt', 'xlsx', 'xls', 'docx', 'pptx'] },
|
||||
});
|
||||
if (!result || result.length === 0) { return; }
|
||||
|
||||
const srcPath = result[0].fsPath;
|
||||
const ext = path.extname(srcPath).toLowerCase();
|
||||
|
||||
const rulesDir = path.join(workspaceRoot, '.code-review', 'rules');
|
||||
if (!fs.existsSync(rulesDir)) {
|
||||
@@ -206,7 +212,30 @@ export class SetupViewProvider implements vscode.WebviewViewProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.importService.convert(srcPath, yamlPath, this.context);
|
||||
if (ext === '.yaml' || ext === '.yml') {
|
||||
fs.copyFileSync(srcPath, yamlPath);
|
||||
vscode.window.showInformationMessage(`规则文件已导入: ${yamlFileName}`);
|
||||
} else {
|
||||
try {
|
||||
const conversion = await this.importService.convert(srcPath, this.context);
|
||||
|
||||
const decision = await showImportPreview(conversion);
|
||||
if (!decision || !decision.confirmed) {
|
||||
vscode.window.showInformationMessage('导入已取消');
|
||||
return;
|
||||
}
|
||||
|
||||
this.importService.applyConversion(conversion, decision, yamlPath);
|
||||
vscode.window.showInformationMessage(
|
||||
`规则已导入: ${yamlFileName}(${conversion.rules.length} 条,` +
|
||||
`${conversion.exactCount} 条完全重复已注释,` +
|
||||
`${conversion.overlapCount} 条部分重叠已标注)`
|
||||
);
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
vscode.window.showErrorMessage(`规则导入失败: ${msg}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async resetConfig(): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user