テストコードがない!レガシーJSを安全にリファクタリングするための『ASTハッシュ比較』

なぜこのツールが必要になったのか?

レガシーJS を prettier、eslint、tsc を使ってリファクタリングしていたときのことです。テストコードなし、E2Eテストは一部だけです。

prettier をかけると、インデントの他にもかなりの量の diff ができました。細かく見ていくのは骨が折れそう。prettierはフォーマッターだから動きは変えないはず。えいやっでマージしようとも思いましたが、これまでの数々のやってもうたが頭をよぎり、踏みとどまりました。

そこでリファクタ前後の js の AST(抽象構文木)のハッシュ値を比較することにしました。

AST(抽象構文木)とは

ソースコードの見た目が違っても、ASTが同じなら、同じプログラムです。不具合も含めて、稼働中のプログラムと同じだから、diffが大きくても安心してリリースしてできるよ、という技術的な根拠です。

モノタロウさんがすごいなと思ったのでphp-astを試してみた - Qiita
背景・概要 20万行超のコードベースをテストせずにリファクタリングリリースした話 を読んで、面白いなあ、これPHPだとどうなってるんだろう・・・と思って調べてみました。 php-ast というのがあるらしい PHP AST 徹底解説 のよう...

ASTから除外するもの

prompt.md は用意しませんでしたが、ブラウザ版Gemini に初期バージョンを実装してもらい、標準入力バージョンに改造。

実際に使いながら、Geminiに相談して、ASTをJSON化する前にキーをソートしたり、ASTから削除する項目を調整しました。

ast-logic-hasher/index.js at 1e1b5911bbb978f68ba0bee7477f09046f856976 · ninton/ast-logic-hasher
Verify JavaScript logic consistency using AST SHA-1 hashes - ninton/ast-logic-hasher
ast-logic-hasher/index.js at 1e1b5911bbb978f68ba0bee7477f09046f856976 · ninton/ast-logic-hasher
Verify JavaScript logic consistency using AST SHA-1 hashes - ninton/ast-logic-hasher

ミニファイのハッシュ値はボツ

ミニファイしてハッシュ値を比較する案もありましたが、引用符の種類('" か)やセミコロンの有無にハッシュ値が左右されてしまいそうです。

JSのAST(抽象構文木)を試してみると、引用符の種類('" か)やセミコロンの有無が違っても、ASTは同じでした。

他にもミニファイの「変数名の圧縮」「糖衣構文を使った最適化」は、決定論的かどうかがわかりませんでした。ここをつきつめて調べるよりも、ASTを使うことにしました。

工夫したこと

diff の原因ごとに、PRを分けました。

  1. インデント、空行
  2. 関数やブロック開始の波カッコの位置
  3. 行末セミコロンの有無
  4. 文字列の引用符をシングルクォートからダブルクォートへ揃える
  5. js内のオブジェクトリテラルのキーのクォートが削除される

修正前後で ASTが同じなら、動作確認なしで。ASTが違うときは、同じ種類の原因だけに揃えてから、かんたんなページ表示できることを確認しました。

tscを使いながら、JSDocのキャストを追加するときは、JSDoc追加の前後でASTが一致することを確認して、純粋に型注釈だけを足したことを確認しました。なぜなら、レガシーJSを見ているとうっかりリファクタしていることがあります。意図しない修正が紛れ込まないように注意しました。

学習の成果

AIとの協働: プロンプトをガチガチに固めなくても、ブラウザ版Geminiと対話しながら、自分の欲しい『ちょうどいいツール』を爆速で作ることができたこと。

github・npm

Just a moment...
GitHub - ninton/ast-logic-hasher: Verify JavaScript logic consistency using AST SHA-1 hashes
Verify JavaScript logic consistency using AST SHA-1 hashes - ninton/ast-logic-hasher

タイトルとURLをコピーしました