Prepis AST
Prepis AST je vyhľadávanie v AST s náhradnou šablónou. Greph používa rovnaký porovnávač ako vyhľadávanie v AST a potom vloží prepísanú šablónu späť do zdrojového kódu v rozsahu bajtov zodpovedajúcom zhode. Okolitý prázdny priestor, odsadenie a komentáre sa zachovajú; zodpovedajúci kód sa vytlačí znova (pozrite si V rámci zhody).
Rýchle prí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
Náhradné šablóny
Náhradná šablóna používa rovnakú syntax metavariabilných premenných ako vzor vyhľadávania. Zachytené uzly sa podľa názvu vložia späť:
| Vo vzore | V šablóne | Výsledok |
|---|---|---|
$X |
$X |
Vloží zachytený uzol |
$$$ARGS |
$$$ARGS |
Vloží zachytenú sekvenciu uzlov |
$NAME (identifikátor) |
$NAME |
Vloží zachytený identifikátor |
Metavariabilné premenné, ktoré sa nachádzajú vo vzore, ale nie v šablóne, sa z prepísaného kódu vynechajú. Metavariabilná premenná, ktorá sa nachádza v šablóne, ale nie vo vzore, sa zatiaľ neodmieta: vypíše sa doslova, takže -r '$o->$m($$$A, $EXTRA)' zapíše do kódu premennú $EXTRA (#4). Šablóny najprv skontrolujte pomocou --dry-run.
Režimy
| Príznak | Správanie |
|---|---|
| (žiadny) | Použije prepis vo všetkých súboroch so zhodou |
--dry-run |
Vypíše obsah prepísaného súboru do štandardného výstupu, nezapisuje ho |
--interactive |
Pred zápisom každého súboru sa opýta Rewrite <file>? [y/N] |
Predvolené správanie je „zapísať všetky zmeny“. Pri dolaďovaní vzoru najprv použite --dry-run a po spokojnosti s rozdielom tento príznak odstráňte.
Zachovanie formátovania
Greph súbor nepreformátuje. Porovnávač zaznamená rozsah bajtov každej zhody v pôvodnom zdroji a náhradný nástroj vloží vykreslenú šablónu priamo do týchto rozsahov. Nedotknutý kód zostane presne taký, aký bol, vrátane:
- Odsadenia a koncových medzier
- Vnorených a samostatných komentárov
- Rozloženia prázdnych riadkov
- Štýlu úvodzoviek a spájania reťazcov
- Štýlu otváracej a zatváracej značky PHP
Je to zámerný kompromis oproti úplnému preformátovaniu: Greph neupraví kód, ktorému nezodpovedal, ale zároveň nevytvorí zbytočné rozdiely v nesúvisiacich riadkoch.
V rámci zhody
Samotný zodpovedajúci rozsah sa nahradí šablónou vykreslenou pomocou tlačového formátovača PHP-Parser; zachytené uzly sa vytlačia znova namiesto skopírovania zdrojového textu. Momentálne to znamená (#2):
- Komentár, ktorý nie je priradený k nasledujúcemu uzlu v rámci zhody, sa zahodí, napríklad koncový komentár za poslednou položkou poľa alebo komentár v inak prázdnom poli.
- Komentáre priradené k zachyteným uzlom sa zachovajú, ale presunú sa na vlastný riadok, čo môže zmeniť jednoriadkový výraz na niekoľko riadkov.
- Zalomenia riadkov v rámci zhody sa nezachovajú: viacriadkové
array(...)bez takýchto komentárov sa zmení na jednoriadkové[...]. - Zápis literálov sa normalizuje:
0x1Fsa zmení na0x1f,1_000sa zmení na1000, escape sekvencie, napríklad"\x41", sa vypíšu ako znak ("A") a flexibilné heredoc bloky stratia svoje odsadenie.
// before
$d = array(
"a" => 1, // trailing comment
);
// after -p 'array($$$ITEMS)' -r '[$$$ITEMS]'
$d = ["a" => 1];
Výstup --dry-run skontrolujte pri zhodách obsahujúcich komentáre alebo ručne formátované literály.
Viacriadkové a vnorené zhody
Viacriadkové vzory fungujú rovnako ako jednoradové. Náhradu vykreslí formátovač po dosadení metapremenných, takže výsledné rozloženie kódu určuje formátovač, nie pôvodná zhoda.
Greph prepíše každý súbor v jednom priechode a nikdy nepoužije dve prekrývajúce sa zhody. Prechádza zhody od konca súboru smerom dozadu a preskočí každú zhodu, ktorá sa prekrýva s už prepísanou. Pri vnorených zhodách s rôznymi počiatočnými pozíciami sa prepíše vnútorná zhoda a vonkajšia zostane nezmenená:
// 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]];
Ak dve zhody začínajú na rovnakej pozícii (napríklad reťazec metód zachytený pomocou $obj->$method($$$ARGS)), prepíše sa vonkajšia zhoda. Ak sa vzor môže vyskytovať vnútorne, opakujte prepis, kým --dry-run nič nevypíše (#3). ast-grep prepíše vonkajšiu z dvoch vnorených zhôd, takže vendor/bin/sg a upstream sg tu vytvoria odlišné výsledky.
Programové použitie
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() samo súbory nezapisuje. Za potvrdenie zmien (alebo ich preskočenie v prípadoch --dry-run a zamietnutých --interactive) zodpovedá CLI. Vďaka tomu možno túto fasádu bezpečne volať z automatizovaných agentov, ktoré chcú pred použitím zmien skontrolovať rozdiely.
RewriteResult poskytuje:
file: absolútna cestaoriginalContents: obsah súboru pred prepisomrewrittenContents: obsah súboru po prepisereplacementCount: počet štrukturálnych náhradchanged(): pomocná funkcia, ktorá vrátitrue, keďoriginalContents !== rewrittenContents
Kedy použiť Rewrite
Rewrite je vhodný nástroj, keď:
- Transformácia je lokálna a štrukturálna (nejde o preusporiadanie celého súboru).
- Chcete deterministický a opakovateľný refaktoring, ktorý môžete znova spúšťať, kým sa nič nezmení.
- Chcete si zmenu pred použitím prezrieť.
- Chcete refaktoring integrovať do skriptu alebo úlohy CI bez spúšťania externého binárneho súboru.
Rewrite nie je vhodný nástroj, keď:
- Transformácia závisí od informácií o typoch, ktoré PHP-Parser nedokáže určiť z jediného súboru.
- Transformácia vyžaduje pridanie alebo odstránenie importov naprieč hranicami súboru.
- Chcete formátovač namiesto refaktoringu (použite PHP-CS-Fixer).