AST:n uudelleenkirjoitus
AST:n uudelleenkirjoitus on AST-haku, jossa on korvaava malli. Greph käyttää samaa vastaavuuden etsintää kuin AST-haku ja sijoittaa sitten uudelleenkirjoitetun mallin lähdekoodiin osuvien tavuvälien kohdalle. Ympäröivät välilyönnit, sisennykset ja kommentit säilyvät; osuva koodi tulostetaan uudelleen (katso Osumien sisällä).
Esimerkkejä
# 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
Korvaavat mallit
Korvaava malli käyttää samaa metamuuttujasyntaksia kuin hakukuvio. Kaapatut solmut sijoitetaan takaisin nimen perusteella:
| Kuviossa | Mallissa | Vaikutus |
|---|---|---|
$X |
$X |
Lisää kaapattu solmu |
$$$ARGS |
$$$ARGS |
Lisää kaapattu solmujono |
$NAME (tunniste) |
$NAME |
Lisää kaapattu tunniste |
Kuviossa esiintyvät mutta mallista puuttuvat metamuuttujat jätetään pois uudelleenkirjoitetusta koodista. Nykyisin mallissa esiintyvää mutta kuviosta puuttuvaa metamuuttujaa ei hylätä: se tulostetaan sellaisenaan, joten -r '$o->$m($$$A, $EXTRA)' kirjoittaa koodiin $EXTRA-muuttujan (#4). Tarkista mallit ensin komennolla --dry-run.
Tilat
| Valitsin | Toiminta |
|---|---|
| (ei mitään) | Käytä uudelleenkirjoitusta kaikkiin osuman sisältäviin tiedostoihin |
--dry-run |
Tulosta uudelleenkirjoitetun tiedoston sisältö stdoutiin, älä kirjoita sitä tiedostoon |
--interactive |
Pyydä Rewrite <file>? [y/N] ennen kunkin tiedoston kirjoittamista |
Oletusarvoisesti "kirjoitetaan kaikki, mikä muuttuu". Käytä ensin valitsinta --dry-run, kun kokeilet kuviota, ja poista valitsin, kun olet tyytyväinen muutoksiin.
Muotoilun säilyttäminen
Greph ei muotoile tiedostoa uudelleen. Vastaavuuden etsintä tallentaa kunkin osuman tavuvälin alkuperäisestä lähdekoodista, ja uudelleenkirjoitin sijoittaa tulostetun mallin suoraan näihin väleihin. Koskematon koodi säilyy täsmälleen ennallaan, mukaan lukien:
- Sisennys ja rivin lopun välilyönnit
- Sisäiset ja omilla riveillään olevat kommentit
- Tyhjien rivien asettelu
- Merkkijonojen lainausmerkkityyli ja yhdistäminen
- PHP-avaus- ja sulkutunnisteen tyyli
Tämä on harkittu kompromissi täyden uudelleenmuotoilun sijaan: Greph ei siisti koodia, johon ei osunut, mutta ei myöskään tuota turhia muutoksia asiaan liittymättömille riveille.
Osuman sisällä
Osuva väli korvataan mallilla, joka tulostetaan uudelleen PHP-Parsern tulostimella. Kaapatut solmut tulostetaan uudelleen sen sijaan, että ne kopioitaisiin lähdetekstistä. Nykyisin tämä tarkoittaa seuraavaa (#2):
- Kommentti, jota ei ole liitetty osuman sisällä seuraavaan solmuun, poistetaan. Tällaisia ovat esimerkiksi taulukon viimeisen alkion jälkeinen kommentti tai muuten tyhjän taulukon kommentti.
- Kaapattuihin solmuihin liitetyt kommentit säilyvät, mutta siirtyvät omalle rivilleen. Tämä voi muuttaa yksirivisen lausekkeen moniriviseksi.
- Osuman sisäiset rivinvaihdot eivät säily: monirivisestä
array(...)-rakenteesta tulee yksirivinen[...]-rakenne, jos siinä ei ole tällaisia kommentteja. - Literaalien kirjoitusasu normalisoidaan:
0x1Fmuuttuu muotoon0x1f,1_000muotoon1000, escape-sekvenssit, kuten"\x41", kirjoitetaan merkkinä ("A"), ja joustavista heredoc-rakenteista poistetaan sisennys.
// before
$d = array(
"a" => 1, // trailing comment
);
// after -p 'array($$$ITEMS)' -r '[$$$ITEMS]'
$d = ["a" => 1];
Tarkista --dry-run-tuloste osumien varalta, jos niissä on kommentteja tai käsin muotoiltuja literaaleja.
Moniriviset ja sisäkkäiset osumat
Moniriviset kuviot toimivat samoin kuin yksiriviset. Korvaava koodi tulostetaan uudelleen tulostimella, johon metamuuttujat on sijoitettu. Korvatun koodin asettelu määräytyy siis tulostimen eikä alkuperäisen osuman mukaan.
Greph kirjoittaa jokaisen tiedoston uudelleen yhdellä kertaa eikä koskaan käytä kahta päällekkäistä osumaa. Se käy osumat läpi tiedoston lopusta alkuun ja ohittaa osumat, jotka menevät päällekkäin jo uudelleenkirjoitetun osuman kanssa. Jos sisäkkäiset osumat alkavat eri kohdista, sisempi osuma kirjoitetaan uudelleen ja ulompi jätetään ennalleen:
// 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]];
Jos kaksi osumaa alkaa samasta kohdasta (esimerkiksi menetelmäketju, johon $obj->$method($$$ARGS) osuu), ulompi osuma kirjoitetaan uudelleen. Jos kuvio voi sisältyä itseensä, suorita uudelleenkirjoitus uudelleen, kunnes komento --dry-run ei tulosta mitään (#3). ast-grep kirjoittaa kahdesta sisäkkäisestä osumasta ulomman uudelleen, joten vendor/bin/sg ja ylävirran sg tuottavat tässä eri tulokset.
Käyttö ohjelmallisesti
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() ei kirjoita tiedostoja itse. Muutosten tekemisestä (tai niiden ohittamisesta --dry-run- ja hylättyjen --interactive-tapausten yhteydessä) vastaa CLI. Näin facadea voivat turvallisesti kutsua automatisoidut agentit, jotka haluavat tarkistaa muutokset ennen niiden käyttöönottoa.
RewriteResult tuo näkyviin:
file: absoluuttinen polkuoriginalContents: tiedoston sisältö ennen uudelleenkirjoitustarewrittenContents: tiedoston sisältö uudelleenkirjoituksen jälkeenreplacementCount: rakenteellisten korvausten määrächanged(): apufunktio, joka palauttaa arvontrue, kunoriginalContents !== rewrittenContents
Milloin uudelleenkirjoitusta kannattaa käyttää
Uudelleenkirjoitus on oikea työkalu, kun:
- Muunnos on paikallinen ja rakenteellinen (ei koko tiedoston uudelleenjärjestelyä).
- Haluat deterministisen ja toistettavan refaktoroinnin, jonka voit ajaa uudelleen, kunnes muutoksia ei enää synny.
- Haluat esikatsella muutoksen ennen sen käyttöönottoa.
- Haluat integroida refaktoroinnin skriptiin tai CI-työhön suorittamatta ulkoista binääritiedostoa.
Uudelleenkirjoitus ei ole oikea työkalu, kun:
- Muunnos riippuu tyyppitiedoista, joita PHP-Parser ei pysty ratkaisemaan yhdestä tiedostosta.
- Muunnos edellyttää tuontien lisäämistä tai poistamista tiedostorajojen yli.
- Haluat muotoilijan refaktoroinnin sijaan (käytä PHP-CS-Fixer).