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

12 KiB
Raw Blame History

自动修复功能重构设计书

日期: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 LinterDiagnosticfix?: 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,新增 undoFixreview 更新缓存
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 新增

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

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

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

export class FixCodeActionProvider implements vscode.CodeActionProvider {
  constructor(orchestrator: Orchestrator);
  provideCodeActions(...): vscode.CodeAction[];  // QuickFixcommand 指向 codeReviewer.fixIssue
}

5.5 Orchestrator 缓存

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. 配置变更

// 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 test84 tests)回归通过

10. 验证顺序

lint → compile → testnpm run lint / npm run compile / npm test