İçeriğe geç

AST Yeniden Yazma

AST yeniden yazma, yerine koyma şablonu içeren AST aramasıdır. Greph, AST araması ile aynı eşleştiriciyi çalıştırır; ardından yeniden yazılmış şablonu, eşleşen bayt aralıklarındaki kaynak koda ekler. Çevredeki boşluklar, girintiler ve yorumlar korunur; eşleşen kodun kendisi yeniden yazdırılır (bkz. Eşleşme içi).

Hızlı örnekler

Kabuk
# Convert array() literals to []
./vendor/bin/greph -p 'array($$$ITEMS)' -r '[$$$ITEMS]' --dry-run src
./vendor/bin/greph -p 'array($$$ITEMS)' -r '[$$$ITEMS]' src

# isset ternary -> null coalesce
./vendor/bin/greph -p 'isset($X) ? $X : $Y' -r '$X ?? $Y' --dry-run src

# Rename a method call
./vendor/bin/greph -p '$obj->oldName($$$ARGS)' -r '$obj->newName($$$ARGS)' src

# Confirm each file before writing
./vendor/bin/greph -p 'array($$$ITEMS)' -r '[$$$ITEMS]' --interactive src

Yerine koyma şablonları

Yerine koyma şablonu, arama deseniyle aynı üst değişken söz dizimini kullanır. Yakalanan düğümler adlarına göre yeniden eklenir:

Desende Şablonda Etki
$X $X Yakalanan düğümü ekler
$$$ARGS $$$ARGS Yakalanan düğüm dizisini ekler
$NAME (tanımlayıcı) $NAME Yakalanan tanımlayıcıyı ekler

