Files
2026Technology-Competition/docs/superpowers/specs/2026-08-14-fixer-refactor-design.md
T
范智鹏 3d8119d9c9 feat: 自动修复重构与审查面板交互修复 + 静态分析 AI 翻译配对 + SQLFluff 方言显示
- 自动修复:废弃 AI 修复,改 linter 原生 fix 多轮收敛;CodeAction hover + 面板修复/全部修复 + 快照 diff 撤销;hover 修复不入「已修复」列表、重新审查清空;修复/撤销后自动保存;单条修复只修目标问题(区间重叠收敛,不再连带相邻同规则)
- 审查面板:内联 JS 外部化(reviewPanel.js)修复 CSP 屏蔽导致的修复按钮/行号跳转/tab 失效;面板操作不依赖文件焦点(resolveFixDocument);行号跳转定位已打开编辑器,不在面板列新开副本
- 静态分析:translatedDiagnostics 规则 ID 归一化配对 + 深度审查 prompt 强化,静态分析条目显示中文翻译与逐条 AI 建议
- 波浪线:诊断补 source/code,hover 显示快速修复链接
- SQLFluff:设置面板方言徽章(显式/全局/项目/内置来源配色)
2026-08-18 21:41:51 +08:00

266 lines
12 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.
# 自动修复功能重构设计书
> 日期:2026-08-14
> 状态:待人类审批(Stage ④)
> 流程:① 用户提出 → ② 需求澄清 → ③ 方案设计
## 1. 背景与目标
现有 `src/fixer/fixer.ts` 基于 AI 生成修复文本,但未接线(`fixIssue`/`fixAll` 命令是空壳),且存在以下问题:
- AI 修复一次调用一次修复,命中率低、上下文小、无法批量收敛
- 面板 webview 已发出 `fix`/`fixAll` 消息但无响应
- 单条修复按钮文案错用「全部修复」;AI tab 误显示「全部修复」按钮
本次重构目标:基于 linter 原生 `--fix` 提取**单条 fix 对象**,多轮循环验证收敛,支持 CodeAction 内联修复与面板修复,采用快照 + diff 反向撤销,保证一次撤销回到修复前状态。
## 2. 需求澄清结论(Stage ② 共识)
| 决策项 | 结论 |
|--------|------|
| 交互入口 | 编辑器 hover 悬浮框修复链接(CodeAction QuickFix+ 审查面板修复按钮 + 一键修复全部 |
| 修复来源 | 仅 linter 静态诊断;PMD/SQLFluff/JSP 无单条 fix 能力则不显示修复入口 |
| 修复生成 | 优先 linter 原生 `--fix`,提取单条 fix 对象精确修复(ESLint/Stylelint |
| 修复策略 | 多轮循环:每轮修复后重跑 linter 验证,未消除继续修,轮次上限默认 3 可配置 |
| 批量修复 | 保留面板「一键修复全部」,逐条循环,修不了的跳过计数 |
| 面板撤销 | 每条修复独立记录 diff,撤销时当前文本定位后反向应用该条;可任意顺序撤销互不影响 |
| Ctrl+Z | 每个修复会话独立一次编辑提交,多轮循环中间态不入 undo 栈,按会话后进先出逐步回退 |
| 修复后刷新 | 自动重跑静态分析 → 波浪线更新/消失、CodeLens 重新获取、面板标记已修复(变绿+撤销按钮+不显示行号但记录行号)、未修复条目行号自动校正 |
## 3. 架构概览
废弃 AI 修复模块,改为 linter 原生 fix 驱动:
```
┌─ UI 层 ──────────────────────────────────────────────┐
│ FixCodeActionProvider (hover 修复链接) ReviewPanel │
└───────────────┬───────────────────────┬──────────────┘
│ CodeAction / fix命令 │ fix/undo 消息
┌───────────────▼───────────────────────▼──────────────┐
│ 核心层 FixEngine (src/fix/) │
│ fixDiagnostic() 多轮循环 内存模拟 → 单次提交 │
│ FixSessionManager 快照 + FixedEntry diff 栈 │
└──────┬───────────────────────────┬───────────────────┘
│ adapter.check(文本) │ linter fix 对象
┌──────▼───────────────────────────▼───────────────────┐
│ 适配层 LinterAdapter.check() 提取 fix 到 LinterFix │
│ ESLint(msg.fix) / Stylelint(w.fix) / PMD/SQLFluff ✗ │
└──────────────────────┬───────────────────────────────┘
┌──────────────────────▼───────────────────────────────┐
│ 基础层 Orchestrator(静态分析结果缓存) 配置 i18n │
└───────────────────────────────────────────────────────┘
```
**数据流**
1. `adapter.check()` 返回诊断 + `fix` 对象 → 存入 Orchestrator 静态分析结果缓存(按 uri)
2. 用户 hover 修复链接 / 面板点修复 → `codeReviewer.fixIssue` 命令 → `FixEngine.fixDiagnostic()`
3. 多轮循环在**内存字符串**上模拟(mockDocument),最终一次 `WorkspaceEdit` 全文替换提交 → undo 栈只有一次编辑
4. 成功记录 `FixedEntry` → 刷新:重跑静态分析 → markers 更新 → CodeLens 刷新 → 面板更新(已修复变绿/撤销按钮/无行号,未修复行号校正)
## 4. 文件变更清单
### 新建
| 文件 | 职责 |
|------|------|
| `src/fix/fixEngine.ts` | 多轮修复循环核心 |
| `src/fix/fixSession.ts` | 修复会话、FixedEntry diff 记录、撤销定位 |
| `src/fix/codeActionProvider.ts` | 编辑器 QuickFix CodeAction 提供者 |
| `src/utils/mockDocument.ts` | 从 jsp.ts 提取的 mockDocument(内存文本→TextDocument),供 fix 引擎复用 |
| `src/test/fixEngine.test.ts` | 修复引擎测试 |
| `src/test/fixSession.test.ts` | 会话撤销测试 |
### 修改
| 文件 | 变更 |
|------|------|
| `src/types.ts` | `LinterDiagnostic``fix?: LinterFix`;新增 `LinterFix` 接口 |
| `src/adapters/eslint.ts` | 提取 `msg.fix` 填充 fix 字段 |
| `src/adapters/stylelint.ts` | 提取 `w.fix` 填充 fix 字段 |
| `src/orchestrator/orchestrator.ts` | 增静态分析结果缓存(uri→diagnostics/adapter/workingDir |
| `src/activation/commands.ts` | 实现 `fixIssue`/`fixAll`,新增 `undoFix`review 更新缓存 |
| `src/extension.ts` | 注册 FixCodeActionProvider |
| `src/panel/webview.ts` | 修复状态渲染 + undo 消息 + 修正 fixAll 按钮 bug |
| `src/config/fixer.ts` | `getFixMaxIterations()`(替代废弃的 contextLines |
| `src/i18n/messages.ts` | 新增 fix 相关 key,删除 fixNotAvailable |
| `package.json` | 配置 `fixer.maxIterations`,移除 `fixer.contextLines` |
### 删除
| 文件 | 说明 |
|------|------|
| `src/fixer/fixer.ts` | 整个旧 AI 修复模块 |
## 5. 关键接口定义
### 5.1 `src/types.ts` 新增
```typescript
export interface LinterFix {
range: [number, number]; // 0-based 字符偏移,相对 lint 传入文本
text: string;
}
export interface LinterDiagnostic {
severity: Severity;
ruleId: string;
message: string;
range: vscode.Range;
suggestion?: string;
fix?: LinterFix; // 新增:可自动修复时存在
}
```
> ESLint 9 的 `LinterMessage.fix` 与 Stylelint 的 `Warning.fix?: EditInfo` 结构完全一致,均为 `{ range: [number, number], text: string }`,可直接透传。
### 5.2 `src/fix/fixEngine.ts`
```typescript
export interface FixResult {
success: boolean;
attempts: number;
message?: string;
}
export async function fixDiagnostic(
document: vscode.TextDocument,
workingDir: string,
adapter: LinterAdapter,
diag: LinterDiagnostic,
maxIterations: number
): Promise<FixResult>;
```
### 5.3 `src/fix/fixSession.ts`
```typescript
export interface FixedEntry {
key: string; // `${ruleId}@${line}`(修复前原始行号)
ruleId: string;
line: number; // 修复前原始行号(撤销定位用,面板不显示)
originalText: string; // 修复前该片段
newText: string; // 修复后该片段
source: 'linter';
}
export class FixSessionManager {
add(uri: vscode.Uri, entry: FixedEntry): void;
get(uri: vscode.Uri, key: string): FixedEntry | undefined;
undo(document: vscode.TextDocument, key: string): Promise<boolean>;
has(uri: vscode.Uri, key: string): boolean;
clear(uri: vscode.Uri): void;
}
```
### 5.4 `src/fix/codeActionProvider.ts`
```typescript
export class FixCodeActionProvider implements vscode.CodeActionProvider {
constructor(orchestrator: Orchestrator);
provideCodeActions(...): vscode.CodeAction[]; // QuickFixcommand 指向 codeReviewer.fixIssue
}
```
### 5.5 Orchestrator 缓存
```typescript
setAnalysisResult(uri: vscode.Uri, result: {
diagnostics: LinterDiagnostic[];
adapterId: string;
workingDir: string;
}): void;
getAnalysisResult(uri: vscode.Uri): {
diagnostics: LinterDiagnostic[];
adapterId: string;
workingDir: string;
} | undefined;
```
### 5.6 命令协议
- 面板消息沿用现有 `{ type, line, ruleId, source }`
- 新增 `{ type: 'undo', line, ruleId, source }``codeReviewer.undoFix`
## 6. 核心逻辑
### 6.1 多轮循环(fixEngine.ts
```
currentText = document.getText()
for round in 1..maxIterations:
mock = mockDocument(currentText, languageId, fileName)
result = adapter.check(mock, workingDir)
target = 找到 ruleId 匹配且行号最接近、带 fix 的诊断
if !target?.fix:
success = false; message = '无法自动修复'; break
newText = applyFix(currentText, target.fix) // 字符串级 offset 替换
currentText = newText
// 预验证:重跑 check 若该问题消失 → success
if 未收敛(达到轮次上限仍存在):
success = false; message = '达到轮次上限仍未修复'
一次 WorkspaceEdit 将 document 全文替换为 currentText(单次提交)
```
### 6.2 撤销(fixSession.ts
- 每次修复成功后,从最终提交前后提取片段记录 `FixedEntry`
- 撤销:打开文档 → 行号优先匹配 `newText` 首行 → 全文搜索回退 → `WorkspaceEdit` 替换回 `originalText`
- 先修 A 再修 B,撤销 A:在当前文本定位 A 的 newText 反向替换,B 保留
- 匹配失败(用户手动改动了相关区域):提示重新审查
### 6.3 CodeAction
- 从 Orchestrator 缓存取当前文档诊断,为每个带 `fix` 的生成 QuickFix CodeAction
- 命令执行 `codeReviewer.fixIssue`(多轮 + 会话),不直接用 `action.edit`
- hover 悬浮框自动显示修复链接(VSCode 标准行为)
### 6.4 面板状态
- 面板内部维护 `fixedEntries: Map<key, FixedEntry>`
- linter tab = 新报告未修复条目(行号+修复按钮) + fixedEntries(变绿+撤销按钮+不显示行号)
- 修复后自动刷新(重跑静态分析 → markers → CodeLens → 面板)
- 顺带修复既有 bugAI tab 误显示 fixAll 按钮、单条修复按钮文案错用「全部修复」
### 6.5 适配器 fix 提取
| 适配器 | fix 来源 | 配置解析链 |
|--------|----------|-----------|
| ESLint | `msg.fix` | 全局 `linters.eslintConfigPath` → 项目 `eslint.config.*` → 内置规则 |
| Stylelint | `warning.fix` | 全局 `linters.stylelintConfigPath` → 项目 `.stylelintrc` → 内置规则 |
| PMD | ✗(无 --fix | — |
| SQLFluff | ✗(CLI 整文件操作) | — |
| JSP | ✗(fix 偏移映射复杂,一期不支持) | — |
## 7. 技术选型与影响范围
- **mockDocument**jsp.ts 已有完整实现,提取复用,零新增依赖
- **fix 对象结构**ESLint `LinterMessage.fix` / Stylelint `EditInfo` 均为 `{range, text}`,已验证
- **影响面**types.ts(契约)、eslint/stylelint 适配器(提取 fix)、orchestrator(缓存)、commands/extension/panel/i18n/config、删除旧 fixer 模块
- **撤销限制**:依赖当前文档文本匹配,修复后用户大幅改动相关区域会撤销失败并提示
## 8. 配置变更
```jsonc
// package.json configuration.properties 新增
"vscode-code-reviewer.fixer.maxIterations": {
"type": "number",
"default": 3,
"description": "单条问题自动修复的最大循环轮次"
}
// 移除
"vscode-code-reviewer.fixer.contextLines" // AI 修复遗留,废弃
```
## 9. 测试计划
- `fixEngine.test.ts`:单条可修问题多轮收敛、不可修问题返回失败、达到轮次上限
- `fixSession.test.ts`:顺序修复→撤销、交错撤销(先 A 后 B 撤销 A 保留 B)
- 现有 `npm test`84 tests)回归通过
## 10. 验证顺序
`lint → compile → test``npm run lint` / `npm run compile` / `npm test`