İçeriğe geç

Kodu yapısal olarak yeniden düzenleme

Bu kılavuz, bir ajanın yapısal arama ve yeniden yazmayı kullanarak geniş ve mekanik bir değişikliği — bir codemod, toplu bir API geçişi, kurtulmak istediğiniz bir deseni — nasıl yapacağını gösterir; değişiklik, herhangi bir şey yazılmadan önce incelenmek üzere sahnelenir.

ast_grep kodu şekle göre eşleştirir. ast_edit onu yeniden yazar. resolve ise işler ya da atar.

Yapısal araçlar, kod grafiğinin ayrıştırdığı dilleri kapsar: Dart, JavaScript, TypeScript, TSX, PHP, Python, Rust, Zig, C, C++, Go, Java, Ruby, C#, Swift, Kotlin, R, Assembly, MATLAB ve Ada. Bunların tree-sitter gramerlerinin hazırlanmış olması gerekir; gönderilen bir derlemede bu her zaman böyledir — eksik bir gramer, aramayı bozmak yerine sunucuyu durdurur.

Bakmadığınız bir şeyi asla yeniden yazmayın. Ajandan arama yapmasını isteyin:

Az önce null denetimi yaptığımız bir nullable üzerinde dispose() çağırdığımız her yeri bul.

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

Çıktı, her yer için file:line bilgisi ve eşleşen kaynak metnidir.

Belirteç Eşleştirdiği
$NAME Yakalanan herhangi bir tek düğüm
$$$NAME Herhangi bir kardeş dizisi — argümanlar, deyimler
$_ Her şey, adsız yakalanır

Önemli olan şu: yinelenen bir $NAME aynı metni yakalamak zorundadır.

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

test ettiği şeyi dispose eden bir koruma koşuluyla eşleşir. if (a != null) b.dispose() ile eşleşmez — ki bu genellikle aradığınız hatanın ta kendisidir. Hiçbir metinsel arama bunu ifade edemez.

Sırayla denenecek iki şey:

  1. Daha küçük yapın. if (a) { foo($X); } yerine önce foo($X) deneyin. Tek başına geçerli kod olmayan bir desen, çoğu zaman kastettiğiniz yapı değildir.
  2. Dili belirtin. language: yalnızca path tek bir dosyayı gösterdiğinde çıkarımlanır.

Hiçbir ayrıştırma bağlamının kabul etmediği bir desen, ayrıştırılamaz olarak bildirilir — araç, hiçbir zaman göründüğü gibi okunmayan bir şey için “eşleşme yok” demek yerine bunu söyler.

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

Sonuç bir öneridir:

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

Henüz diskte hiçbir şey yok. Listeyi okuyun, aramanın bildirdiği birkaç yeri denetleyin, ardından:

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

ya da

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

Bunu yapısal olarak yapmanın amacı, geniş olmasıdır. Akla yatkın görünen bir yeniden yazmanın bir türü bozduğu yer tam da geniş değişikliklerdir. İşledikten sonra tanılama isteyin:

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

file: "*", dosya başına bir sorgu yerine projenin kendi denetleyicisini (dart analyze, tsc --noEmit, cargo check, go build) çalıştırır — kırk dosyaya dokunan bir değişiklikten sonra sorulacak doğru soru biçimi budur.

Uygulamalı örnek: bir çağrıyı yeni biçimine geçirme

Bölüm başlığı “Uygulamalı örnek: bir çağrıyı yeni biçimine geçirme”

Diyelim ki log(msg, level), log(level: level, message: msg) biçimine geçiyor.

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

Eşleşmelere bakın. Bazıları başka bir iki argümanlı log ise, farkı desende ifade etmeye çalışmak yerine path değerini kastettiğiniz log’un bulunduğu dizinle sınırlayarak deseni daraltın.

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

Ardından resolve, sonra lsp(action: "diagnostics", file: "*").

Durum Daha iyi araç
Bir sembolü yeniden adlandırma lsp_rename — şekilleri değil, referansları izler
Bir dizeyi, bir yorumu ya da bir dosya adını bulma search (içerik) / search_files (adlar)
“Bunu değiştirirsem ne bozulur?” Kod grafiği
Gramerlerin kapsamadığı bir dil search artı edit