Preskoči na vsebino

Preoblikovanje AST

Preoblikovanje AST je iskanje AST s predlogo za zamenjavo. Greph uporablja isti mehanizem za ujemanje kot iskanje AST, nato pa vstavi preoblikovano predlogo nazaj v izvorno kodo na obsegu ujemajočih se bajtov. Presledki, zamiki in komentarji v okolici se ohranijo; ujemajoča se koda pa se znova izpiše (glejte Znotraj ujemanja).

Hitri primeri

Lupina
# 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

Predloge za zamenjavo

Predloga za zamenjavo uporablja enako sintakso metaspremenljivk kot iskalni vzorec. Zajeta vozlišča se po imenu znova vstavijo:

V vzorcu V predlogi Učinek
$X $X Vstavi zajeto vozlišče
$$$ARGS $$$ARGS Vstavi zaporedje zajetih vozlišč
$NAME (identifikator) $NAME Vstavi zajeti identifikator

Metaspremenljivke, ki so v vzorcu, ne pa tudi v predlogi, se iz izhodne kode izpustijo. Metaspremenljivka, ki je v predlogi, ne pa v vzorcu, trenutno ni zavrnjena: izpiše se dobesedno, zato -r '$o->$m($$$A, $EXTRA)' v kodo zapiše spremenljivko $EXTRA (#4). Predloge najprej preverite z --dry-run.

Načini

Zastavica Delovanje
(brez) Izvede preoblikovanje v vsaki datoteki z ujemanjem
--dry-run Izpiše vsebino preoblikovane datoteke na stdout, ne da bi jo zapisala
--interactive Pred zapisom vsake datoteke pozove Rewrite <file>? [y/N]

Privzeto se »zapiše vse, kar se spremeni«. Pri prilagajanju vzorca najprej uporabite --dry-run, nato pa zastavico odstranite, ko ste zadovoljni z razliko.

Ohranjanje oblikovanja

Greph datoteke ne preoblikuje. Mehanizem za ujemanje zabeleži bajtni obseg vsakega ujemanja v izvirni kodi, mehanizem za preoblikovanje pa izrisano predlogo vstavi neposredno na te obsege. Nedotaknjena koda ostane nespremenjena, vključno z:

  • Zamikom in presledki na koncu vrstice
  • Vstavljenimi in samostojnimi komentarji
  • Postavitvijo praznih vrstic
  • Slogom narekovajev in združevanjem nizov
  • Slogom PHP začetnih in končnih oznak

To je nameren kompromis glede na popolno preoblikovanje: Greph ne bo »urejal« kode, ki je ni ujemal, hkrati pa ne bo povzročal nepreglednih razlik v nepovezanih vrsticah.

Znotraj ujemanja

Sam ujemajoči se obseg se zamenja s predlogo, izrisano s tiskalnikom za lepo oblikovanje v PHP-Parser; zajeta vozlišča se znova izpišejo, namesto da bi se kopirala kot izvorno besedilo. Trenutno to pomeni (#2):

  • Komentar, ki v ujemanju ni pripet na naslednje vozlišče, se izpusti; na primer končni komentar za zadnjim elementom tabele ali komentar v sicer prazni tabeli.
  • Komentarji, pripeti na zajeta vozlišča, se ohranijo, vendar se prestavijo v svojo vrstico, zato se lahko enovrstični izraz raztegne čez več vrstic.
  • Prelomi vrstic znotraj ujemanja se ne ohranijo: večvrstični array(...) brez takih komentarjev postane enovrstični [...].
  • Zapis literalov se poenoti: 0x1F postane 0x1f, 1_000 postane 1000, ubežna zaporedja, kot je "\x41", se izpišejo kot znak ("A"), prilagodljivi heredoc pa izgubijo zamik.
PHP
// before
$d = array(
    "a" => 1,  // trailing comment
);

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

Preglejte izhod --dry-run za ujemanja, ki vsebujejo komentarje ali ročno oblikovane literale.

Večvrstična in ugnezdena ujemanja

Večvrstični vzorci delujejo enako kot enovrstični. Zamenjavo izriše tiskalnik za lepo oblikovanje, pri čemer vanjo vstavi metaspremenljivke, zato postavitev zamenjane kode določi tiskalnik in ne izvirno ujemanje.

Greph vsako datoteko preoblikuje v enem prehodu in nikoli ne uporabi dveh prekrivajočih se ujemanj. Ujemanja pregleda od konca datoteke proti začetku in preskoči vsako ujemanje, ki se prekriva z že preoblikovanim. Pri ugnezdenih ujemanjih, ki se začnejo na različnih odmikih, se preoblikuje notranje ujemanje, zunanje pa ostane nespremenjeno:

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

Ko se dve ujemanji začneta na istem odmiku (na primer veriga metod, ki se ujema z $obj->$method($$$ARGS)), se prepiše zunanje ujemanje. Če se vzorec lahko gnezdi, ponovite prepisovanje, dokler --dry-run ne izpiše ničesar (#3). ast-grep prepiše zunanje od dveh gnezdenih ujemanj, zato vendor/bin/sg in nadrejeni sg tukaj ustvarita različna rezultata.

Uporaba v kodi

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() sam ne zapisuje datotek. Za uveljavitev sprememb (ali njihovo preskočitev v primerih --dry-run in zavrnjenih primerih --interactive) je odgovoren CLI. Tako lahko fasado varno kličejo avtomatizirani agenti, ki želijo pregledati razliko, preden uveljavijo spremembe.

RewriteResult razkriva:

  • file: absolutna pot
  • originalContents: vsebina datoteke pred prepisom
  • rewrittenContents: vsebina datoteke po prepisu
  • replacementCount: število strukturnih zamenjav
  • changed(): pomočnik, ki vrne true, kadar originalContents !== rewrittenContents

Kdaj uporabiti Rewrite

Rewrite je prava izbira, kadar:

  • Je preoblikovanje lokalno in strukturno (ne gre za preureditev celotne datoteke).
  • Želite deterministično refaktoriranje, ki ga lahko ponavljate, dokler se nič več ne spremeni.
  • Želite pred uveljavitvijo predogledati spremembo.
  • Želite refaktoriranje vključiti v skript ali opravilo CI, ne da bi zagnali zunanjo binarno datoteko.

Rewrite ni prava izbira, kadar:

  • Je preoblikovanje odvisno od informacij o tipih, ki jih PHP-Parser ne more razbrati iz ene same datoteke.
  • Morate dodati ali odstraniti uvoze prek meja datotek.
  • Želite oblikovalnik namesto orodja za refaktoriranje (uporabite PHP-CS-Fixer).