Lewati ke konten

Penulisan Ulang AST

Penulisan ulang AST adalah pencarian AST dengan templat pengganti. Greph menjalankan pencocok yang sama seperti pencarian AST, lalu menyisipkan templat yang telah ditulis ulang kembali ke sumber pada rentang byte yang cocok. Spasi di sekitarnya, indentasi, dan komentar dipertahankan; kode yang cocok itu sendiri dicetak ulang (lihat Di dalam kecocokan).

Contoh singkat

Shell
# 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

Templat pengganti

Templat pengganti menggunakan sintaks metavariabel yang sama dengan pola pencarian. Simpul yang ditangkap disisipkan kembali berdasarkan nama:

Dalam pola Dalam templat Efek
$X $X Sisipkan simpul yang ditangkap
$$$ARGS $$$ARGS Sisipkan urutan simpul yang ditangkap
$NAME (pengenal) $NAME Sisipkan pengenal yang ditangkap

Metavariabel yang muncul dalam pola tetapi tidak dalam templat akan dihapus dari kode hasil penulisan ulang. Metavariabel yang muncul dalam templat tetapi tidak dalam pola tidak ditolak saat ini: metavariabel itu dicetak apa adanya, sehingga -r '$o->$m($$$A, $EXTRA)' menulis variabel $EXTRA ke dalam kode (#4). Periksa templat dengan --dry-run terlebih dahulu.

Mode

Flag Perilaku
(tidak ada) Terapkan penulisan ulang ke setiap file yang cocok
--dry-run Cetak konten file hasil penulisan ulang ke stdout, jangan menulisnya
--interactive Minta konfirmasi Rewrite <file>? [y/N] sebelum menulis setiap file

Default-nya adalah "tulis semua perubahan". Gunakan --dry-run terlebih dahulu saat mencoba-coba pola, lalu hapus flag tersebut setelah Anda puas dengan diff-nya.

Mempertahankan format

Greph tidak memformat ulang file. Pencocok mencatat rentang byte setiap kecocokan pada sumber asli, lalu penulis ulang menyisipkan templat yang dirender langsung ke rentang tersebut. Kode yang tidak disentuh tetap persis seperti semula, termasuk:

  • Indentasi dan spasi di akhir baris
  • Komentar sebaris dan komentar mandiri
  • Tata letak baris kosong
  • Gaya tanda kutip string dan penggabungannya
  • Gaya tag buka/tutup PHP

Ini adalah kompromi yang disengaja dibandingkan pemformatan ulang penuh: Greph tidak akan "merapikan" kode yang tidak dicocokkan, tetapi juga tidak akan menimbulkan diff yang berisik pada baris yang tidak terkait.

Di dalam kecocokan

Rentang yang cocok itu sendiri diganti dengan templat yang dirender oleh pencetak cantik milik PHP-Parser, dengan simpul yang ditangkap dicetak ulang alih-alih disalin sebagai teks sumber. Saat ini, hasilnya adalah (#2):

  • Komentar yang tidak melekat pada simpul berikutnya di dalam kecocokan akan dihapus, misalnya komentar di akhir setelah item array terakhir atau komentar di dalam array yang kosong.
  • Komentar yang melekat pada simpul yang ditangkap dipertahankan, tetapi dipindahkan ke barisnya sendiri. Akibatnya, ekspresi satu baris dapat menjadi beberapa baris.
  • Pemisah baris di dalam kecocokan tidak dipertahankan: array(...) multibaris tanpa komentar semacam itu akan menjadi [...] satu baris.
  • Ejaan literal dinormalisasi: 0x1F menjadi 0x1f, 1_000 menjadi 1000, escape seperti "\x41" ditulis sebagai karakternya ("A"), dan heredoc fleksibel kehilangan indentasinya.
PHP
// before
$d = array(
    "a" => 1,  // trailing comment
);

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

Tinjau output --dry-run untuk kecocokan yang memuat komentar atau literal yang formatnya diatur secara manual.

Kecocokan multibaris dan bertingkat

Pola multibaris bekerja sama seperti pola satu baris. Penggantinya dirender oleh pencetak cantik dengan metavariabel yang disubstitusikan, sehingga tata letak kode yang diganti berasal dari pencetak, bukan dari kecocokan asli.

Greph menulis ulang setiap file dalam satu kali lintasan dan tidak pernah menerapkan dua kecocokan yang saling tumpang tindih. Alat ini menelusuri kecocokan dari akhir file ke awal dan melewati kecocokan yang tumpang tindih dengan kecocokan yang sudah ditulis ulang. Untuk kecocokan bertingkat yang dimulai pada offset berbeda, kecocokan bagian dalam ditulis ulang dan bagian luar dibiarkan seperti semula:

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]];

Jika dua kecocokan dimulai pada offset yang sama (misalnya, rantai metode yang cocok dengan $obj->$method($$$ARGS)), kecocokan terluar yang ditulis ulang. Jika pola dapat bersarang, jalankan penulisan ulang lagi hingga --dry-run tidak mencetak apa pun (#3). ast-grep menulis ulang kecocokan terluar dari dua kecocokan bersarang, sehingga vendor/bin/sg dan sg di hulu menghasilkan hasil yang berbeda di sini.

Penggunaan secara terprogram

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() tidak menulis file sendiri. CLI bertanggung jawab untuk melakukan commit atas perubahan (atau melewatinya dalam kasus --dry-run dan --interactive yang ditolak). Hal ini membuat facade aman dipanggil oleh agen otomatis yang ingin memeriksa diff sebelum menerapkannya.

RewriteResult menyediakan:

  • file: path absolut
  • originalContents: isi file sebelum penulisan ulang
  • rewrittenContents: isi file setelah penulisan ulang
  • replacementCount: jumlah penggantian struktural
  • changed(): helper yang mengembalikan true saat originalContents !== rewrittenContents

Kapan menggunakan rewrite

Rewrite adalah alat yang tepat ketika:

  • Transformasi bersifat lokal dan struktural (bukan penataan ulang seluruh file).
  • Anda menginginkan refaktor deterministik dan berulang yang dapat dijalankan kembali hingga tidak ada perubahan.
  • Anda ingin melihat pratinjau perubahan sebelum menerapkannya.
  • Anda ingin mengintegrasikan refaktor ke dalam skrip atau tugas CI tanpa menjalankan biner eksternal.

Rewrite bukan alat yang tepat ketika:

  • Transformasi bergantung pada informasi tipe yang tidak dapat diselesaikan PHP-Parser dari satu file.
  • Transformasi perlu menambahkan atau menghapus import lintas batas file.
  • Anda menginginkan pemformat, bukan refaktor (gunakan PHP-CS-Fixer).