RubyConf China 2023 - 玩转 AST,构建自己的代码分析和代码重写工具 by 黄志敏

以下是整理后的演讲内容,已去除发言人姓名、时间及口头语,保留核心逻辑与内容结构,使文本结构更为清晰连贯。


玩转 AST:构建自己的代码分析与重写工具

一、AST 的基本概念

抽象语法树(AST, Abstract Syntax Tree)是在编程语言处理中最常见的数据结构,用于抽象表示源代码的语法结构。
通过树形结构来展示代码的组成,每个节点对应代码中的一个语法元素。AST 在编译器、解释器、静态分析工具、代码格式化工具中都有广泛应用,使得代码分析、优化、重构更容易实现。

二、Ruby 中常见的 AST 工具

在 Ruby 中有几种常用方式将源代码转换成 AST:

  • Ripper:Ruby 自带的库。
  • Parser Gem:如 RuboCop 使用。
  • Ruby Parser:Breakman 等安全扫描工具使用。

这些工具能将 Ruby 源码转换成 AST,为代码格式化、静态分析等提供基础。


三、格式化工具的工作原理

代码格式化工具会遍历整个 AST,根据节点类型生成新的代码字符串。
例如遇到 class 节点就输出类定义代码,遇到 def 节点就生成方法定义。
格式规则(如缩进、单双引号样式)由工具预置,而生成的代码并不依赖原始源文件的布局。

Prettier 为例,它通过定义“中间表示”,使用 grouplinehardline 等标记控制换行与缩进。当代码超出行宽限制时,可以自动换行或保持一行显示。


四、Linter 工具

Linter 是静态分析工具。其实现模式一般有两种:

  1. 纯分析型:发现问题仅提示。
  2. 可修复型:除了提示,还能自动修复。

作者开发过两个工具:

  • rails_best_practices:检查是否违反最佳实践,仅提示问题。
  • Synvert:能自动重写和修复代码。

五、rails_best_practices 的实现模式

通过监听特定 AST 节点的类型与属性,分析模型与数据库索引、关联定义是否匹配。如检测到缺少索引,则输出警告信息,指出文件与行号。此方法不修改源码,仅报告问题。


六、Synvert 的实现模式

Synvert 以“代码片段(snippet)”为基本单元,提供查找、修改 AST 的规则组合。
它以 AST 为核心,对 Ruby 代码执行“查找 + 重写”操作。
工作流程为:

  1. 使用 RipperParserRuby Parser 将代码生成 AST。
  2. 利用四类信息:node typechildrenlocationsource 来定位节点。
  3. 增强节点属性,便于后续使用(如 node.receivernode.message 等)。

七、自定义 AST 查询语言(Node Query Language)

为方便查询 AST,作者设计了类 CSS 的查询语法(NQL):

.send[receiver=factory_bot][message=create]

该语法可根据节点类型与属性条件匹配 AST 节点。
其底层实现借鉴编译器中的 词法分析(LEX)语法分析(YACC) 技术,使用 Ruby 中的 RexRacc 实现。

通过定义 token(如节点类型、属性 key/value、操作符等),构建解析器,将字符串形式的查询语言解析成可执行的节点选择器。

该语言支持:

  • 多个属性条件;
  • 各种数值、布尔、字符串类型;
  • 多种比较操作符(=、!=、>、<、in、not in 等);
  • 数组、正则匹配;
  • 多重选择器与子节点关系选择。

可匹配复杂模式,例如查找方法调用、查找特定 key 的哈希、检测测试方法中缺少 super 调用等。


八、代码替换与重写策略

代码替换通常有两种方式:

  1. 基于 AST 修改后重新生成代码:简单但可能破坏代码风格。
  2. 基于位置信息直接修改源文件:保留原始代码风格。

Synvert 采用第二种方式,通过 AST 节点的 start/end 位置信息,在源代码字符串中精确替换。
支持操作包括:

  • replace_with
  • insert_after
  • insert_before
  • delete
  • remove

示例:
将代码 FactoryBot.create(:user) 替换为 FactoryBot.create(:user, :activated)
或在测试方法中自动插入 super 调用。


九、查找与替换实例示例

  1. 删除调试代码
    利用查询语法查找 puts 调用并删除。

  2. 方法命名调整
    在 Rails 2.3 → 3.0 升级场景中,可自动将 find_by_email_and_active(true) 转换为:

    where(email: email, active: true)
    

    并处理类似的其他动态 finder 方法。

  3. 语法升级自动化
    批量替换旧写法、增加必要语法元素(如 super),或更新断言风格。


十、Synvert 的架构组成

  1. Synvert Core:提供查询与重写 API。
  2. CLI 工具 synvert:可读取本地或远程 snippet,批量修改文件。
  3. GUI 与 VSCode 扩展
    • 可视化展示修改 diff;
    • 单选或批量应用修改;
    • 支持自动生成 snippet;
    • 提供输入原始代码与期望输出,即可自动生成对应 snippet。

十一、自动生成 Snippet 逻辑

输入示例代码与目标代码,系统会:

  1. 将两段代码转换为 AST;
  2. 对比节点差异;
  3. 生成相应的“insert / replace / delete”规则;
  4. 构建出 snippet。

此逻辑可应用在 GUI、VSCode 扩展与 API 后端中,目前尚非 AI,而是规则生成。


十二、跨语言支持与扩展

Synvert 不仅支持 Ruby,还拥有 JavaScript 版本,可实现:

  • TypeScript 类型语法转换;
  • CSS/LESS/SASS → SCSS 的文件重命名与语法转换等。

十三、开源与协作

Synvert 的核心组件(synvert-corenode_querynode_mutation)均为开源。
社区可贡献 snippet 规则库(synvert-snippets),在 GUI 中可直接启用。
支持即插即用、可版本更新分发的扩展机制。


十四、常见问题答疑摘要

  • AST 是否能分析动态定义的方法(metaprogramming)?
    只能处理静态语法,动态定义的方法无法直接出现在 AST 中。

  • 代码是否需语法正确?
    必须语法正确才能生成 AST;语法错误文件无法解析。

  • 是否能自动处理 Rails 版本迁移时的参数传递变化?
    可以部分自动化处理,复杂场景需人工复核。

  • 是否能实现 RSpec 到 Minitest 的自动转化?
    可以,通过预定义输入输出规则与 snippet 即可扩展。


十五、总结

通过 AST 技术,可以实现自定义的代码分析、格式化和重写工具。
Synvert 结合同步的 CLI、GUI 与编辑器扩展,将 AST 操作简化为直观的查找与重写过程,使大规模代码重构自动化成为可能。


(整理完)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值