Files
2026Technology-Competition/docs/superpowers/specs/2026-07-25-import-preview-edit-design.md
T
范智鹏 effcf30802 feat: 适配器 i18n + Provider 动态注册 + SetupView 重构 + jars 资源
- 适配器 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 依赖及测试用例
2026-07-28 22:57:15 +08:00

264 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 规则导入预览编辑设计
> 适用项目: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<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` 替代原有行级操作逻辑:
```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/, 添加) | 比逗号分隔文本框更直观,防格式错误 |
| 展开方式 | 点击卡片切换展开/折叠 | 简单直接,不增加额外按钮 |
| 保留/注释与编辑 | 共存,互不影响 | 出用户需求:保留/注释状态不影响字段编辑 |