
# Přepis AST

Přepis AST je vyhledávání AST s šablonou náhrady. Greph spustí stejný porovnávací modul jako [vyhledávání AST](https://greph.inline0.dev/cs/docs/modes/ast) a poté vloží přepsanou šablonu do zdroje v rozsazích odpovídajících bajtů. Okolní mezery, odsazení a komentáře zůstanou zachovány; odpovídající kód se vytiskne znovu (viz [Uvnitř shody](#inside-a-match)).

## Rychlé pří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
```

## Šablony náhrady

Šablona náhrady používá stejnou syntaxi metavariables jako vyhledávací vzor. Zachycené uzly se vkládají zpět podle názvu:

| Ve vzoru | V šabloně | Výsledek |
|---|---|---|
| `$X` | `$X` | Vloží zachycený uzel |
| `$$$ARGS` | `$$$ARGS` | Vloží zachycenou sekvenci uzlů |
| `$NAME` (identifikátor) | `$NAME` | Vloží zachycený identifikátor |

Metavariables, které se objeví ve vzoru, ale ne v šabloně, se z přepsaného kódu odstraní. Metavariable, která se objeví v šabloně, ale ne ve vzoru, se zatím neodmítá: vypíše se doslova, takže `-r '$o->$m($$$A, $EXTRA)'` zapíše do kódu proměnnou `$EXTRA` ([#4](https://github.com/inline0/greph/issues/4)). Šablony nejprve zkontrolujte pomocí `--dry-run`.

## Režimy

| Příznak | Chování |
|---|---|
| (žádný) | Použije přepis na každý soubor, který odpovídá |
| `--dry-run` | Vypíše obsah přepsaného souboru na stdout, ale nezapíše ho |
| `--interactive` | Před zápisem každého souboru se zeptá `Rewrite <file>? [y/N]` |

Výchozí nastavení znamená „zapsat všechny změny“. Při úpravách vzoru nejprve použijte `--dry-run` a až budete s rozdílem spokojeni, příznak odeberte.

## Zachování formátu

Greph soubor nepřeformátuje. Porovnávací modul zaznamená rozsah bajtů každé shody v původním zdroji a přepisovací modul vloží vykreslenou šablonu přímo do těchto rozsahů. Nedotčený kód zůstane přesně tak, jak byl, včetně:

- Odsazení a koncových mezer
- Řádkových i samostatných komentářů
- Rozložení prázdných řádků
- Stylu uvozovek a spojování řetězců
- Stylu otevíracích a uzavíracích značek PHP

Jde o záměrný kompromis oproti úplnému přeformátování: Greph neupraví kód, který nenašel, ale zároveň nevytváří zbytečně rozsáhlé rozdíly na nesouvisejících řádcích.

### Uvnitř shody

Samotný odpovídající rozsah se nahradí šablonou vykreslenou pomocí formátovače PHP-Parser; zachycené uzly se vytisknou znovu místo kopírování zdrojového textu. V současnosti to znamená ([#2](https://github.com/inline0/greph/issues/2)):

- Komentář, který uvnitř shody není připojen k následujícímu uzlu, se odstraní; například koncový komentář za poslední položkou pole nebo komentář v jinak prázdném poli.
- Komentáře připojené k zachyceným uzlům zůstanou zachovány, ale přesunou se na samostatný řádek, takže se může jednořádkový výraz změnit na několik řádků.
- Zalomení řádků uvnitř shody se nezachovají: víceřádkové `array(...)` bez takových komentářů se změní na jednořádkové `[...]`.
- Zápis literálů se sjednotí: `0x1F` se změní na `0x1f`, `1_000` na `1000`, escape sekvence jako `"\x41"` se vypíšou jako znak (`"A"`) a flexibilní heredocy ztratí odsazení.

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

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

U výstupu `--dry-run` zkontrolujte shody obsahující komentáře nebo ručně formátované literály.

## Víceřádkové a vnořené shody

Víceřádkové vzory fungují stejně jako jednořádkové. Náhradu vykreslí formátovač po dosazení metavariables, takže rozložení nahrazeného kódu určuje formátovač, nikoli původní shoda.

Greph přepíše každý soubor v jediném průchodu a nikdy nepoužije dvě překrývající se shody. Shody prochází od konce souboru směrem zpět a přeskočí každou shodu, která se překrývá s již provedeným přepisem. U vnořených shod s různými počátečními pozicemi se přepíše vnitřní shoda a vnější zůstane beze změny:

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

Když dvě shody začínají na stejné pozici (například řetězec metod odpovídající `$obj->$method($$$ARGS)`), přepíše se vnější shoda. Pokud lze vzor vnořovat, spouštějte přepis opakovaně, dokud `--dry-run` nic nevypíše ([#3](https://github.com/inline0/greph/issues/3)). ast-grep přepíše vnější ze dvou vnořených shod, takže `vendor/bin/sg` a upstream verze `sg` zde vrátí odlišné výsledky.

## Programové použití

```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()` soubory samo nezapisuje. Za potvrzení změn (nebo jejich přeskočení v případech `--dry-run` a odmítnutí `--interactive`) odpovídá CLI. Díky tomu lze fasádu bezpečně volat z automatizovaných agentů, kteří chtějí nejprve zkontrolovat rozdíl a teprve potom změny použít.

`RewriteResult` zpřístupňuje:

- `file`: absolutní cestu
- `originalContents`: obsah souboru před přepsáním
- `rewrittenContents`: obsah souboru po přepsání
- `replacementCount`: počet strukturálních náhrad
- `changed()`: pomocnou funkci, která vrací `true`, když `originalContents !== rewrittenContents`

## Kdy použít rewrite

Rewrite je správný nástroj, když:

- Transformace je lokální a strukturální (nejde o přeuspořádání celého souboru).
- Chcete deterministický, opakovatelný refaktor, který můžete spouštět znovu, dokud se nic nezmění.
- Chcete si změnu před použitím prohlédnout.
- Chcete refaktor začlenit do skriptu nebo úlohy CI bez spouštění externího binárního souboru.

Rewrite není správný nástroj, když:

- Transformace závisí na informacích o typech, které PHP-Parser nedokáže zjistit z jediného souboru.
- Transformace potřebuje přidat nebo odebrat importy napříč hranicí souboru.
- Potřebujete formátovač, nikoli refaktor (použijte PHP-CS-Fixer).
