
# AST-átírás

Az AST-átírás az AST-keresést helyettesítősablonnal egészíti ki. A Greph ugyanazt az illesztőt futtatja, mint az [AST-keresés](https://greph.inline0.dev/hu/docs/modes/ast), majd az átírt sablont a megtalált bájttartományokon visszailleszti a forrásba. A környező szóközök, behúzások és megjegyzések megmaradnak; a megtalált kód új formátumot kap (lásd: [Illesztésen belül](#inside-a-match)).

## Gyors példák

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

## Helyettesítősablonok

A helyettesítősablon ugyanazt a metaváltozó-szintaxist használja, mint a keresési minta. A rögzített csomópontokat név alapján illeszti vissza:

| A mintában | A sablonban | Hatás |
|---|---|---|
| `$X` | `$X` | A rögzített csomópont beszúrása |
| `$$$ARGS` | `$$$ARGS` | A rögzített csomópontsorozat beszúrása |
| `$NAME` (azonosító) | `$NAME` | A rögzített azonosító beszúrása |

A mintában szereplő, de a sablonból hiányzó metaváltozók kimaradnak az átírt kódból. A sablonban szereplő, de a mintából hiányzó metaváltozókat a rendszer jelenleg nem utasítja el: szó szerint kiírja őket, így a `-r '$o->$m($$$A, $EXTRA)'` egy `$EXTRA` változót ír a kódba ([#4](https://github.com/inline0/greph/issues/4)). Előbb a `--dry-run` segítségével ellenőrizze a sablonokat.

## Módok

| Jelző | Viselkedés |
|---|---|
| (nincs) | Az átírás alkalmazása minden illeszkedő fájlra |
| `--dry-run` | Az átírt fájl tartalmának kiírása a szabványos kimenetre, írás nélkül |
| `--interactive` | A rendszer megerősítést kér a `Rewrite <file>? [y/N]` felhasználótól minden fájl írása előtt |

Az alapértelmezés: „minden módosítás kiírása”. Minta finomításakor először használja a `--dry-run` jelzőt, majd távolítsa el, ha elégedett a különbséggel.

## Formátum megőrzése

A Greph nem formázza újra a fájlt. Az illesztő rögzíti minden találat bájttartományát az eredeti forrásban, az átíró pedig közvetlenül ezekbe a tartományokba illeszti be a megjelenített sablont. Az érintetlen kód pontosan úgy marad, ahogy volt, többek között:

- Behúzás és sorvégi szóközök
- Sorközi és önálló megjegyzések
- Üres sorok elrendezése
- Idézőjelek stílusa és összefűzés
- A PHP nyitó- és zárócímke-stílusa

Ez tudatos kompromisszum a teljes újraformázással szemben: a Greph nem „csinosítja ki” az illesztésben nem szereplő kódot, ugyanakkor nem okoz zajos különbségeket a nem érintett sorokban.

### Illesztésen belül

Magát a megtalált tartományt a PHP-Parser szépségformázóján keresztül megjelenített sablon váltja fel; a rögzített csomópontok új formátumot kapnak ahelyett, hogy forrásszövegként másolódnának be. Jelenleg ez a következőket jelenti ([#2](https://github.com/inline0/greph/issues/2)):

- A megjegyzés, amely nincs a találaton belüli következő csomóponthoz rendelve, kimarad; ilyen például a tömb utolsó eleme utáni záró megjegyzés vagy egy egyébként üres tömbben lévő megjegyzés.
- A rögzített csomópontokhoz rendelt megjegyzések megmaradnak, de külön sorba kerülnek; ettől egy egysoros kifejezés több sorossá válhat.
- A találaton belüli sortörések nem maradnak meg: az ilyen megjegyzések nélküli többsoros `array(...)` egysoros `[...]` lesz.
- A literálok írásmódja egységesül: a `0x1F` alakja `0x1f` lesz, a `1_000` alakja `1000`, az olyan escape-sorozatok pedig, mint a `"\x41"`, karakterként jelennek meg (`"A"`); a rugalmas heredocok elveszítik behúzásukat.

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

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

Ellenőrizze a `--dry-run` kimenetet, ha a találatok megjegyzéseket vagy kézzel formázott literálokat tartalmaznak.

## Többsoros és egymásba ágyazott találatok

A többsoros minták ugyanúgy működnek, mint az egysorosak. A szépségformázó a behelyettesített metaváltozókkal együtt jeleníti meg a helyettesítést, így az átírt kód elrendezését a formázó határozza meg, nem az eredeti találat.

A Greph fájlonként egy menetben végzi az átírást, és soha nem alkalmaz két egymást átfedő találatot. A találatokat a fájl végétől visszafelé járja be, és kihagyja azokat, amelyek átfednek egy már átírt találattal. Eltérő eltolásnál kezdődő, egymásba ágyazott találatoknál a belső találat íródik át, a külső pedig változatlan marad:

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

Ha két találat ugyanazon az eltoláson kezdődik (például ha egy metódusláncra a `$obj->$method($$$ARGS)` illeszkedik), a külső találat íródik át. Ha a minta egymásba ágyazható, ismételje meg az átírást addig, amíg a `--dry-run` már nem ír ki semmit ([#3](https://github.com/inline0/greph/issues/3)). A ast-grep a két egymásba ágyazott találat közül a külsőt írja át, ezért a `vendor/bin/sg` és a felsőbb rétegbeli `sg` itt eltérő eredményt ad.

## Programozott használat

```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);
}
```

A `Greph::rewriteAst()` maga nem ír fájlokat. A CLI felelős a módosítások véglegesítéséért (vagy kihagyásáért `--dry-run` és elutasított `--interactive` esetekben). Így a homlokzat biztonságosan hívható automatizált ügynökökből, amelyek alkalmazás előtt meg akarják vizsgálni a különbségeket.

`RewriteResult` a következőket teszi elérhetővé:

- `file`: abszolút elérési út
- `originalContents`: a fájl tartalma az átírás előtt
- `rewrittenContents`: a fájl tartalma az átírás után
- `replacementCount`: a szerkezeti cserék száma
- `changed()`: segédfüggvény, amely `originalContents !== rewrittenContents` esetén `true` értéket ad vissza

## Mikor használja az átírást

Az átírás a megfelelő eszköz, ha:

- Az átalakítás helyi és szerkezeti jellegű (nem az egész fájl átszervezéséről van szó).
- Determinisztikus, megismételhető refaktorálást szeretne, amelyet addig futtathat újra, amíg már nem történik változás.
- Szeretné megtekinteni a módosítást az alkalmazása előtt.
- Külső bináris meghívása nélkül szeretné beépíteni a refaktorálást egy szkriptbe vagy CI-feladatba.

Az átírás nem megfelelő eszköz, ha:

- Az átalakítás olyan típusinformációktól függ, amelyeket a PHP-Parser egyetlen fájlból nem tud feloldani.
- Az átalakításnak fájlhatáron átnyúlóan kell importokat hozzáadnia vagy eltávolítania.
- Refaktorálás helyett formázót szeretne használni (használja a PHP-CS-Fixer eszközt).
