Přejít k obsahu

Přepis AST

Přepis AST je vyhledávání AST s šablonou náhrady. Greph spustí stejný porovnávací modul jako vyhledávání AST a poté vloží přepsanou šablonu do zdroje v rozsazích odpovídajících bajtů. Okolní mezery, odsazení a komentáře zůstanou zachovány; odpovídající kód se vytiskne znovu (viz Uvnitř shody).

Rychlé příklady

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

Šablony náhrady

Šablona náhrady používá stejnou syntaxi metavariables jako vyhledávací vzor. Zachycené uzly se vkládají zpět podle názvu:

Ve vzoru V šabloně Výsledek
$X $X Vloží zachycený uzel
$$$ARGS $$$ARGS Vloží zachycenou sekvenci uzlů
$NAME (identifikátor) $NAME Vloží zachycený identifikátor

Metavariables, které se objeví ve vzoru, ale ne v šabloně, se z přepsaného kódu odstraní. Metavariable, která se objeví v šabloně, ale ne ve vzoru, se zatím neodmítá: vypíše se doslova, takže -r '$o->$m($$$A, $EXTRA)' zapíše do kódu proměnnou $EXTRA (#4). Šablony nejprve zkontrolujte pomocí --dry-run.

Režimy

Příznak Chování
(žádný) Použije přepis na každý soubor, který odpovídá
--dry-run Vypíše obsah přepsaného souboru na stdout, ale nezapíše ho
--interactive Před zápisem každého souboru se zeptá Rewrite <file>? [y/N]

Výchozí nastavení znamená „zapsat všechny změny“. Při úpravách vzoru nejprve použijte --dry-run a až budete s rozdílem spokojeni, příznak odeberte.

Zachování formátu

Greph soubor nepřeformátuje. Porovnávací modul zaznamená rozsah bajtů každé shody v původním zdroji a přepisovací modul vloží vykreslenou šablonu přímo do těchto rozsahů. Nedotčený kód zůstane přesně tak, jak byl, včetně:

  • Odsazení a koncových mezer
  • Řádkových i samostatných komentářů
  • Rozložení prázdných řádků
  • Stylu uvozovek a spojování řetězců
  • Stylu otevíracích a uzavíracích značek PHP

Jde o záměrný kompromis oproti úplnému přeformátování: Greph neupraví kód, který nenašel, ale zároveň nevytváří zbytečně rozsáhlé rozdíly na nesouvisejících řádcích.

Uvnitř shody

Samotný odpovídající rozsah se nahradí šablonou vykreslenou pomocí formátovače PHP-Parser; zachycené uzly se vytisknou znovu místo kopírování zdrojového textu. V současnosti to znamená (#2):

  • Komentář, který uvnitř shody není připojen k následujícímu uzlu, se odstraní; například koncový komentář za poslední položkou pole nebo komentář v jinak prázdném poli.
  • Komentáře připojené k zachyceným uzlům zůstanou zachovány, ale přesunou se na samostatný řádek, takže se může jednořádkový výraz změnit na několik řádků.
  • Zalomení řádků uvnitř shody se nezachovají: víceřádkové array(...) bez takových komentářů se změní na jednořádkové [...].
  • Zápis literálů se sjednotí: 0x1F se změní na 0x1f, 1_000 na 1000, escape sekvence jako "\x41" se vypíšou jako znak ("A") a flexibilní heredocy ztratí odsazení.
PHP
// before
$d = array(
    "a" => 1,  // trailing comment
);

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

U výstupu --dry-run zkontrolujte shody obsahující komentáře nebo ručně formátované literály.

Víceřádkové a vnořené shody

Víceřádkové vzory fungují stejně jako jednořádkové. Náhradu vykreslí formátovač po dosazení metavariables, takže rozložení nahrazeného kódu určuje formátovač, nikoli původní shoda.

Greph přepíše každý soubor v jediném průchodu a nikdy nepoužije dvě překrývající se shody. Shody prochází od konce souboru směrem zpět a přeskočí každou shodu, která se překrývá s již provedeným přepisem. U vnořených shod s různými počátečními pozicemi se přepíše vnitřní shoda a vnější zůstane beze změny:

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

Když dvě shody začínají na stejné pozici (například řetězec metod odpovídající $obj->$method($$$ARGS)), přepíše se vnější shoda. Pokud lze vzor vnořovat, spouštějte přepis opakovaně, dokud --dry-run nic nevypíše (#3). ast-grep přepíše vnější ze dvou vnořených shod, takže vendor/bin/sg a upstream verze sg zde vrátí odlišné výsledky.

Programové použití

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() soubory samo nezapisuje. Za potvrzení změn (nebo jejich přeskočení v případech --dry-run a odmítnutí --interactive) odpovídá CLI. Díky tomu lze fasádu bezpečně volat z automatizovaných agentů, kteří chtějí nejprve zkontrolovat rozdíl a teprve potom změny použít.

RewriteResult zpřístupňuje:

  • file: absolutní cestu
  • originalContents: obsah souboru před přepsáním
  • rewrittenContents: obsah souboru po přepsání
  • replacementCount: počet strukturálních náhrad
  • changed(): pomocnou funkci, která vrací true, když originalContents !== rewrittenContents

Kdy použít rewrite

Rewrite je správný nástroj, když:

  • Transformace je lokální a strukturální (nejde o přeuspořádání celého souboru).
  • Chcete deterministický, opakovatelný refaktor, který můžete spouštět znovu, dokud se nic nezmění.
  • Chcete si změnu před použitím prohlédnout.
  • Chcete refaktor začlenit do skriptu nebo úlohy CI bez spouštění externího binárního souboru.

Rewrite není správný nástroj, když:

  • Transformace závisí na informacích o typech, které PHP-Parser nedokáže zjistit z jediného souboru.
  • Transformace potřebuje přidat nebo odebrat importy napříč hranicí souboru.
  • Potřebujete formátovač, nikoli refaktor (použijte PHP-CS-Fixer).