Lewati ke konten

Refaktor kode secara struktural

Panduan ini menunjukkan cara meminta agen melakukan perubahan mekanis yang luas — codemod, migrasi API massal, pola yang ingin Anda hilangkan — memakai pencarian dan penulisan ulang struktural, dengan perubahannya di-staging untuk ditinjau sebelum apa pun ditulis.

ast_grep mencocokkan kode berdasarkan bentuk. ast_edit menulis ulangnya. resolve melakukan commit atau membuangnya.

Alat struktural mencakup bahasa yang diuraikan graf kode: Dart, JavaScript, TypeScript, TSX, PHP, Python, Rust, Zig, C, C++, Go, Java, Ruby, C#, Swift, Kotlin, R, Assembly, MATLAB, dan Ada. Alat-alat ini memerlukan grammar tree-sitter yang sudah disiapkan, dan itu selalu tersedia pada build yang dikirim — grammar yang hilang menghentikan server alih-alih menurunkan kualitas pencarian.

Jangan pernah menulis ulang sesuatu yang belum Anda lihat. Minta agen mencari:

Temukan setiap tempat kita memanggil dispose() pada nilai nullable yang baru saja kita periksa null-nya.

ast_grep(pattern: "if ($X != null) $X.dispose()", language: "dart")

Outputnya adalah file:line beserta kode sumber yang cocok untuk setiap situs.

Token Kecocokan
$NAME Satu node mana pun, ditangkap
$$$NAME Rangkaian saudara mana pun — argumen, pernyataan
$_ Apa pun, ditangkap tanpa nama

Yang paling penting: $NAME yang diulang harus menangkap teks yang sama.

if ($X != null) $X.dispose()

menemukan penjaga yang membuang apa yang diujinya. Pola ini tidak menemukan if (a != null) b.dispose() — yang biasanya justru bug yang Anda cari. Tidak ada pencarian tekstual yang dapat mengungkapkan hal itu.

Dua hal yang bisa dicoba, secara berurutan:

  1. Kecilkan polanya. Coba foo($X) sebelum if (a) { foo($X); }. Pola yang tidak valid sebagai kode mandiri sering kali bukan konstruk yang Anda maksud.
  2. Sebutkan bahasanya. language: hanya disimpulkan saat path menunjuk satu file.

Pola yang tidak diterima konteks penguraian mana pun dilaporkan sebagai tidak dapat diuraikan — alat menyatakannya, alih-alih melaporkan “tidak ada kecocokan” untuk sesuatu yang memang tidak pernah dibaca sesuai penampilannya.

ast_edit(
pattern: "if ($X != null) $X.dispose()",
rewrite: "$X?.dispose()",
language: "dart",
)

Hasilnya adalah proposal:

(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.

Belum ada apa pun yang tertulis di disk. Baca daftarnya, periksa beberapa situs yang dilaporkan pencarian, lalu:

resolve(edit_id: "edit_1", action: "accept")

atau

resolve(edit_id: "edit_1", action: "discard")

Inti melakukannya secara struktural adalah jangkauannya yang luas. Perubahan luas justru tempat penulisan ulang yang tampak masuk akal merusak sebuah tipe. Minta diagnostik setelah Anda menjalankan resolve:

lsp(action: "diagnostics", file: "*")

file: "*" menjalankan pemeriksa milik proyek (dart analyze, tsc --noEmit, cargo check, go build), bukan kueri per file — bentuk pertanyaan yang tepat setelah perubahan yang menyentuh empat puluh file.

Misalnya log(msg, level) akan diubah menjadi log(level: level, message: msg).

ast_grep(pattern: "log($MSG, $LEVEL)", language: "dart")

Perhatikan hasil yang cocok. Jika sebagian di antaranya adalah log dua argumen yang berbeda, persempit pola dengan membatasi path ke direktori pemilik log yang Anda maksud, alih-alih mencoba mengungkapkan perbedaannya di dalam pola.

ast_edit(
pattern: "log($MSG, $LEVEL)",
rewrite: "log(level: $LEVEL, message: $MSG)",
path: "lib/features/telemetry",
language: "dart",
)

Lalu resolve, lalu lsp(action: "diagnostics", file: "*").

Situasi Alat yang lebih baik
Mengganti nama simbol lsp_rename — alat ini mengikuti referensi, bukan bentuk
Menemukan string, komentar, atau nama file search (konten) / search_files (nama)
“Apa yang rusak jika saya mengubah ini?” Graf kode
Bahasa yang tidak dicakup grammar search ditambah edit