
# Prepis AST

Prepis AST je vyhľadávanie v AST s náhradnou šablónou. Greph používa rovnaký porovnávač ako [vyhľadávanie v AST](https://greph.inline0.dev/sk/docs/modes/ast) a potom vloží prepísanú šablónu späť do zdrojového kódu v rozsahu bajtov zodpovedajúcom zhode. Okolitý prázdny priestor, odsadenie a komentáre sa zachovajú; zodpovedajúci kód sa vytlačí znova (pozrite si [V rámci zhody](#inside-a-match)).

## Rýchle príklady

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

## Náhradné šablóny

Náhradná šablóna používa rovnakú syntax metavariabilných premenných ako vzor vyhľadávania. Zachytené uzly sa podľa názvu vložia späť:

| Vo vzore | V šablóne | Výsledok |
|---|---|---|
| `$X` | `$X` | Vloží zachytený uzol |
| `$$$ARGS` | `$$$ARGS` | Vloží zachytenú sekvenciu uzlov |
| `$NAME` (identifikátor) | `$NAME` | Vloží zachytený identifikátor |

Metavariabilné premenné, ktoré sa nachádzajú vo vzore, ale nie v šablóne, sa z prepísaného kódu vynechajú. Metavariabilná premenná, ktorá sa nachádza v šablóne, ale nie vo vzore, sa zatiaľ neodmieta: vypíše sa doslova, takže `-r '$o->$m($$$A, $EXTRA)'` zapíše do kódu premennú `$EXTRA` ([#4](https://github.com/inline0/greph/issues/4)). Šablóny najprv skontrolujte pomocou `--dry-run`.

## Režimy

| Príznak | Správanie |
|---|---|
| (žiadny) | Použije prepis vo všetkých súboroch so zhodou |
| `--dry-run` | Vypíše obsah prepísaného súboru do štandardného výstupu, nezapisuje ho |
| `--interactive` | Pred zápisom každého súboru sa opýta `Rewrite <file>? [y/N]` |

Predvolené správanie je „zapísať všetky zmeny“. Pri dolaďovaní vzoru najprv použite `--dry-run` a po spokojnosti s rozdielom tento príznak odstráňte.

## Zachovanie formátovania

Greph súbor nepreformátuje. Porovnávač zaznamená rozsah bajtov každej zhody v pôvodnom zdroji a náhradný nástroj vloží vykreslenú šablónu priamo do týchto rozsahov. Nedotknutý kód zostane presne taký, aký bol, vrátane:

- Odsadenia a koncových medzier
- Vnorených a samostatných komentárov
- Rozloženia prázdnych riadkov
- Štýlu úvodzoviek a spájania reťazcov
- Štýlu otváracej a zatváracej značky PHP

Je to zámerný kompromis oproti úplnému preformátovaniu: Greph neupraví kód, ktorému nezodpovedal, ale zároveň nevytvorí zbytočné rozdiely v nesúvisiacich riadkoch.

### V rámci zhody

Samotný zodpovedajúci rozsah sa nahradí šablónou vykreslenou pomocou tlačového formátovača PHP-Parser; zachytené uzly sa vytlačia znova namiesto skopírovania zdrojového textu. Momentálne to znamená ([#2](https://github.com/inline0/greph/issues/2)):

- Komentár, ktorý nie je priradený k nasledujúcemu uzlu v rámci zhody, sa zahodí, napríklad koncový komentár za poslednou položkou poľa alebo komentár v inak prázdnom poli.
- Komentáre priradené k zachyteným uzlom sa zachovajú, ale presunú sa na vlastný riadok, čo môže zmeniť jednoriadkový výraz na niekoľko riadkov.
- Zalomenia riadkov v rámci zhody sa nezachovajú: viacriadkové `array(...)` bez takýchto komentárov sa zmení na jednoriadkové `[...]`.
- Zápis literálov sa normalizuje: `0x1F` sa zmení na `0x1f`, `1_000` sa zmení na `1000`, escape sekvencie, napríklad `"\x41"`, sa vypíšu ako znak (`"A"`) a flexibilné heredoc bloky stratia svoje odsadenie.

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

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

Výstup `--dry-run` skontrolujte pri zhodách obsahujúcich komentáre alebo ručne formátované literály.

## Viacriadkové a vnorené zhody

Viacriadkové vzory fungujú rovnako ako jednoradové. Náhradu vykreslí formátovač po dosadení metapremenných, takže výsledné rozloženie kódu určuje formátovač, nie pôvodná zhoda.

Greph prepíše každý súbor v jednom priechode a nikdy nepoužije dve prekrývajúce sa zhody. Prechádza zhody od konca súboru smerom dozadu a preskočí každú zhodu, ktorá sa prekrýva s už prepísanou. Pri vnorených zhodách s rôznymi počiatočnými pozíciami sa prepíše vnútorná zhoda a vonkajšia zostane nezmenená:

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

Ak dve zhody začínajú na rovnakej pozícii (napríklad reťazec metód zachytený pomocou `$obj->$method($$$ARGS)`), prepíše sa vonkajšia zhoda. Ak sa vzor môže vyskytovať vnútorne, opakujte prepis, kým `--dry-run` nič nevypíše ([#3](https://github.com/inline0/greph/issues/3)). ast-grep prepíše vonkajšiu z dvoch vnorených zhôd, takže `vendor/bin/sg` a upstream `sg` tu vytvoria odlišné výsledky.

## Programové použitie

```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()` samo súbory nezapisuje. Za potvrdenie zmien (alebo ich preskočenie v prípadoch `--dry-run` a zamietnutých `--interactive`) zodpovedá CLI. Vďaka tomu možno túto fasádu bezpečne volať z automatizovaných agentov, ktoré chcú pred použitím zmien skontrolovať rozdiely.

`RewriteResult` poskytuje:

- `file`: absolútna cesta
- `originalContents`: obsah súboru pred prepisom
- `rewrittenContents`: obsah súboru po prepise
- `replacementCount`: počet štrukturálnych náhrad
- `changed()`: pomocná funkcia, ktorá vráti `true`, keď `originalContents !== rewrittenContents`

## Kedy použiť Rewrite

Rewrite je vhodný nástroj, keď:

- Transformácia je lokálna a štrukturálna (nejde o preusporiadanie celého súboru).
- Chcete deterministický a opakovateľný refaktoring, ktorý môžete znova spúšťať, kým sa nič nezmení.
- Chcete si zmenu pred použitím prezrieť.
- Chcete refaktoring integrovať do skriptu alebo úlohy CI bez spúšťania externého binárneho súboru.

Rewrite nie je vhodný nástroj, keď:

- Transformácia závisí od informácií o typoch, ktoré PHP-Parser nedokáže určiť z jediného súboru.
- Transformácia vyžaduje pridanie alebo odstránenie importov naprieč hranicami súboru.
- Chcete formátovač namiesto refaktoringu (použite PHP-CS-Fixer).
