AST perrašymas
AST perrašymas – tai AST paieška su pakeitimo šablonu. Greph naudoja tą patį atitikmenų paieškos mechanizmą kaip ir AST paieška, tada perrašytą šabloną įterpia į šaltinio kodą atitinkančiuose baitų intervaluose. Gretimi tarpai, įtraukos ir komentarai išsaugomi; atitinkantis kodas išspausdinamas iš naujo (žr. Atitikmens viduje).
Greiti pavyzdžiai
# 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
Pakeitimo šablonai
Pakeitimo šablone naudojama tokia pati metavariantų sintaksė kaip ir paieškos šablone. Užfiksuoti mazgai pagal pavadinimą įterpiami iš naujo:
| Šablone | Pakeitimo šablone | Poveikis |
|---|---|---|
$X |
$X |
Įterpiamas užfiksuotas mazgas |
$$$ARGS |
$$$ARGS |
Įterpiama užfiksuotų mazgų seka |
$NAME (identifikatorius) |
$NAME |
Įterpiamas užfiksuotas identifikatorius |
Šablone esantys, bet pakeitimo šablone nenaudojami metavariantai iš perrašyto kodo pašalinami. Metavariantas, esantis pakeitimo šablone, bet ne paieškos šablone, šiuo metu neatmetamas: jis išspausdinamas pažodžiui, todėl -r '$o->$m($$$A, $EXTRA)' į kodą įrašo $EXTRA kintamąjį (#4). Pirmiausia patikrinkite šablonus naudodami --dry-run.
Režimai
| Vėliava | Veikimas |
|---|---|
| (nėra) | Pakeitimas pritaikomas kiekvienam atitinkančiam failui |
--dry-run |
Perrašyto failo turinys išvedamas į stdout, bet neįrašomas |
--interactive |
Prieš įrašant kiekvieną failą pateikiama užklausa Rewrite <file>? [y/N] |
Pagal numatytuosius nustatymus „įrašyti visus pakeitimus“. Pirmiausia naudokite --dry-run, kai derinate šabloną, o kai pakeitimai jus tenkina, pašalinkite šią vėliavą.
Formatavimo išsaugojimas
Greph neperformatuoja failo. Atitikmenų paieškos mechanizmas užfiksuoja kiekvieno atitikmens baitų intervalą pradiniame šaltinio tekste, o perrašymo mechanizmas įterpia atvaizduotą šabloną tiesiai į tuos intervalus. Nepaliestas kodas lieka tiksliai toks, koks buvo, įskaitant:
- Įtraukas ir gale esančius tarpus
- Eilutinius ir atskirai pateiktus komentarus
- Tuščių eilučių išdėstymą
- Eilučių kabučių stilių ir konkatenaciją
- PHP atidarymo ir uždarymo žymų stilių
Tai sąmoningas kompromisas, palyginti su viso failo formatavimu: Greph netvarkys neatitikusio kodo, bet taip pat nesukurs triukšmingų, su nesusijusiomis eilutėmis susijusių pakeitimų.
Atitikmens viduje
Pats atitinkantis intervalas pakeičiamas šablonu, atvaizduotu naudojant PHP-Parser gražaus spausdinimo priemonę; užfiksuoti mazgai išspausdinami iš naujo, o ne nukopijuojami kaip šaltinio tekstas. Šiuo metu tai reiškia (#2):
- Komentaras, nepriskirtas po jo einančiam mazgui atitikmens viduje, pašalinamas, pavyzdžiui, galinis komentaras po paskutinio masyvo elemento arba komentaras kitaip tuščiame masyve.
- Prie užfiksuotų mazgų priskirti komentarai išsaugomi, bet perkeliami į atskirą eilutę, todėl vienos eilutės išraiška gali virsti kelių eilučių išraiška.
- Eilučių lūžiai atitikmens viduje neišsaugomi: kelių eilučių
array(...)be tokių komentarų tampa vienos eilutės[...]. - Pažodinis užrašas normalizuojamas:
0x1Ftampa0x1f,1_000tampa1000, tokios pabėgimo sekos kaip"\x41"išrašomos kaip simbolis ("A"), o lanksčios heredoc konstrukcijos praranda įtraukas.
// before
$d = array(
"a" => 1, // trailing comment
);
// after -p 'array($$$ITEMS)' -r '[$$$ITEMS]'
$d = ["a" => 1];
Peržiūrėkite --dry-run išvestį, jei atitikmenyse yra komentarų arba rankiniu būdu formatuotų literalių.
Kelių eilučių ir įdėtiniai atitikmenys
Kelių eilučių šablonai veikia taip pat kaip ir vienos eilutės. Pakeitimą atvaizduoja gražaus spausdinimo priemonė su įterptais metavariantais, todėl pakeisto kodo išdėstymą nustato spausdinimo priemonė, o ne pradinis atitikmuo.
Greph perrašo kiekvieną failą vienu perėjimu ir niekada nepritaiko dviejų persidengiančių atitikmenų. Ji pereina atitikmenis nuo failo pabaigos atgal ir praleidžia kiekvieną atitikmenį, persidengiantį su jau perrašytu. Jei įdėtiniai atitikmenys prasideda skirtingose vietose, perrašomas vidinis atitikmuo, o išorinis paliekamas nepakeistas:
// 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]];
Kai du atitikmenys prasideda toje pačioje vietoje (pavyzdžiui, metodų grandinę atitinka $obj->$method($$$ARGS)), perrašomas išorinis atitikmuo. Jei šabloną galima įdėti į save, kartokite perrašymą, kol --dry-run nieko neišveda (#3). ast-grep perrašo išorinį iš dviejų įdėtinių atitikmenų, todėl vendor/bin/sg ir ankstesnė sg šiame pavyzdyje pateikia skirtingus rezultatus.
Naudojimas programoje
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() pati failų neįrašo. CLI atsako už pakeitimų patvirtinimą (arba jų praleidimą --dry-run ir atmesto --interactive atvejais). Todėl šią sąsają saugu iškviesti automatizuotiems agentams, kurie prieš pritaikydami pakeitimus nori peržiūrėti skirtumą.
RewriteResult pateikia:
file: absoliutusis keliasoriginalContents: failo turinys prieš perrašymąrewrittenContents: failo turinys po perrašymoreplacementCount: struktūrinių pakeitimų skaičiuschanged(): pagalbinė funkcija, kuri grąžinatrue, kaioriginalContents !== rewrittenContents
Kada naudoti rewrite
Rewrite yra tinkamas įrankis, kai:
- Transformacija yra vietinė ir struktūrinė (ne viso failo pertvarkymas).
- Norite deterministinio, pakartojamo refaktoringo, kurį galite vykdyti pakartotinai, kol niekas nebesikeis.
- Norite peržiūrėti pakeitimą prieš jį pritaikydami.
- Norite integruoti refaktoringą į scenarijų arba CI užduotį nepaleisdami išorinės dvejetainės programos.
Rewrite nėra tinkamas įrankis, kai:
- Transformacija priklauso nuo tipo informacijos, kurios PHP-Parser negali nustatyti iš vieno failo.
- Transformacijai reikia pridėti arba pašalinti importus už failo ribų.
- Norite formatuoklio, o ne refaktoringo (naudokite PHP-CS-Fixer).