
# Riscrittura AST

La riscrittura AST è una ricerca AST con un modello di sostituzione. Greph usa lo stesso motore di corrispondenza della [ricerca AST](https://greph.inline0.dev/it/docs/modes/ast), poi inserisce il modello riscritto nel sorgente agli intervalli di byte corrispondenti. Gli spazi circostanti, l’indentazione e i commenti vengono preservati; il codice corrispondente viene ristampato (vedere [All’interno di una corrispondenza](#inside-a-match)).

## Esempi rapidi

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

## Modelli di sostituzione

Il modello di sostituzione usa la stessa sintassi delle metavariabili del pattern di ricerca. I nodi acquisiti vengono reinseriti in base al nome:

| Nel pattern | Nel modello | Effetto |
|---|---|---|
| `$X` | `$X` | Inserisce il nodo acquisito |
| `$$$ARGS` | `$$$ARGS` | Inserisce la sequenza di nodi acquisita |
| `$NAME` (identificatore) | `$NAME` | Inserisce l’identificatore acquisito |

Le metavariabili presenti nel pattern ma assenti nel modello vengono eliminate dal codice riscritto. Al momento, una metavariabile presente nel modello ma assente nel pattern non viene rifiutata: viene stampata letteralmente, quindi `-r '$o->$m($$$A, $EXTRA)'` inserisce nel codice una variabile `$EXTRA` ([#4](https://github.com/inline0/greph/issues/4)). Verificate prima i modelli con `--dry-run`.

## Modalità

| Flag | Comportamento |
|---|---|
| (nessuno) | Applica la riscrittura a tutti i file corrispondenti |
| `--dry-run` | Stampa su stdout il contenuto dei file riscritti, senza scriverlo su disco |
| `--interactive` | Chiede `Rewrite <file>? [y/N]` prima di scrivere ogni file |

L’impostazione predefinita è «scrivi tutte le modifiche». Usate prima `--dry-run` mentre lavorate su un pattern, poi rimuovete il flag quando siete soddisfatti delle differenze.

## Conservazione della formattazione

Greph non riformatta il file. Il motore di corrispondenza registra l’intervallo di byte di ogni corrispondenza nel sorgente originale e il riscrittore inserisce direttamente in quegli intervalli il modello generato. Il codice non modificato resta esattamente com’era, inclusi:

- Indentazione e spazi finali
- Commenti inline e autonomi
- Disposizione delle righe vuote
- Stile delle virgolette nelle stringhe e concatenazione
- Stile dei tag di apertura/chiusura PHP

È un compromesso intenzionale rispetto alla riformattazione completa: Greph non riordina il codice che non ha trovato, ma evita anche di introdurre differenze superflue in righe non pertinenti.

### All’interno di una corrispondenza

L’intervallo corrispondente viene sostituito dal modello generato con il pretty printer di PHP-Parser; i nodi acquisiti vengono ristampati invece di essere copiati come testo sorgente. Al momento, questo significa ([#2](https://github.com/inline0/greph/issues/2)):

- Un commento non associato a un nodo successivo all’interno della corrispondenza viene eliminato, per esempio un commento finale dopo l’ultimo elemento di un array o un commento in un array altrimenti vuoto.
- I commenti associati ai nodi acquisiti vengono conservati, ma spostati su una riga propria; un’espressione su una sola riga può quindi occupare più righe.
- Le interruzioni di riga all’interno della corrispondenza non vengono conservate: un `array(...)` su più righe senza questi commenti diventa un `[...]` su una sola riga.
- La forma letterale viene normalizzata: `0x1F` diventa `0x1f`, `1_000` diventa `1000`, le sequenze di escape come `"\x41"` vengono scritte come carattere (`"A"`) e gli heredoc flessibili perdono l’indentazione.

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

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

Controllate l’output di `--dry-run` per le corrispondenze che contengono commenti o valori letterali formattati a mano.

## Corrispondenze su più righe e annidate

I pattern su più righe funzionano come quelli su una sola riga. La sostituzione viene generata dal pretty printer con le metavariabili inserite, quindi la disposizione del codice sostituito è determinata dal printer, non dalla corrispondenza originale.

Greph riscrive ogni file in un solo passaggio e non applica mai due corrispondenze sovrapposte. Esamina le corrispondenze a ritroso, dalla fine del file, e salta quelle che si sovrappongono a una già riscritta. Per le corrispondenze annidate che iniziano a offset diversi, viene riscritta quella interna e quella esterna resta com’era:

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

Quando due corrispondenze iniziano allo stesso offset (per esempio, una catena di metodi individuata da `$obj->$method($$$ARGS)`), viene riscritta la corrispondenza più esterna. Se un pattern può annidarsi, eseguite di nuovo la riscrittura finché `--dry-run` non produce più output ([#3](https://github.com/inline0/greph/issues/3)). ast-grep riscrive la corrispondenza più esterna tra due corrispondenze annidate, quindi `vendor/bin/sg` e `sg` upstream producono qui risultati diversi.

## Uso programmatico

```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()` non scrive direttamente i file. CLI è responsabile del commit delle modifiche (oppure di ignorarle nei casi `--dry-run` e di rifiuto `--interactive`). In questo modo, la facade può essere chiamata in sicurezza da agenti automatizzati che vogliono esaminare il diff prima di applicarlo.

`RewriteResult` espone:

- `file`: percorso assoluto
- `originalContents`: contenuto del file prima della riscrittura
- `rewrittenContents`: contenuto del file dopo la riscrittura
- `replacementCount`: numero di sostituzioni strutturali
- `changed()`: helper che restituisce `true` quando `originalContents !== rewrittenContents`

## Quando usare rewrite

Rewrite è lo strumento giusto quando:

- La trasformazione è locale e strutturale (non ristruttura l'intero file).
- Volete un refactoring deterministico e ripetibile, da rieseguire finché non cambia più nulla.
- Volete visualizzare l'anteprima delle modifiche prima di applicarle.
- Volete integrare il refactoring in uno script o in un job CI senza richiamare un binario esterno.

Rewrite non è lo strumento giusto quando:

- La trasformazione dipende da informazioni sui tipi che PHP-Parser non può ricavare da un singolo file.
- La trasformazione deve aggiungere o rimuovere import oltre i confini del file.
- Volete un formatter anziché un refactoring (usate PHP-CS-Fixer).
