# 规则导入预览编辑设计 > 适用项目:vscode-code-reviewer > 设计日期:2026-07-25 > 参考文档:`docs/superpowers/specs/2026-07-23-rule-prefilter-design.md` Part 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 扩展 ```typescript // src/rules/import-types.ts export interface PreviewDecision { keepRule: Record; 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` 替代原有行级操作逻辑: ```typescript // 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` 改为判断入口: ```typescript 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. 前端消息协议扩展 ```typescript // 新增消息类型 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. 实施顺序 1. `import-types.ts` — PreviewDecision 扩展 2. `import-service.ts` — 新增 `renderRulesToYaml`,改造 `buildFinalYaml` 3. `import-preview.ts` — 重写 Webview(卡片展开 + 编辑表单 + 校验 + 传递编辑数据) 4. `import-dedup.test.ts` — 补充测试 ## 11. 设计决策记录 | 决策项 | 选择 | 理由 | |--------|------|------| | 编辑数据传递方式 | confirm 时携带 editedRules 数组 | 无需逐字段实时回传,减少消息往返 | | 无编辑时行为 | 回退到原始 yamlContent 行操作 | 保证未编辑场景零变化、零风险 | | languages 输入 | 标签式 chip 输入(Enter/, 添加) | 比逗号分隔文本框更直观,防格式错误 | | 展开方式 | 点击卡片切换展开/折叠 | 简单直接,不增加额外按钮 | | 保留/注释与编辑 | 共存,互不影响 | 出用户需求:保留/注释状态不影响字段编辑 |