
# AST-i ümberkirjutamine

AST-i ümberkirjutamine on AST-otsing asendusmalliga. Greph kasutab sama sobitajat nagu [AST-otsing](https://greph.inline0.dev/et/docs/modes/ast), seejärel põimib ümberkirjutatud malli lähtekoodi tagasi sobitatud baidivahemikesse. Ümbritsevad tühikud, taanded ja kommentaarid säilivad; sobitatud kood prinditakse uuesti (vt [Sobitatud lõigu sees](#inside-a-match)).

## Kiired näited

```bash
# 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
```

## Asendusmallid

Asendusmall kasutab sama metamärkide süntaksit mis otsingumuster. Hõivatud sõlmed põimitakse nime järgi tagasi:

| Mustri sees | Mallis | Tulemus |
|---|---|---|
| `$X` | `$X` | Lisa hõivatud sõlm |
| `$$$ARGS` | `$$$ARGS` | Lisa hõivatud sõlmede jada |
| `$NAME` (identifikaator) | `$NAME` | Lisa hõivatud identifikaator |

Metamärgid, mis esinevad mustris, kuid mitte mallis, eemaldatakse ümberkirjutatud koodist. Mallis esinevat metamärki, mida mustris pole, praegu tagasi ei lükata: see prinditakse sõna-sõnalt, nii et `-r '$o->$m($$$A, $EXTRA)'` kirjutab koodi muutuja `$EXTRA` ([#4](https://github.com/inline0/greph/issues/4)). Kontrollige malle esmalt valikuga `--dry-run`.

## Režiimid

| Lipp | Käitumine |
|---|---|
| (puudub) | Rakenda ümberkirjutus kõigile sobivatele failidele |
| `--dry-run` | Prindi ümberkirjutatud faili sisu standardväljundisse, ära kirjuta faili |
| `--interactive` | Küsi enne iga faili kirjutamist `Rewrite <file>? [y/N]` käest |

Vaikimisi kirjutatakse kõik muutunud failid üle. Kasutage mustri täpsustamisel esmalt `--dry-run` ning eemaldage seejärel lipp, kui erinevus sobib.

## Vormingu säilitamine

Greph ei vorminda faili ümber. Sobitaja salvestab iga vaste baidivahemiku algses lähtekoodis ning ümberkirjutaja põimib renderdatud malli otse nendesse vahemikesse. Puutumata kood jääb täpselt samaks, sealhulgas:

- Taanded ja lõputühikud
- Reasisesed ja eraldi kommentaarid
- Tühjade ridade paigutus
- Stringide jutumärkide stiil ja ühendamine
- PHP avamis- ja sulgemissiltide stiil

See on teadlik kompromiss täieliku ümbervormindamise arvelt: Greph ei korrasta koodi, millele vaste ei leitud, kuid ei tekita ka seosetute ridade kohta mürarikkaid muudatusi.

### Sobitatud lõigu sees

Sobitatud vahemik asendatakse malliga, mis renderdatakse läbi PHP-Parser ilusa printeri; hõivatud sõlmed prinditakse uuesti, mitte ei kopeerita lähtekoodist. Praegu tähendab see järgmist ([#2](https://github.com/inline0/greph/issues/2)):

- Kommentaar, mis pole sobitatud lõigus seotud järgneva sõlmega, eemaldatakse; näiteks massiivi viimase elemendi järel olev kommentaar või muidu tühjas massiivis olev kommentaar.
- Hõivatud sõlmedega seotud kommentaarid säilivad, kuid liiguvad omaette reale; nii võib üherealine avaldis muutuda mitmerealiseks.
- Reavahetused sobitatud lõigus ei säili: mitmerealisest `array(...)`-st, milles pole selliseid kommentaare, saab üherealine `[...]`.
- Literaalide kirjapilt ühtlustatakse: `0x1F` muutub kujule `0x1f`, `1_000` muutub kujule `1000`, paojadad nagu `"\x41"` kirjutatakse märgina välja (`"A"`) ning paindliku taandega heredoc'id kaotavad oma taande.

```php
// before
$d = array(
    "a" => 1,  // trailing comment
);

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

Vaadake üle `--dry-run` väljund, kui vasted sisaldavad kommentaare või käsitsi vormindatud literaale.

## Mitmerealised ja pesastatud vasted

Mitmerealised mustrid toimivad samamoodi nagu üherealised. Asenduse renderdab ilus printer, kuhu metamärgid on asendatud, seega tuleb asendatud koodi paigutus printerist, mitte algsest vastest.

Greph kirjutab iga faili ümber ühe läbimisega ega rakenda kunagi kaht kattuvat vastet. See läbib vasteid faili lõpust alguse poole ja jätab vahele iga vaste, mis kattub juba ümberkirjutatuga. Erinevatest nihetest algavate pesastatud vastete korral kirjutatakse sisemine vaste ümber, välimine aga jäetakse muutmata:

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

Kui kaks vastet algavad samast nihkest (näiteks meetodite ahel, mille sobitab `$obj->$method($$$ARGS)`), kirjutatakse ümber välimine vaste. Kui muster võib pesastuda, käivitage ümberkirjutus uuesti, kuni `--dry-run` ei väljasta midagi ([#3](https://github.com/inline0/greph/issues/3)). ast-grep kirjutab kahest pesastatud vastest ümber välimise, seega annavad `vendor/bin/sg` ja ülesvoolu `sg` siin erinevad tulemused.

## Programmipõhine kasutus

```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()` ise faile ei kirjuta. Muudatuste kinnitamise (või nende vahelejätmise `--dry-run` ja tagasilükatud `--interactive` juhtudel) eest vastutab CLI. Nii saab fassaadi turvaliselt kutsuda automatiseeritud agentidest, kes soovivad enne rakendamist muudatuste erinevust kontrollida.

`RewriteResult` pakub:

- `file`: absoluutne tee
- `originalContents`: faili sisu enne ümberkirjutamist
- `rewrittenContents`: faili sisu pärast ümberkirjutamist
- `replacementCount`: struktuursete asenduste arv
- `changed()`: abifunktsioon, mis tagastab `true`, kui `originalContents !== rewrittenContents`

## Millal ümberkirjutamist kasutada

Rewrite on õige tööriist, kui:

- Teisendus on lokaalne ja struktuurne (mitte kogu faili ümberkorraldamine).
- Soovite deterministlikku ja korratavat refaktoreerimist, mida saab uuesti käivitada, kuni muudatusi enam ei toimu.
- Soovite muudatust enne rakendamist eelvaates näha.
- Soovite integreerida refaktoreerimise skripti või CI-töösse ilma välist binaarfaili käivitamata.

Rewrite pole õige tööriist, kui:

- Teisendus sõltub tüübiteabest, mida PHP-Parser ei saa ühe faili põhjal tuvastada.
- Teisendus peab faile ületades importimisi lisama või eemaldama.
- Soovite refaktoreerimise asemel vormindajat (kasutage PHP-CS-Fixer).
