Preskočiť na obsah

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

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

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: 0x1F sa zmení na 0x1f, 1_000 sa zmení na 1000, escape sekvencie, napríklad "\x41", sa vypíšu ako znak ("A") a flexibilné heredoc bloky stratia svoje odsadenie.
PHP
// 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á:

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

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

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() 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 cesta
  • originalContents: obsah súboru pred prepisom
  • rewrittenContents: obsah súboru po prepise
  • replacementCount: počet štrukturálnych náhrad
  • changed(): pomocná funkcia, ktorá vráti true, 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).