Pereiti prie turinio

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

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

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: 0x1F tampa 0x1f, 1_000 tampa 1000, tokios pabėgimo sekos kaip "\x41" išrašomos kaip simbolis ("A"), o lanksčios heredoc konstrukcijos praranda įtraukas.
PHP
// 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:

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

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

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() 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 kelias
  • originalContents: failo turinys prieš perrašymą
  • rewrittenContents: failo turinys po perrašymo
  • replacementCount: struktūrinių pakeitimų skaičius
  • changed(): pagalbinė funkcija, kuri grąžina true, kai originalContents !== 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).