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

245 lines
9.2 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.
# 导入预览 · 错误规则字段级报红设计书
> 版本: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` 错误分支改造
1. **卡片样式还原**:去掉 `opacity:0.7` 与红边框,`cardStyle` 仅保留错误徽标。
2. **去掉顶部 issues 横幅**:删除 `issuesHtml` 变量及其渲染。
3. **字段级渲染**:渲染各 `edit-field` 时,若 `validationIssues` 中存在对应 `field`,给该 `edit-field` 追加:
```html
<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` 返回字段
```ts
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)`
```js
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`
```js
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` 清理
卡片移入有效分区后,清除该卡全部字段级错误:
```js
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
```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 事件 → 修复后红框清除