Files
2026Technology-Competition/docs/superpowers/specs/2026-07-31-import-field-error-design.md
T
范智鹏 247f19fd44 feat: 方法级代码审查 + 模板导入/预览增强 + SQLFluff 方言 + AI 空响应报错修复
- 方法级审查:CodeLens 触发 + 单次 AI 调用(规则匹配 + 6 维度深度审查),新增 method-extractor / status-cache / codeLensProvider
- 模板导入:severity 保留原始值 + 占位 id、去重对照统一 known-rules、重复提示条双语翻译、箭头展开/折叠 UI、520 条静态规则补 zh/ja 翻译
- SQL:sql-lint 重命名 sqlfluff + sqlfluff.dialect 方言可配置 + 默认方言调整
- ESLint:v9 flat config 接线修复(overrideConfigFile)+ legacy 迁移提示
- AI:空响应 EmptyContentError + 重试一次 + max_tokens 截断专用报错
- JSP:整文件检查走 PMD JSP 规则集 + scriptlet 包装解析 + 行号映射
- 诊断按 severity + 行号排序
2026-08-03 22:53:20 +08:00

9.2 KiB
Raw Blame History

导入预览 · 错误规则字段级报红设计书

版本:v1.0 日期:2026-07-31 适用项目:vscode-code-reviewer 参考文档:2026-07-31-import-error-rule-edit-design.md(错误规则可编辑 + 添加流程)


一、方案概览

1.1 目标

导入预览中的错误规则卡片,报红从「整卡红边 + 半透明 + 顶部 issues 横幅」改为字段级报红

  • 整卡恢复普通样式
  • 仅出错的输入框加红框高亮,错误原因文字显示在该字段下方
  • 用户修复字段时实时清除该字段的红框与提示
  • 「添加」失败与 id 冲突同样精确定位到具体字段

1.2 现状 → 改造

现状:
  ┌─ 规则卡片(opacity:0.7 + 红边框)─────────────┐
  │  [需修复后点击添加]                            │
  │  ⚠ description 为空                            │  ← 顶部 issues 横幅
  │  ⚠ message 为空                                │
  │  [id] [severity] [description] [message] ...   │  ← 全部无高亮
  └────────────────────────────────────────────────┘

改造后:
  ┌─ 规则卡片(普通样式)─────────────────────────┐
  │  [需修复后点击添加]                            │
  │  [id]                                         │
  │  [severity]                                   │
  │  [description]      ← 红框                    │
  │    ⚠ description 为空                          │  ← 字段下方提示
  │  [message]          ← 红框                    │
  │    ⚠ message 为空                              │
  └────────────────────────────────────────────────┘

1.3 范围

类型 内容
初始错误字段红框 + 字段下方原因;添加失败/id 冲突定位到字段;实时清除;卡片样式还原
不含 新增 languages/excludeLanguages 校验(维持现状);错误卡片以外的样式改动
不触碰 import-service.tsimport-types.ts、i18n 结构、扩展端去重流程

二、详细实现

改动文件src/rules/import-preview.ts(前端渲染 + JS + 少量 CSS,扩展端消息协议兼容扩展)。

2.1 字段 → 表单元素映射

field 表单元素
id .id-display-input
severity .edit-field select
description .edit-field textarea(第 1 个)
message .edit-field textarea(第 2 个)

2.2 renderRuleCard 错误分支改造

  1. 卡片样式还原:去掉 opacity:0.7 与红边框,cardStyle 仅保留错误徽标。
  2. 去掉顶部 issues 横幅:删除 issuesHtml 变量及其渲染。
  3. 字段级渲染:渲染各 edit-field 时,若 validationIssues 中存在对应 field,给该 edit-field 追加:
<div class="edit-field field-error">
  <label>description(规则描述)</label>
  <textarea rows="2" ...>...</textarea>
  <div class="field-error-msg">⚠ description 为空</div>
</div>
  • edit-fieldfield-error 类;输入控件本身加 field-error-input
  • 提示文字 <div class="field-error-msg">⚠ {message}</div> 插在输入控件之后、edit-field 内部末尾

实现方式:渲染前构建 const issueByField = new Map((rule.validationIssues||[]).map(i => [i.field, i]));渲染 severity/description/message 三个字段时按 map 命中追加。

2.3 validateRule 返回字段

function validateRule(rule: ImportableRule): {
  field: 'id' | 'severity' | 'description' | 'message';
  message: string;
} | null {
  if (!rule.id || !rule.id.trim()) {
    return { field: 'id', message: t('import.validationIdEmpty') };
  }
  if (!['error', 'warning', 'info'].includes(rule.severity)) {
    return { field: 'severity', message: t('import.validationSeverityInvalid') };
  }
  if (!rule.description || !rule.description.trim()) {
    return { field: 'description', message: t('import.validationDescEmpty', { 0: rule.id }) };
  }
  if (!rule.message || !rule.message.trim()) {
    return { field: 'message', message: t('import.validationMsgEmpty', { 0: rule.id }) };
  }
  return null;
}

