- 方法级审查: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 + 行号排序
9.2 KiB
9.2 KiB
导入预览 · 错误规则字段级报红设计书
版本: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.ts、import-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 错误分支改造
- 卡片样式还原:去掉
opacity:0.7与红边框,cardStyle仅保留错误徽标。 - 去掉顶部 issues 横幅:删除
issuesHtml变量及其渲染。 - 字段级渲染:渲染各
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-field加field-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 上委托监听 input 与 change:
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 消息带 field;setFieldError/clearFieldError/clearCardFieldErrors/fieldElement;input/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 事件 → 修复后红框清除
- 初始错误卡片:description/message 空 → 对应文本域带