
# Przepisywanie AST

Przepisywanie AST to wyszukiwanie AST z szablonem zastępczym. Greph używa tego samego mechanizmu dopasowywania co [wyszukiwanie AST](https://greph.inline0.dev/pl/docs/modes/ast), a następnie wstawia przepisany szablon do źródła w dopasowanych zakresach bajtów. Otaczające białe znaki, wcięcia i komentarze są zachowywane; sam dopasowany kod jest drukowany ponownie (zob. [Wewnątrz dopasowania](#inside-a-match)).

## Szybkie przykłady

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

## Szablony zastępcze

Szablon zastępczy używa tej samej składni metazmiennych co wzorzec wyszukiwania. Przechwycone węzły są wstawiane z powrotem według nazwy:

| We wzorcu | W szablonie | Efekt |
|---|---|---|
| `$X` | `$X` | Wstaw przechwycony węzeł |
| `$$$ARGS` | `$$$ARGS` | Wstaw przechwyconą sekwencję węzłów |
| `$NAME` (identyfikator) | `$NAME` | Wstaw przechwycony identyfikator |

Metazmienne występujące we wzorcu, ale nie w szablonie, są pomijane w przepisanym kodzie. Metazmienna występująca w szablonie, ale nie we wzorcu, nie jest obecnie odrzucana: zostaje wypisana dosłownie, więc `-r '$o->$m($$$A, $EXTRA)'` zapisuje zmienną `$EXTRA` w kodzie ([#4](https://github.com/inline0/greph/issues/4)). Najpierw sprawdź szablony za pomocą `--dry-run`.

## Tryby

| Flaga | Działanie |
|---|---|
| (brak) | Zastosuj przepisywanie do każdego pasującego pliku |
| `--dry-run` | Wypisz zawartość przepisanych plików na standardowe wyjście, ale niczego nie zapisuj |
| `--interactive` | Poproś o potwierdzenie `Rewrite <file>? [y/N]` przed zapisaniem każdego pliku |

Domyślnie „zapisuj wszystkie zmiany”. Najpierw użyj `--dry-run` podczas dopracowywania wzorca, a następnie usuń flagę, gdy uznasz różnice za odpowiednie.

## Zachowanie formatowania

Greph nie formatuje ponownie pliku. Mechanizm dopasowywania zapisuje zakres bajtów każdego dopasowania w oryginalnym źródle, a mechanizm przepisujący wstawia wyrenderowany szablon bezpośrednio w tych zakresach. Nietknięty kod pozostaje dokładnie taki jak wcześniej, w tym:

- Wcięcia i końcowe białe znaki
- Komentarze w wierszu i samodzielne
- Układ pustych wierszy
- Styl cudzysłowów i konkatenacji w ciągach znaków
- Styl znaczników otwierających i zamykających PHP

To celowy kompromis w porównaniu z pełnym formatowaniem: Greph nie „porządkuje” kodu, którego nie dopasowało, ale też nie wprowadza niepotrzebnych różnic w niezwiązanych wierszach.

### Wewnątrz dopasowania

Sam dopasowany zakres zostaje zastąpiony szablonem przetworzonym przez drukarkę AST w PHP-Parser; przechwycone węzły są drukowane ponownie, a nie kopiowane jako tekst źródłowy. Obecnie oznacza to ([#2](https://github.com/inline0/greph/issues/2)):

- Komentarz, który nie jest przypisany do kolejnego węzła wewnątrz dopasowania, zostaje usunięty, na przykład komentarz na końcu po ostatnim elemencie tablicy lub komentarz w pustej tablicy.
- Komentarze przypisane do przechwyconych węzłów są zachowywane, ale przenoszone do osobnego wiersza, co może zamienić jednowierszowe wyrażenie w kilka wierszy.
- Podziały wierszy wewnątrz dopasowania nie są zachowywane: wielowierszowe `array(...)` bez takich komentarzy staje się jednowierszowym `[...]`.
- Zapis literałów jest normalizowany: `0x1F` staje się `0x1f`, `1_000` staje się `1000`, sekwencje ucieczki takie jak `"\x41"` są zapisywane jako znak (`"A"`), a elastyczne heredoki tracą wcięcia.

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

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

Sprawdź wynik `--dry-run` pod kątem dopasowań zawierających komentarze lub ręcznie sformatowane literały.

## Dopasowania wielowierszowe i zagnieżdżone

Wzorce wielowierszowe działają tak samo jak jednowierszowe. Zastępnik jest renderowany przez drukarkę AST po podstawieniu metazmiennych, więc układ zastępowanego kodu pochodzi z drukarki, a nie z oryginalnego dopasowania.

Greph przepisuje każdy plik w jednym przebiegu i nigdy nie stosuje dwóch nakładających się dopasowań. Przechodzi po dopasowaniach od końca pliku do początku i pomija te, które nakładają się na już przepisane. W przypadku zagnieżdżonych dopasowań zaczynających się w różnych przesunięciach przepisuje dopasowanie wewnętrzne, a zewnętrzne pozostawia bez zmian:

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

Gdy dwa dopasowania zaczynają się na tej samej pozycji (na przykład łańcuch metod dopasowany przez `$obj->$method($$$ARGS)`), przepisywane jest dopasowanie zewnętrzne. Jeśli wzorzec może być zagnieżdżony, uruchamiaj przepisywanie ponownie, aż `--dry-run` niczego nie wypisze ([#3](https://github.com/inline0/greph/issues/3)). ast-grep przepisuje zewnętrzne z dwóch zagnieżdżonych dopasowań, więc `vendor/bin/sg` i nadrzędny `sg` dają tu różne wyniki.

## Użycie programowe

```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()` samodzielnie nie zapisuje plików. Za zatwierdzanie zmian (lub pomijanie ich w przypadkach `--dry-run` i odrzuconych `--interactive`) odpowiada CLI. Dzięki temu z fasady mogą bezpiecznie korzystać zautomatyzowani agenci, którzy chcą sprawdzić różnice przed zastosowaniem zmian.

`RewriteResult` udostępnia:

- `file`: ścieżka bezwzględna
- `originalContents`: zawartość pliku przed przepisaniem
- `rewrittenContents`: zawartość pliku po przepisaniu
- `replacementCount`: liczba zamian strukturalnych
- `changed()`: funkcja pomocnicza zwracająca `true`, gdy `originalContents !== rewrittenContents`

## Kiedy używać przepisywania

Przepisywanie sprawdzi się, gdy:

- Transformacja jest lokalna i strukturalna (nie obejmuje przebudowy całego pliku).
- Chcesz deterministycznego, powtarzalnego refaktoryzowania, które możesz ponawiać, aż nic się nie zmieni.
- Chcesz podejrzeć zmianę przed jej zastosowaniem.
- Chcesz zintegrować refaktoryzację ze skryptem lub zadaniem CI bez uruchamiania zewnętrznego pliku binarnego.

Przepisywanie nie sprawdzi się, gdy:

- Transformacja zależy od informacji o typach, których PHP-Parser nie potrafi ustalić na podstawie pojedynczego pliku.
- Transformacja wymaga dodania lub usunięcia importów poza granicami pliku.
- Potrzebujesz formatowania, a nie refaktoryzacji (użyj PHP-CS-Fixer).
