- 适配器 i18n 接入(eslint/pmd/sql-lint/stylelint) - Provider 动态注册机制(registry.ts + providers.json + factory 重构) - SetupView 全面重构(setupView.ts 新增 600+ 行) - i18n 消息扩展(messages.ts +210 行) - 规则导入流程优化(import-service / prompt-builder) - 新增 PMD jars 依赖及测试用例
11 KiB
11 KiB
规则导入预览编辑设计
适用项目:vscode-code-reviewer 设计日期:2026-07-25 参考文档:
docs/superpowers/specs/2026-07-23-rule-prefilter-design.mdPart B(导入去重)
1. 现状
import-preview.ts 中预览 Webview 面板仅支持:
- 按 exact/overlap/none 三区展示规则卡片
- overlap 规则可切换「保留/注释」
- exact 规则有「恢复」按钮
- 无字段编辑能力
用户确认后,buildFinalYaml 对原始 yamlContent 做行级操作(加/去 # 前缀)。编辑后的字段值无法传入。
2. 目标
导入预览中支持:
- 每条规则卡片默认证折叠,点击展开完整编辑表单
- 可编辑字段:severity(下拉框)、description(多行文本)、message(多行文本)、languages(标签式输入)、excludeLanguages(标签式输入)
- id 只读显示(不可编辑)
- 所有规则类型(exact/overlap/none)均可编辑
- 展开表单右上角显示「保留/注释」切换,两种状态下都可编辑字段
- 确认后基于修改后的 rules 数组直接生成最终 YAML 写盘
3. 数据流变更
现状:
importService.convert() → ConversionResult → showImportPreview() → PreviewDecision{keepRule}
↓
buildFinalYaml(操作原始 yamlContent 行)
改造后:
importService.convert() → ConversionResult → showImportPreview() → PreviewDecision{keepRule, editedRules}
↓
renderRulesToYaml(从 rules 数组生成新 YAML)
关键变化:写盘不再基于原始 yamlContent 文本行操作,而是从(可能被用户编辑过的)rules 数组渲染出完整 YAML。
4. 数据结构变更
4.1 PreviewDecision 扩展
// src/rules/import-types.ts
export interface PreviewDecision {
keepRule: Record<string, boolean>;
confirmed: boolean;
editedRules?: ImportableRule[]; // 用户编辑后的完整规则数组(含未修改的规则)
}
editedRules 为 undefined 表示用户未做任何字段修改,回退到原始 rules。非 undefined 时用于生成最终 YAML。
5. 预览 Webview 重设计
5.1 卡片结构
┌─────────────────────────────────────────────┐
│ no-console-log [▼ 展开] │ ← 折叠态:id + severity 标签 + 描述摘要
│ ⚠ warning │
│ 禁止在 console.log 中输出敏感信息 │
└─────────────────────────────────────────────┘
展开态: ← 点击卡片任意位置展开
┌─────────────────────────────────────────────┐
│ no-console-log [保留] [注释] │ ← 顶部:id + 保留/注释切换
├─────────────────────────────────────────────┤
│ severity │
│ [▼ error ▼] ← 下拉框 │
│ │
│ description │
│ ┌─────────────────────────────────────────┐│
│ │ 禁止在 console.log 中输出敏感信息 ││ ← textarea
│ │ ││
│ └─────────────────────────────────────────┘│
│ │
│ message │
│ ┌─────────────────────────────────────────┐│
│ │ 检测到敏感信息输出到 console,请移除 ││ ← textarea
│ │ ││
│ └─────────────────────────────────────────┘│
│ │
│ languages │
│ [java ×] [typescript ×] [▌ ] │ ← 标签式输入
│ │
│ excludeLanguages │
│ [▌ ] │ ← 标签式输入
└─────────────────────────────────────────────┘
5.2 交互行为
| 操作 | 行为 |
|---|---|
| 点击折叠卡片 | 展开表单,其余卡片不受影响 |
| 展开状态下再次点击顶部 | 折叠回摘要 |
| 修改字段 | 实时保存在前端 modifiedRules map 中 |
| 切换保留/注释 | 更新 keepRule,不影响已编辑的字段值 |
| 点「确认导入」 | 发送 confirm 消息 + 全部修改后的 rules 数据 |
| 点「取消」 | 不写盘 |
5.3 标签式输入实现
languages / excludeLanguages 使用纯 HTML/CSS/JS 实现:
- 文本输入框 + 已添加标签的行内显示
- 输入语言名后按
Enter或,添加为标签 - 标签显示为 chip 样式,右侧
×按钮删除 - 去重(同名不重复添加)
- 支持粘贴逗号分隔列表
5.4 字段校验(确认时)
| 字段 | 规则 |
|---|---|
| severity | 必须是 error / warning / info 之一(下拉框天然保证) |
| description | 非空,trim() 后长度 > 0 |
| message | 非空,trim() 后长度 > 0 |
| languages | 可选,每个值非空字符串 |
| excludeLanguages | 可选,每个值非空字符串 |
校验不通过时弹 vscode.window.showErrorMessage 提示具体字段名,不关闭面板。
6. YAML 生成函数
新增 renderRulesToYaml 替代原有行级操作逻辑:
// src/rules/import-service.ts
function renderRulesToYaml(
rules: ImportableRule[],
decision: PreviewDecision,
): string {
const lines: string[] = [];
for (const rule of rules) {
const keep = decision.keepRule[rule.id] ?? rule.duplicateLevel !== 'exact';
// 构造该规则的标准 YAML 行
const ruleLines: string[] = [];
ruleLines.push(`- id: ${rule.id}`);
ruleLines.push(` severity: ${rule.severity}`);
ruleLines.push(` description: ${rule.description}`);
ruleLines.push(` message: ${rule.message}`);
if (rule.languages && rule.languages.length > 0) {
ruleLines.push(` languages: [${rule.languages.join(', ')}]`);
}
if (rule.excludeLanguages && rule.excludeLanguages.length > 0) {
ruleLines.push(` excludeLanguages: [${rule.excludeLanguages.join(', ')}]`);
}
if (keep) {
lines.push(...ruleLines);
} else {
// 注释化:加注释头 + 每行加 #
const dupLevel = rule.duplicateLevel ?? 'none';
const dupOf = rule.duplicateOf ?? 'manual';
if (dupLevel === 'exact') {
lines.push(`# [DUPLICATE: exact] 重复 ${dupOf}(检测目标完全一致)`);
} else if (dupLevel === 'overlap') {
lines.push(`# [DUPLICATE: overlap] 与 ${dupOf} 部分重叠`);
if (rule.duplicateReason) {
lines.push(`# 重叠原因:${rule.duplicateReason}`);
}
} else {
lines.push(`# [手动注释] 用户选择不启用此规则`);
}
lines.push(`# 如需启用,删除以下每行开头的 # 即可`);
for (const rl of ruleLines) {
lines.push(`# ${rl}`);
}
}
lines.push(''); // 规则间空行
}
return lines.join('\n');
}
buildFinalYaml 改为判断入口:
export function buildFinalYaml(
yamlContent: string,
rules: ImportableRule[],
decision: PreviewDecision,
): string {
if (decision.editedRules && decision.editedRules.length > 0) {
// 用户编辑过字段,从编辑后的 rules 渲染
return renderRulesToYaml(decision.editedRules, decision);
}
// 无字段编辑,使用原始 yamlContent(保持向后兼容)
return buildFinalYamlFromRaw(yamlContent, rules, decision);
}
// 原 buildFinalYaml 逻辑重命名为 buildFinalYamlFromRaw
7. 前端消息协议扩展
// 新增消息类型
interface UpdateRuleMessage {
type: 'updateRule';
ruleId: string;
rule: ImportableRule; // 该规则的完整最新字段值
}
// confirm 消息扩展:确认时携带编辑数据
interface ConfirmMessage {
type: 'confirm';
editedRules?: ImportableRule[]; // 附加全部规则的最新字段值
}
8. 文件变更清单
| 文件 | 操作 | 内容 |
|---|---|---|
src/rules/import-types.ts |
修改 | PreviewDecision 新增 editedRules?: ImportableRule[] |
src/rules/import-preview.ts |
重写 | 可折叠卡片 + 展开编辑表单 + 校验 + 传递编辑数据 |
src/rules/import-service.ts |
修改 | 新增 renderRulesToYaml,buildFinalYaml 做入口判断 |
src/views/setupView.ts |
不改 | applyConversion 调用不变(PreviewDecision 接口向后兼容) |
src/test/import-dedup.test.ts |
修改 | 补充编辑后生成 YAML 的测试用例 |
9. 测试用例
追加到 src/test/import-dedup.test.ts:
| # | 用例 | 输入 | 预期 |
|---|---|---|---|
| 13 | 编辑 description 后确认 | rule 的 description 被修改为新值 | 最终 YAML 中该规则的 description 为新值 |
| 14 | 编辑 severity 后确认 | rule 的 severity 从 warning 改为 error | 最终 YAML 中 severity 为 error |
| 15 | 编辑 languages 后确认 | 添加 javascript 到 languages | 最终 YAML 含 languages: [javascript] |
| 16 | 编辑后切换为注释 | 编辑字段后 toggle 为注释 | 规则被注释,注释内容为编辑后的值 |
| 17 | 无编辑场景回退 | editedRules 为 undefined |
行为与当前 buildFinalYamlFromRaw 一致 |
10. 实施顺序
import-types.ts— PreviewDecision 扩展import-service.ts— 新增renderRulesToYaml,改造buildFinalYamlimport-preview.ts— 重写 Webview(卡片展开 + 编辑表单 + 校验 + 传递编辑数据)import-dedup.test.ts— 补充测试
11. 设计决策记录
| 决策项 | 选择 | 理由 |
|---|---|---|
| 编辑数据传递方式 | confirm 时携带 editedRules 数组 | 无需逐字段实时回传,减少消息往返 |
| 无编辑时行为 | 回退到原始 yamlContent 行操作 | 保证未编辑场景零变化、零风险 |
| languages 输入 | 标签式 chip 输入(Enter/, 添加) | 比逗号分隔文本框更直观,防格式错误 |
| 展开方式 | 点击卡片切换展开/折叠 | 简单直接,不增加额外按钮 |
| 保留/注释与编辑 | 共存,互不影响 | 出用户需求:保留/注释状态不影响字段编辑 |