#AI Agent#TypeScript#开源

AST Codemod 重构不变量:让大规模自动改写可证明、可回滚

Codemod 能一次改几千个调用点,也能稳定复制同一个错误。本文从节点选择、语义保持、幂等性、格式边界和差分预算建立可执行的不变量门禁。

本文要点

  • 主题聚焦:AI Agent、TypeScript、开源
  • 适合人群:独立开发者、全栈工程师、AI 应用开发者
  • 核心价值:快速理解背景、实践路径、常见误区和工程建议
作者 @admin·发布于 2026年6月4日·更新于 2026年7月20日

把 API 调用从旧签名迁移到新签名时,文本替换会误改注释、字符串和同名符号;让模型逐文件重写又容易漏调用方并产生无关格式变化。AST codemod 可以精确选择节点,但“能解析并打印”不等于行为保持。错误的 symbol 绑定、默认参数、副作用顺序或注释丢失,会被机械地复制到整个仓库。

代码智能中的 AST + LLM介绍如何找到结构,本文只关注变换自身必须满足哪些不变量,以及怎样让每次运行可重放、可阻断和可撤销。

本文命令固定在 Linux/GNU CI 容器(Bash、GNU coreutils、Git)执行;macOS/Windows runner 必须使用同一容器。

先冻结变换的输入边界

一次 codemod 由基线提交、文件集合、解析器模式、语言版本、transform 源码、配置和 printer 版本共同决定。所有工具使用 lockfile 中的精确版本,CI 从干净检出安装;不要用 npx package@latest 临时运行。第一步只统计命中,不写文件:

bash
BASE_SHA=$(git rev-parse HEAD)
mkdir -p .artifacts
pnpm install --frozen-lockfile
pnpm exec jscodeshift src \
  --extensions=ts,tsx --parser=tsx \
  --transform=tools/codemods/migrate-client.cjs \
  --dry --print > .artifacts/codemod-preview.txt

预览报告列出每种节点的命中数、跳过数与原因。若预期迁移 840 个调用却只命中 600 个,不能开始写入;如果命中突然增至 4,000,也不能把它解释为工具更努力。动态属性访问、条件导入和类型错误文件进入人工清单。

五类不变量共同定义正确

选择不变量要求每个被改节点绑定目标声明,而不是名字相同。TypeScript 项目应使用 type checker 确认 symbol;只有语法树时,至少通过 import 来源和作用域收窄。修改后目标旧引用数必须归零,允许保留的兼容层单独列白名单。

表面不变量约束公开导出、注释许可证、指令、编码和未命中区域。Printer 往往会重排引号或空白,应限定只打印包含修改节点的文件,并设置最大文件数与最大行数差分预算。生成文件、vendor 和迁移快照默认排除。

语义不变量来自业务,而不是 AST。比如把 legacyCall(user, true) 改为 client.call({ user, strict: true }),必须证明参数求值顺序、异常、异步等待、默认值和返回类型未变。先用 characterization tests 锁住当前行为,再用AI 生成代码的属性测试覆盖空值、Unicode、边界数值和参数组合。

类型不变量要求 transform 前后公共声明可比较、全仓 typecheck 通过,且不能用新增 any、@ts-ignore 或宽化返回类型换取绿色。构建通过后仍要跑受影响调用方的集成测试。

最后是幂等不变量:第二次运行必须产生零新增差分。它能发现 matcher 反复包裹节点、import 每次重复插入、printer 不稳定等问题。

bash
mkdir -p .artifacts
pnpm exec jscodeshift src --extensions=ts,tsx --parser=tsx \
  --transform=tools/codemods/migrate-client.cjs
git diff --binary > .artifacts/pass-1.patch

pnpm exec jscodeshift src --extensions=ts,tsx --parser=tsx \
  --transform=tools/codemods/migrate-client.cjs
git diff --binary > .artifacts/pass-2.patch
cmp .artifacts/pass-1.patch .artifacts/pass-2.patch

小批执行比一次全仓改写更可审

按依赖层或包拆分批次,每批保持独立构建。先迁移叶子消费者,再迁移共享库,或者先提供兼容 API 后逐步迁移调用方;具体顺序由契约决定。每批输出命中统计、跳过清单、文件 diff、类型检查和测试结果,reviewer 可以抽样检查每一种变换形态。

bash
pnpm exec tsc --noEmit
pnpm test -- --runInBand
git diff --check
git diff --stat "$BASE_SHA"

命令名称应替换为仓库已有脚本,并把退出码绑定当前 HEAD。若 codemod 修改测试来适配错误实现,验证门要比较断言与历史失败样本;普通重构护栏可结合AI 辅助代码重构,但批量变换还必须检查命中集合和幂等性。

失败后按类别处理,不放宽 matcher

解析失败意味着文件未覆盖,不能跳过后继续声称全量完成;symbol 不明确就转人工;类型失败先确认变换是否破坏签名;测试失败用最小样本复现。禁止为了提高通过率删除注释、加入忽略规则、把 matcher 从 symbol 降级为名称或扩大格式化范围。

回滚前保存 transform digest、基线、批次文件清单和 patch。未合并批次在隔离工作树中执行 git restore --source="$BASE_SHA" --staged --worktree -- <batch-paths>;已合并批次使用反向提交,不重跑一个未经验证的“反向 codemod”。恢复后重新 typecheck 和测试,确认生成文件与依赖锁没有残留。

最终交付物是 transform 源码、固定版本、命中与跳过报告、两次运行一致证据、语义测试以及每批回滚 SHA。编程 Agent 的改动验证门负责总体合并决策;这些 codemod 不变量确保大规模自动改写不会因为 diff 很整齐就被误认为正确。

Related

相关推荐