
# Rescriere AST

Rescrierea AST înseamnă căutare AST cu un șablon de înlocuire. Greph folosește același mecanism de potrivire ca [căutarea AST](https://greph.inline0.dev/ro/docs/modes/ast), apoi inserează șablonul rescris în sursă la intervalele de octeți potrivite. Spațiile albe din jur, indentarea și comentariile sunt păstrate; codul potrivit este reimprimat (consultați [În interiorul unei potriviri](#inside-a-match)).

## Exemple rapide

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

## Șabloane de înlocuire

Șablonul de înlocuire folosește aceeași sintaxă a metavariabilelor ca tiparul de căutare. Nodurile capturate sunt inserate după nume:

| În tipar | În șablon | Efect |
|---|---|---|
| `$X` | `$X` | Inserează nodul capturat |
| `$$$ARGS` | `$$$ARGS` | Inserează secvența de noduri capturată |
| `$NAME` (identificator) | `$NAME` | Inserează identificatorul capturat |

Metavariabilele care apar în tipar, dar nu și în șablon, sunt eliminate din codul rescris. O metavariabilă care apare în șablon, dar nu și în tipar, nu este respinsă deocamdată: este afișată literal, așa că `-r '$o->$m($$$A, $EXTRA)'` scrie o variabilă `$EXTRA` în cod ([#4](https://github.com/inline0/greph/issues/4)). Verificați mai întâi șabloanele cu `--dry-run`.

## Moduri

| Opțiune | Comportament |
|---|---|
| (niciuna) | Aplică rescrierea tuturor fișierelor potrivite |
| `--dry-run` | Afișează în stdout conținutul rescris al fișierului, fără să-l scrie |
| `--interactive` | Solicită confirmarea pentru `Rewrite <file>? [y/N]` înainte de a scrie fiecare fișier |

În mod implicit, se aplică „scrie tot ce se schimbă”. Folosiți mai întâi `--dry-run` când ajustați un tipar, apoi eliminați opțiunea când sunteți mulțumit de diferențe.

## Păstrarea formatării

Greph nu reformatează fișierul. Mecanismul de potrivire înregistrează intervalul de octeți al fiecărei potriviri din sursa originală, iar rescrierea inserează șablonul rezultat direct în aceste intervale. Codul neatins rămâne exact la fel, inclusiv:

- Indentarea și spațiile albe de la sfârșitul liniilor
- Comentariile inline și cele autonome
- Dispunerea liniilor goale
- Stilul ghilimelelor și concatenarea șirurilor
- Stilul etichetelor de deschidere/închidere PHP

Acesta este un compromis intenționat față de reformatarea completă: Greph nu va „aranja” codul pe care nu l-a potrivit, dar nici nu va introduce diferențe inutile în linii fără legătură.

### În interiorul unei potriviri

Intervalul potrivit este înlocuit de șablonul redat prin imprimanta elegantă a PHP-Parser, iar nodurile capturate sunt reimprimate, nu copiate ca text sursă. Deocamdată, asta înseamnă ([#2](https://github.com/inline0/greph/issues/2)):

- Un comentariu care nu este atașat unui nod următor din potrivire este eliminat, de exemplu un comentariu la sfârșitul unui tablou sau unul dintr-un tablou gol.
- Comentariile atașate nodurilor capturate sunt păstrate, dar mutate pe propria linie; o expresie de o singură linie poate ajunge să ocupe mai multe linii.
- Sfârșiturile de linie din potrivire nu sunt păstrate: un `array(...)` pe mai multe linii, fără astfel de comentarii, devine un `[...]` pe o singură linie.
- Forma literalilor este normalizată: `0x1F` devine `0x1f`, `1_000` devine `1000`, secvențele de escape precum `"\x41"` sunt scrise ca acel caracter (`"A"`), iar heredoc-urile flexibile își pierd indentarea.

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

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

Revizuiți rezultatul `--dry-run` pentru potriviri care conțin comentarii sau literali formatați manual.

## Potriviri pe mai multe linii și imbricate

Tiparele pe mai multe linii funcționează la fel ca cele pe o singură linie. Înlocuirea este redată de imprimanta elegantă cu metavariabilele substituite, așa că dispunerea codului înlocuit este stabilită de imprimantă, nu de potrivirea originală.

Greph rescrie fiecare fișier dintr-o singură trecere și nu aplică niciodată două potriviri suprapuse. Parcurge potrivirile de la sfârșitul fișierului spre început și omite orice potrivire care se suprapune cu una deja rescrisă. Pentru potriviri imbricate care încep la poziții diferite, rescrie potrivirea interioară și o lasă neschimbată pe cea exterioară:

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

Când două potriviri încep la aceeași poziție (de exemplu, un lanț de metode potrivit de `$obj->$method($$$ARGS)`), este rescrisă potrivirea exterioară. Când un tipar se poate imbrica, rulați rescrierea din nou până când `--dry-run` nu mai afișează nimic ([#3](https://github.com/inline0/greph/issues/3)). ast-grep rescrie potrivirea exterioară dintre două potriviri imbricate, așa că `vendor/bin/sg` și `sg` din amonte produc aici rezultate diferite.

## Utilizare programatică

```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()` nu scrie fișiere de unul singur. CLI este responsabil de aplicarea modificărilor (sau de omiterea lor în cazurile `--dry-run` și `--interactive` refuzate). Astfel, fațada poate fi apelată în siguranță de agenți automatizați care vor să inspecteze diferențele înainte de a le aplica.

`RewriteResult` expune:

- `file`: cale absolută
- `originalContents`: conținutul fișierului înainte de rescriere
- `rewrittenContents`: conținutul fișierului după rescriere
- `replacementCount`: numărul de înlocuiri structurale
- `changed()`: funcție ajutătoare care returnează `true` când `originalContents !== rewrittenContents`

## Când să folosiți rescrierea

Rescrierea este instrumentul potrivit când:

- Transformarea este locală și structurală (nu restructurarea întregului fișier).
- Doriți o refactorizare deterministă și repetabilă, pe care să o puteți relua până când nu se mai schimbă nimic.
- Doriți să previzualizați modificarea înainte de a o aplica.
- Doriți să integrați refactorizarea într-un script sau într-o sarcină CI fără a invoca un binar extern.

Rescrierea nu este instrumentul potrivit când:

- Transformarea depinde de informații despre tipuri pe care PHP-Parser nu le poate determina dintr-un singur fișier.
- Transformarea trebuie să adauge sau să elimine importuri dincolo de limitele fișierului.
- Doriți un formatter, nu o refactorizare (folosiți PHP-CS-Fixer).
