構造的にコードをリファクタ
このガイドでは、構造検索と構造書き換えを使って、エージェントに広範囲の機械的な変更 — codemod、一括の API 移行、消し去りたいパターン — を加えさせる方法を説明します。変更は、何かが書き込まれる前にレビュー用にステージングされます。
ast_grep はコードを形でマッチします。ast_edit はそれを書き換えます。resolve がコミットするか破棄します。
始める前に
「始める前に」というセクション構造ツールが扱うのは、コードグラフがパースする言語です: Dart、JavaScript、TypeScript、TSX、PHP、Python、Rust、Zig、C、C++、Go、Java、Ruby、C#、Swift、Kotlin、R、Assembly、MATLAB、Ada。これらは tree-sitter の文法がステージされている必要がありますが、出荷ビルドでは常にそうなっています — 文法が欠けていると、検索が劣化するのではなくサーバーが停止します。
まず形を見つける
「まず形を見つける」というセクション見ていないものを書き換えてはいけません。エージェントに検索を頼みます:
null チェックしたばかりの nullable に対して
dispose()を呼び出している箇所をすべて見つけて。
ast_grep(pattern: "if ($X != null) $X.dispose()", language: "dart")出力は、各箇所の file:line と一致したソースです。
パターン構文で何が得られるか
「パターン構文で何が得られるか」というセクション| トークン | マッチするもの |
|---|---|
$NAME |
任意の単一ノード(捕捉される) |
$$$NAME |
任意の連続する兄弟 — 引数、文 |
$_ |
何にでもマッチ(名前を付けずに捕捉される) |
重要なのはこれです: 繰り返された $NAME は同じテキストを捕捉しなければなりません。
if ($X != null) $X.dispose()これは、検査したものを破棄するガードを見つけます。if (a != null) b.dispose() は見つけません — そしてそれは通常、探していたバグそのものです。テキスト検索ではこれを表現できません。
パターンが何も見つけないとき
「パターンが何も見つけないとき」というセクション順に試すべきことが 2 つあります:
- 小さくする。
if (a) { foo($X); }より先にfoo($X)。それ自体では有効なコードにならないパターンは、多くの場合、意図した構文ではありません。 - 言語を明示する。
language:が推論されるのは、pathが単一のファイルを指すときだけです。
どのパースコンテキストも受け入れないパターンは、パース不能として報告されます — 見た目どおりには一度も読まれていないものに対して「マッチなし」と報告するのではなく、ツールがそう告げます。
書き換えて、確定する前に確認する
「書き換えて、確定する前に確認する」というセクションast_edit( pattern: "if ($X != null) $X.dispose()", rewrite: "$X?.dispose()", language: "dart",)結果は提案です:
(proposed) ast_edit: if ($X != null) $X.dispose() → $X?.dispose()12 replacements in 4 files: lib/features/a/x.dart (3) lib/features/b/y.dart (5) …Nothing has been written. Call resolve with edit_id "edit_1" and action"accept" to apply it, or "discard" to drop it.まだディスクには何も書き込まれていません。リストを読み、検索が報告した箇所をいくつか確認してから、次を実行します:
resolve(edit_id: "edit_1", action: "accept")または
resolve(edit_id: "edit_1", action: "discard")コンパイラーで作業を確認する
「コンパイラーで作業を確認する」というセクションこれを構造的に行う要点は、変更が広範囲に及ぶことです。広範囲の変更こそ、もっともらしく見える書き換えが型を壊す場所です。resolve の後に診断を要求します:
lsp(action: "diagnostics", file: "*")file: "*" はファイル単位のクエリではなく、プロジェクト自身のチェッカー(dart analyze、tsc --noEmit、cargo check、go build)を実行します — 40 ファイルに触れた変更の後にふさわしい問いの形です。
実例: 呼び出しの移行
「実例: 呼び出しの移行」というセクションlog(msg, level) が log(level: level, message: msg) になるとします。
ast_grep(pattern: "log($MSG, $LEVEL)", language: "dart")ヒットを確認します。その一部が別の 2 引数の log であるなら、違いをパターンで表現しようとするのではなく、path を目的の log を所有するディレクトリに絞ってパターンを狭めます。
ast_edit( pattern: "log($MSG, $LEVEL)", rewrite: "log(level: $LEVEL, message: $MSG)", path: "lib/features/telemetry", language: "dart",)次に resolve、そして lsp(action: "diagnostics", file: "*") です。
ほかのものを使うべきとき
「ほかのものを使うべきとき」というセクション| 状況 | 適したツール |
|---|---|
| シンボルの名前変更 | lsp_rename — 形ではなく参照をたどります |
| 文字列、コメント、ファイル名の検索 | search(内容)/ search_files(名前) |
| 「これを変えると何が壊れるか?」 | コードグラフ |
| 文法が対応していない言語 | search と edit |
関連項目
「関連項目」というセクション- コードインテリジェンス — 構造マッチングがひと手間多い正規表現ではない理由
- 内蔵エージェントツール — 引数の完全なリスト
- コードグラフでコードを検索 — シンボルと影響の側面