动态

详情 返回 返回

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 操作簡化為直觀的查找與重寫過程,使大規模代碼重構自動化成為可能。


(整理完)

Add a new 评论

Some HTML is okay.