Desende yer alıp şablonda yer almayan üst değişkenler yeniden yazılan koddan çıkarılır. Şablonda yer alıp desende bulunmayan bir üst değişken şu anda reddedilmez: olduğu gibi yazdırılır; dolayısıyla -r '$o->$m($$$A, $EXTRA)' koda bir $EXTRA değişkeni yazar (#4). Önce şablonları --dry-run ile denetleyin.

Modlar

Bayrak Davranış
(yok) Yeniden yazmayı eşleşen her dosyaya uygular
--dry-run Yeniden yazılmış dosya içeriğini stdout'a yazdırır, dosyaya yazmaz
--interactive Her dosyaya yazmadan önce Rewrite <file>? [y/N] sorar

Varsayılan davranış, "değişen her şeyi yaz" şeklindedir. Bir desen üzerinde çalışırken önce --dry-run kullanın, ardından farktan memnun kaldığınızda bu bayrağı kaldırın.

Biçimi koruma

Greph dosyayı yeniden biçimlendirmez. Eşleştirici, özgün kaynakta her eşleşmenin bayt aralığını kaydeder; yeniden yazıcı da oluşturulan şablonu doğrudan bu aralıklara ekler. Dokunulmayan kod, aşağıdakiler de dahil olmak üzere aynen kalır:

  • Girinti ve sondaki boşluklar
  • Satır içi ve bağımsız yorumlar
  • Boş satır düzeni
  • Dize tırnak stili ve birleştirme biçimi
  • PHP açma/kapatma etiketi stili

Bu, dosyanın tamamını yeniden biçimlendirmeye karşı bilinçli bir ödünleşimdir: Greph eşleşmediği kodu "düzeltmez", ancak ilgisiz satırlarda gereksiz farklar da oluşturmaz.

Eşleşme içi

Eşleşen aralığın kendisi, yakalanan düğümler kaynak metinden kopyalanmak yerine yeniden yazdırılarak, şablonun PHP-Parser biçimlendiricisiyle oluşturulmuş hâliyle değiştirilir. Şimdilik bunun anlamı şudur (#2):

  • Eşleşme içinde sonraki bir düğüme bağlı olmayan yorumlar atılır; örneğin son dizi öğesinden sonraki bir yorum veya başka öğe içermeyen bir dizideki yorum.
  • Yakalanan düğümlere bağlı yorumlar korunur, ancak kendi satırlarına taşınır; bu da tek satırlık bir ifadeyi birkaç satıra dönüştürebilir.
  • Eşleşme içindeki satır sonları korunmaz: bu tür yorumlar içermeyen çok satırlı bir array(...), tek satırlık bir [...] hâline gelir.
  • Değişmezlerin yazımı normalleştirilir: 0x1F, 0x1f olur; 1_000, 1000 olur; "\x41" gibi kaçış dizileri karakter olarak ("A") yazılır ve esnek heredoc'ların girintisi kaldırılır.
PHP
// before
$d = array(
    "a" => 1,  // trailing comment
);

// after -p 'array($$$ITEMS)' -r '[$$$ITEMS]'
$d = ["a" => 1];

Yorumlar veya elle biçimlendirilmiş değişmezler içeren eşleşmeler için --dry-run çıktısını inceleyin.

Çok satırlı ve iç içe eşleşmeler

Çok satırlı desenler, tek satırlı desenlerle aynı şekilde çalışır. Yerine koyma, üst değişkenler eklenmiş olarak biçimlendirici tarafından oluşturulur; dolayısıyla değiştirilen kodun düzeni özgün eşleşmeden değil, biçimlendiriciden gelir.

Greph her dosyayı tek geçişte yeniden yazar ve çakışan eşleşmelerin ikisini birden hiçbir zaman uygulamaz. Eşleşmeleri dosyanın sonundan geriye doğru tarar ve daha önce yeniden yazdığı bir eşleşmeyle çakışanları atlar. Farklı konumlarda başlayan iç içe eşleşmelerde içteki eşleşme yeniden yazılır, dıştaki ise olduğu gibi bırakılır:

PHP
// before
$b = array(1, array(2));

// after one run of -p 'array($$$ITEMS)' -r '[$$$ITEMS]'
$b = array(1, [2]);

// after a second run
$b = [1, [2]];

Aynı konumda başlayan iki eşleşme varsa (örneğin $obj->$method($$$ARGS) ile eşleşen bir yöntem zinciri), dıştaki eşleşme yeniden yazılır. Bir desen iç içe eşleşebiliyorsa, --dry-run hiçbir şey yazdırmayana kadar yeniden yazma işlemini çalıştırın (#3). ast-grep, iç içe iki eşleşmeden dıştakini yeniden yazar; bu nedenle vendor/bin/sg ile yukarı akıştaki sg burada farklı sonuçlar üretir.

Programatik kullanım

PHP
use Greph\Greph;
use Greph\Ast\AstSearchOptions;

$rewrites = Greph::rewriteAst(
    'array($$$ITEMS)',
    '[$$$ITEMS]',
    'src',
    new AstSearchOptions(jobs: 4),
);

foreach ($rewrites as $result) {
    if (!$result->changed()) {
        continue;
    }

    echo "{$result->file}: {$result->replacementCount} replacement(s)\n";
    file_put_contents($result->file, $result->rewrittenContents);
}

Greph::rewriteAst() dosyaları kendisi yazmaz. Değişiklikleri kaydetmekten (veya --dry-run ile reddedilen --interactive durumlarında atlamaktan) CLI sorumludur. Böylece bu cephe, değişiklikleri uygulamadan önce farkı incelemek isteyen otomatik ajanlar tarafından güvenle çağrılabilir.

RewriteResult şunları sunar:

  • file: mutlak yol
  • originalContents: yeniden yazma öncesindeki dosya içeriği
  • rewrittenContents: yeniden yazma sonrasındaki dosya içeriği
  • replacementCount: yapısal değişiklik sayısı
  • changed(): originalContents !== rewrittenContents olduğunda true döndüren yardımcı

Yeniden yazma ne zaman kullanılmalı

Yeniden yazma şu durumlarda doğru araçtır:

  • Dönüştürme yerel ve yapısalsa (dosyanın tamamını yeniden düzenlemiyorsa).
  • Hiçbir şey değişmeyene kadar yeniden çalıştırabileceğiniz, belirleyici ve tekrarlanabilir bir yeniden düzenleme istiyorsanız.
  • Uygulamadan önce değişikliği önizlemek istiyorsanız.
  • Yeniden düzenlemeyi harici bir ikili dosya çağırmadan bir betiğe veya CI işine entegre etmek istiyorsanız.

Yeniden yazma şu durumlarda doğru araç değildir:

  • Dönüştürme, PHP-Parser'ün tek bir dosyadan çözemeyeceği tür bilgilerine bağlıysa.
  • Dönüştürmenin dosya sınırları arasında içe aktarmalar eklemesi veya kaldırması gerekiyorsa.
  • Yeniden düzenleme yerine bir biçimlendirici istiyorsanız (PHP-CS-Fixer kullanın).