Ugrás a tartalomhoz

AST-átírás

Az AST-átírás az AST-keresést helyettesítősablonnal egészíti ki. A Greph ugyanazt az illesztőt futtatja, mint az AST-keresés, majd az átírt sablont a megtalált bájttartományokon visszailleszti a forrásba. A környező szóközök, behúzások és megjegyzések megmaradnak; a megtalált kód új formátumot kap (lásd: Illesztésen belül).

Gyors példák

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

Helyettesítősablonok

A helyettesítősablon ugyanazt a metaváltozó-szintaxist használja, mint a keresési minta. A rögzített csomópontokat név alapján illeszti vissza:

A mintában A sablonban Hatás
$X $X A rögzített csomópont beszúrása
$$$ARGS $$$ARGS A rögzített csomópontsorozat beszúrása
$NAME (azonosító) $NAME A rögzített azonosító beszúrása

A mintában szereplő, de a sablonból hiányzó metaváltozók kimaradnak az átírt kódból. A sablonban szereplő, de a mintából hiányzó metaváltozókat a rendszer jelenleg nem utasítja el: szó szerint kiírja őket, így a -r '$o->$m($$$A, $EXTRA)' egy $EXTRA változót ír a kódba (#4). Előbb a --dry-run segítségével ellenőrizze a sablonokat.

Módok

Jelző Viselkedés
(nincs) Az átírás alkalmazása minden illeszkedő fájlra
--dry-run Az átírt fájl tartalmának kiírása a szabványos kimenetre, írás nélkül
--interactive A rendszer megerősítést kér a Rewrite <file>? [y/N] felhasználótól minden fájl írása előtt

Az alapértelmezés: „minden módosítás kiírása”. Minta finomításakor először használja a --dry-run jelzőt, majd távolítsa el, ha elégedett a különbséggel.

Formátum megőrzése

A Greph nem formázza újra a fájlt. Az illesztő rögzíti minden találat bájttartományát az eredeti forrásban, az átíró pedig közvetlenül ezekbe a tartományokba illeszti be a megjelenített sablont. Az érintetlen kód pontosan úgy marad, ahogy volt, többek között:

  • Behúzás és sorvégi szóközök
  • Sorközi és önálló megjegyzések
  • Üres sorok elrendezése
  • Idézőjelek stílusa és összefűzés
  • A PHP nyitó- és zárócímke-stílusa

Ez tudatos kompromisszum a teljes újraformázással szemben: a Greph nem „csinosítja ki” az illesztésben nem szereplő kódot, ugyanakkor nem okoz zajos különbségeket a nem érintett sorokban.

Illesztésen belül

Magát a megtalált tartományt a PHP-Parser szépségformázóján keresztül megjelenített sablon váltja fel; a rögzített csomópontok új formátumot kapnak ahelyett, hogy forrásszövegként másolódnának be. Jelenleg ez a következőket jelenti (#2):

  • A megjegyzés, amely nincs a találaton belüli következő csomóponthoz rendelve, kimarad; ilyen például a tömb utolsó eleme utáni záró megjegyzés vagy egy egyébként üres tömbben lévő megjegyzés.
  • A rögzített csomópontokhoz rendelt megjegyzések megmaradnak, de külön sorba kerülnek; ettől egy egysoros kifejezés több sorossá válhat.
  • A találaton belüli sortörések nem maradnak meg: az ilyen megjegyzések nélküli többsoros array(...) egysoros [...] lesz.
  • A literálok írásmódja egységesül: a 0x1F alakja 0x1f lesz, a 1_000 alakja 1000, az olyan escape-sorozatok pedig, mint a "\x41", karakterként jelennek meg ("A"); a rugalmas heredocok elveszítik behúzásukat.
PHP
// before
$d = array(
    "a" => 1,  // trailing comment
);

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

Ellenőrizze a --dry-run kimenetet, ha a találatok megjegyzéseket vagy kézzel formázott literálokat tartalmaznak.

Többsoros és egymásba ágyazott találatok

A többsoros minták ugyanúgy működnek, mint az egysorosak. A szépségformázó a behelyettesített metaváltozókkal együtt jeleníti meg a helyettesítést, így az átírt kód elrendezését a formázó határozza meg, nem az eredeti találat.

A Greph fájlonként egy menetben végzi az átírást, és soha nem alkalmaz két egymást átfedő találatot. A találatokat a fájl végétől visszafelé járja be, és kihagyja azokat, amelyek átfednek egy már átírt találattal. Eltérő eltolásnál kezdődő, egymásba ágyazott találatoknál a belső találat íródik át, a külső pedig változatlan marad:

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

Ha két találat ugyanazon az eltoláson kezdődik (például ha egy metódusláncra a $obj->$method($$$ARGS) illeszkedik), a külső találat íródik át. Ha a minta egymásba ágyazható, ismételje meg az átírást addig, amíg a --dry-run már nem ír ki semmit (#3). A ast-grep a két egymásba ágyazott találat közül a külsőt írja át, ezért a vendor/bin/sg és a felsőbb rétegbeli sg itt eltérő eredményt ad.

Programozott használat

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);
}

A Greph::rewriteAst() maga nem ír fájlokat. A CLI felelős a módosítások véglegesítéséért (vagy kihagyásáért --dry-run és elutasított --interactive esetekben). Így a homlokzat biztonságosan hívható automatizált ügynökökből, amelyek alkalmazás előtt meg akarják vizsgálni a különbségeket.

RewriteResult a következőket teszi elérhetővé:

  • file: abszolút elérési út
  • originalContents: a fájl tartalma az átírás előtt
  • rewrittenContents: a fájl tartalma az átírás után
  • replacementCount: a szerkezeti cserék száma
  • changed(): segédfüggvény, amely originalContents !== rewrittenContents esetén true értéket ad vissza

Mikor használja az átírást

Az átírás a megfelelő eszköz, ha:

  • Az átalakítás helyi és szerkezeti jellegű (nem az egész fájl átszervezéséről van szó).
  • Determinisztikus, megismételhető refaktorálást szeretne, amelyet addig futtathat újra, amíg már nem történik változás.
  • Szeretné megtekinteni a módosítást az alkalmazása előtt.
  • Külső bináris meghívása nélkül szeretné beépíteni a refaktorálást egy szkriptbe vagy CI-feladatba.

Az átírás nem megfelelő eszköz, ha:

  • Az átalakítás olyan típusinformációktól függ, amelyeket a PHP-Parser egyetlen fájlból nem tud feloldani.
  • Az átalakításnak fájlhatáron átnyúlóan kell importokat hozzáadnia vagy eltávolítania.
  • Refaktorálás helyett formázót szeretne használni (használja a PHP-CS-Fixer eszközt).