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
# 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í:
0x1Fse změní na0x1f,1_000na1000, escape sekvence jako"\x41"se vypíšou jako znak ("A") a flexibilní heredocy ztratí odsazení.
// 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:
// 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í
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í cestuoriginalContents: obsah souboru před přepsánímrewrittenContents: obsah souboru po přepsáníreplacementCount: počet strukturálních náhradchanged(): 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).