玩轉 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 為例,它通過定義“中間表示”,使用 group、line、hardline 等標記控制換行與縮進。當代碼超出行寬限制時,可以自動換行或保持一行顯示。
四、Linter 工具
Linter 是靜態分析工具。其實現模式一般有兩種:
- 純分析型:發現問題僅提示。
- 可修復型:除了提示,還能自動修復。
作者開發過兩個工具:
- rails_best_practices:檢查是否違反最佳實踐,僅提示問題。
- Synvert:能自動重寫和修復代碼。
五、rails_best_practices 的實現模式
通過監聽特定 AST 節點的類型與屬性,分析模型與數據庫索引、關聯定義是否匹配。如檢測到缺少索引,則輸出警告信息,指出文件與行號。此方法不修改源碼,僅報告問題。
六、Synvert 的實現模式
Synvert 以“代碼片段(snippet)”為基本單元,提供查找、修改 AST 的規則組合。
它以 AST 為核心,對 Ruby 代碼執行“查找 + 重寫”操作。
工作流程為:
- 使用
Ripper、Parser或Ruby Parser將代碼生成 AST。 - 利用四類信息:
node type、children、location、source來定位節點。 - 增強節點屬性,便於後續使用(如
node.receiver、node.message等)。
七、自定義 AST 查詢語言(Node Query Language)
為方便查詢 AST,作者設計了類 CSS 的查詢語法(NQL):
.send[receiver=factory_bot][message=create]
該語法可根據節點類型與屬性條件匹配 AST 節點。
其底層實現借鑑編譯器中的 詞法分析(LEX) 與 語法分析(YACC) 技術,使用 Ruby 中的 Rex 和 Racc 實現。
通過定義 token(如節點類型、屬性 key/value、操作符等),構建解析器,將字符串形式的查詢語言解析成可執行的節點選擇器。
該語言支持:
- 多個屬性條件;
- 各種數值、布爾、字符串類型;
- 多種比較操作符(=、!=、>、<、in、not in 等);
- 數組、正則匹配;
- 多重選擇器與子節點關係選擇。
可匹配複雜模式,例如查找方法調用、查找特定 key 的哈希、檢測測試方法中缺少 super 調用等。
八、代碼替換與重寫策略
代碼替換通常有兩種方式:
- 基於 AST 修改後重新生成代碼:簡單但可能破壞代碼風格。
- 基於位置信息直接修改源文件:保留原始代碼風格。
Synvert 採用第二種方式,通過 AST 節點的 start/end 位置信息,在源代碼字符串中精確替換。
支持操作包括:
replace_withinsert_afterinsert_beforedeleteremove
示例:
將代碼 FactoryBot.create(:user) 替換為 FactoryBot.create(:user, :activated);
或在測試方法中自動插入 super 調用。
九、查找與替換實例示例
- 刪除調試代碼
利用查詢語法查找puts調用並刪除。 -
方法命名調整
在 Rails 2.3 → 3.0 升級場景中,可自動將find_by_email_and_active(true)轉換為:where(email: email, active: true)並處理類似的其他動態 finder 方法。
- 語法升級自動化
批量替換舊寫法、增加必要語法元素(如super),或更新斷言風格。
十、Synvert 的架構組成
- Synvert Core:提供查詢與重寫 API。
- CLI 工具
synvert:可讀取本地或遠程 snippet,批量修改文件。 -
GUI 與 VSCode 擴展:
- 可視化展示修改 diff;
- 單選或批量應用修改;
- 支持自動生成 snippet;
- 提供輸入原始代碼與期望輸出,即可自動生成對應 snippet。
十一、自動生成 Snippet 邏輯
輸入示例代碼與目標代碼,系統會:
- 將兩段代碼轉換為 AST;
- 對比節點差異;
- 生成相應的“insert / replace / delete”規則;
- 構建出 snippet。
此邏輯可應用在 GUI、VSCode 擴展與 API 後端中,目前尚非 AI,而是規則生成。
十二、跨語言支持與擴展
Synvert 不僅支持 Ruby,還擁有 JavaScript 版本,可實現:
- TypeScript 類型語法轉換;
- CSS/LESS/SASS → SCSS 的文件重命名與語法轉換等。
十三、開源與協作
Synvert 的核心組件(synvert-core、node_query、node_mutation)均為開源。
社區可貢獻 snippet 規則庫(synvert-snippets),在 GUI 中可直接啓用。
支持即插即用、可版本更新分發的擴展機制。
十四、常見問題答疑摘要
- AST 是否能分析動態定義的方法(metaprogramming)?
只能處理靜態語法,動態定義的方法無法直接出現在 AST 中。 - 代碼是否需語法正確?
必須語法正確才能生成 AST;語法錯誤文件無法解析。 - 是否能自動處理 Rails 版本遷移時的參數傳遞變化?
可以部分自動化處理,複雜場景需人工複核。 - 是否能實現 RSpec 到 Minitest 的自動轉化?
可以,通過預定義輸入輸出規則與 snippet 即可擴展。
十五、總結
通過 AST 技術,可以實現自定義的代碼分析、格式化和重寫工具。
Synvert 結合同步的 CLI、GUI 與編輯器擴展,將 AST 操作簡化為直觀的查找與重寫過程,使大規模代碼重構自動化成為可能。
(整理完)