
# Preoblikovanje AST

Preoblikovanje AST je iskanje AST s predlogo za zamenjavo. Greph uporablja isti mehanizem za ujemanje kot [iskanje AST](https://greph.inline0.dev/sl/docs/modes/ast), nato pa vstavi preoblikovano predlogo nazaj v izvorno kodo na obsegu ujemajočih se bajtov. Presledki, zamiki in komentarji v okolici se ohranijo; ujemajoča se koda pa se znova izpiše (glejte [Znotraj ujemanja](#inside-a-match)).

## Hitri primeri

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

## Predloge za zamenjavo

Predloga za zamenjavo uporablja enako sintakso metaspremenljivk kot iskalni vzorec. Zajeta vozlišča se po imenu znova vstavijo:

| V vzorcu | V predlogi | Učinek |
|---|---|---|
| `$X` | `$X` | Vstavi zajeto vozlišče |
| `$$$ARGS` | `$$$ARGS` | Vstavi zaporedje zajetih vozlišč |
| `$NAME` (identifikator) | `$NAME` | Vstavi zajeti identifikator |

Metaspremenljivke, ki so v vzorcu, ne pa tudi v predlogi, se iz izhodne kode izpustijo. Metaspremenljivka, ki je v predlogi, ne pa v vzorcu, trenutno ni zavrnjena: izpiše se dobesedno, zato `-r '$o->$m($$$A, $EXTRA)'` v kodo zapiše spremenljivko `$EXTRA` ([#4](https://github.com/inline0/greph/issues/4)). Predloge najprej preverite z `--dry-run`.

## Načini

| Zastavica | Delovanje |
|---|---|
| (brez) | Izvede preoblikovanje v vsaki datoteki z ujemanjem |
| `--dry-run` | Izpiše vsebino preoblikovane datoteke na stdout, ne da bi jo zapisala |
| `--interactive` | Pred zapisom vsake datoteke pozove `Rewrite <file>? [y/N]` |

Privzeto se »zapiše vse, kar se spremeni«. Pri prilagajanju vzorca najprej uporabite `--dry-run`, nato pa zastavico odstranite, ko ste zadovoljni z razliko.

## Ohranjanje oblikovanja

Greph datoteke ne preoblikuje. Mehanizem za ujemanje zabeleži bajtni obseg vsakega ujemanja v izvirni kodi, mehanizem za preoblikovanje pa izrisano predlogo vstavi neposredno na te obsege. Nedotaknjena koda ostane nespremenjena, vključno z:

- Zamikom in presledki na koncu vrstice
- Vstavljenimi in samostojnimi komentarji
- Postavitvijo praznih vrstic
- Slogom narekovajev in združevanjem nizov
- Slogom PHP začetnih in končnih oznak

To je nameren kompromis glede na popolno preoblikovanje: Greph ne bo »urejal« kode, ki je ni ujemal, hkrati pa ne bo povzročal nepreglednih razlik v nepovezanih vrsticah.

### Znotraj ujemanja

Sam ujemajoči se obseg se zamenja s predlogo, izrisano s tiskalnikom za lepo oblikovanje v PHP-Parser; zajeta vozlišča se znova izpišejo, namesto da bi se kopirala kot izvorno besedilo. Trenutno to pomeni ([#2](https://github.com/inline0/greph/issues/2)):

- Komentar, ki v ujemanju ni pripet na naslednje vozlišče, se izpusti; na primer končni komentar za zadnjim elementom tabele ali komentar v sicer prazni tabeli.
- Komentarji, pripeti na zajeta vozlišča, se ohranijo, vendar se prestavijo v svojo vrstico, zato se lahko enovrstični izraz raztegne čez več vrstic.
- Prelomi vrstic znotraj ujemanja se ne ohranijo: večvrstični `array(...)` brez takih komentarjev postane enovrstični `[...]`.
- Zapis literalov se poenoti: `0x1F` postane `0x1f`, `1_000` postane `1000`, ubežna zaporedja, kot je `"\x41"`, se izpišejo kot znak (`"A"`), prilagodljivi heredoc pa izgubijo zamik.

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

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

Preglejte izhod `--dry-run` za ujemanja, ki vsebujejo komentarje ali ročno oblikovane literale.

## Večvrstična in ugnezdena ujemanja

Večvrstični vzorci delujejo enako kot enovrstični. Zamenjavo izriše tiskalnik za lepo oblikovanje, pri čemer vanjo vstavi metaspremenljivke, zato postavitev zamenjane kode določi tiskalnik in ne izvirno ujemanje.

Greph vsako datoteko preoblikuje v enem prehodu in nikoli ne uporabi dveh prekrivajočih se ujemanj. Ujemanja pregleda od konca datoteke proti začetku in preskoči vsako ujemanje, ki se prekriva z že preoblikovanim. Pri ugnezdenih ujemanjih, ki se začnejo na različnih odmikih, se preoblikuje notranje ujemanje, zunanje pa ostane nespremenjeno:

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

Ko se dve ujemanji začneta na istem odmiku (na primer veriga metod, ki se ujema z `$obj->$method($$$ARGS)`), se prepiše zunanje ujemanje. Če se vzorec lahko gnezdi, ponovite prepisovanje, dokler `--dry-run` ne izpiše ničesar ([#3](https://github.com/inline0/greph/issues/3)). ast-grep prepiše zunanje od dveh gnezdenih ujemanj, zato `vendor/bin/sg` in nadrejeni `sg` tukaj ustvarita različna rezultata.

## Uporaba v kodi

```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()` sam ne zapisuje datotek. Za uveljavitev sprememb (ali njihovo preskočitev v primerih `--dry-run` in zavrnjenih primerih `--interactive`) je odgovoren CLI. Tako lahko fasado varno kličejo avtomatizirani agenti, ki želijo pregledati razliko, preden uveljavijo spremembe.

`RewriteResult` razkriva:

- `file`: absolutna pot
- `originalContents`: vsebina datoteke pred prepisom
- `rewrittenContents`: vsebina datoteke po prepisu
- `replacementCount`: število strukturnih zamenjav
- `changed()`: pomočnik, ki vrne `true`, kadar `originalContents !== rewrittenContents`

## Kdaj uporabiti Rewrite

Rewrite je prava izbira, kadar:

- Je preoblikovanje lokalno in strukturno (ne gre za preureditev celotne datoteke).
- Želite deterministično refaktoriranje, ki ga lahko ponavljate, dokler se nič več ne spremeni.
- Želite pred uveljavitvijo predogledati spremembo.
- Želite refaktoriranje vključiti v skript ali opravilo CI, ne da bi zagnali zunanjo binarno datoteko.

Rewrite ni prava izbira, kadar:

- Je preoblikovanje odvisno od informacij o tipih, ki jih PHP-Parser ne more razbrati iz ene same datoteke.
- Morate dodati ali odstraniti uvoze prek meja datotek.
- Želite oblikovalnik namesto orodja za refaktoriranje (uporabite PHP-CS-Fixer).