handleAddErrorRule 中:

  • validateRule 失败 → postMessage({ type:'addError', ruleId, field, message })
  • id 冲突 → postMessage({ type:'addError', ruleId, field:'id', message: t('import.idConflict', ...) })

2.4 前端消息处理改造

showCardError(ruleId, message)showCardError(ruleId, field, message)

function setFieldError(card, field, message) {
  const el = fieldElement(card, field);
  if (!el) return;
  el.classList.add('field-error-input');
  const wrap = el.closest('.edit-field');
  if (!wrap) return;
  wrap.classList.add('field-error');
  let msg = wrap.querySelector('.field-error-msg');
  if (!msg) {
    msg = document.createElement('div');
    msg.className = 'field-error-msg';
    wrap.appendChild(msg);
  }
  msg.textContent = '⚠ ' + message;
}

function clearFieldError(card, field) {
  const el = fieldElement(card, field);
  if (!el) return;
  el.classList.remove('field-error-input');
  const wrap = el.closest('.edit-field');
  if (wrap) {
    wrap.classList.remove('field-error');
    const msg = wrap.querySelector('.field-error-msg');
    if (msg) msg.remove();
  }
}

function fieldElement(card, field) {
  if (field === 'id') return card.querySelector('.id-display-input');
  if (field === 'severity') return card.querySelector('.edit-field select');
  const tas = card.querySelectorAll('.edit-field textarea');
  return field === 'description' ? (tas[0] || null) : (tas[1] || null);
}

window.addEventListener('message')addError 分支改为透传 field

2.5 实时清除(事件委托)

document 上委托监听 inputchange

function liveClear(event) {
  const card = event.target.closest('.rule-card');
  if (!card || !card.hasAttribute('data-error')) return;
  const target = event.target;
  if (target.classList.contains('id-display-input') || target.classList.contains('rule-id-input')) {
    if (target.value.trim()) clearFieldError(card, 'id');
  } else if (target.tagName === 'SELECT') {
    clearFieldError(card, 'severity');
  } else if (target.tagName === 'TEXTAREA') {
    const tas = card.querySelectorAll('.edit-field textarea');
    const field = tas[0] === target ? 'description' : (tas[1] === target ? 'message' : null);
    if (field && target.value.trim()) clearFieldError(card, field);
  }
}
document.addEventListener('input', liveClear);
document.addEventListener('change', liveClear);

severity 下拉天然只会给出合法值,故 change 即清除;id/description/message 以非空 trim 判定。

2.6 moveCardToSection 清理

卡片移入有效分区后,清除该卡全部字段级错误:

function clearCardFieldErrors(card) {
  card.querySelectorAll('.field-error-input').forEach(el => {
    el.classList.remove('field-error-input');
  });
  card.querySelectorAll('.field-error').forEach(wrap => {
    wrap.classList.remove('field-error');
    const msg = wrap.querySelector('.field-error-msg');
    if (msg) msg.remove();
  });
}

在移除 data-error 后调用。

2.7 CSS

.field-error-input {
  border-color: rgba(248,81,73,0.7) !important;
  box-shadow: 0 0 0 1px rgba(248,81,73,0.25);
}
.field-error-msg {
  color: #f48771; font-size: 11px; margin-top: 4px;
}

删除不再使用的 .error-issues 规则(或保留无引用,推荐删除)。


三、边界与影响

边界 处理
同一卡片多字段出错 每个字段独立红框 + 独立提示,互不影响
字段修复后再点「添加」 校验通过即入区;实时清除逻辑保证先显示绿色状态
添加失败(未修复) 红框/提示重新命中对应字段
id 冲突 field:'id' 定位到 id 输入框
severity 原始非法但默认值为合法 红框+提示展示原始问题,下拉 change 即清除
确认导入校验(validate() 仍跳过 data-error 卡片,行为不变
非模板导入路径 validationIssues,不受影响

四、文件变更清单

文件 操作 内容
src/rules/import-preview.ts 修改 卡片样式还原;字段级错误渲染;validateRule 返回字段;addError 消息带 fieldsetFieldError/clearFieldError/clearCardFieldErrors/fieldElementinput/change 委托实时清除;moveCardToSection 清理;CSS .field-error-*

i18n、import-service、import-types 均不改动。

五、验证

  • npm run lint + npm run compile
  • 复用既有 harness 思路,mock vscode 渲染 webview 脚本并校验语法
  • 扩展端消息流验证:
    • 初始错误卡片:description/message 空 → 对应文本域带 field-error-input,无整卡红边、无顶部横幅
    • 添加失败(description 空)→ addError 消息带 field:'description'
    • id 冲突 → addError 消息带 field:'id'
    • 模拟 input 事件 → 修复后红框清除