
# AST-omskrivning

AST-omskrivning är AST-sökning med en ersättningsmall. Greph kör samma matchare som [AST-sökning](https://greph.inline0.dev/sv/docs/modes/ast) och infogar sedan den omskrivna mallen i källkoden vid matchningarnas byteintervall. Omgivande blanksteg, indrag och kommentarer bevaras; den matchade koden skrivs ut på nytt (se [Inuti en matchning](#inside-a-match)).

## Snabba exempel

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

## Ersättningsmallar

Ersättningsmallen använder samma metavariabelsyntax som sökmönstret. Infångade noder infogas igen utifrån namn:

| I mönstret | I mallen | Effekt |
|---|---|---|
| `$X` | `$X` | Infoga den infångade noden |
| `$$$ARGS` | `$$$ARGS` | Infoga den infångade nodsekvensen |
| `$NAME` (identifierare) | `$NAME` | Infoga den infångade identifieraren |

Metavariabler som finns i mönstret men inte i mallen tas bort från den omskrivna koden. I dag avvisas inte en metavariabel som finns i mallen men inte i mönstret: den skrivs ut bokstavligt, så `-r '$o->$m($$$A, $EXTRA)'` skriver in en variabel `$EXTRA` i koden ([#4](https://github.com/inline0/greph/issues/4)). Kontrollera mallar med `--dry-run` först.

## Lägen

| Flagga | Beteende |
|---|---|
| (ingen) | Tillämpa omskrivningen på alla matchande filer |
| `--dry-run` | Skriv ut den omskrivna filens innehåll till stdout utan att skriva till filen |
| `--interactive` | Fråga `Rewrite <file>? [y/N]` innan varje fil skrivs |

Standardläget är ”skriv allt som ändras”. Använd `--dry-run` först medan du justerar ett mönster och ta sedan bort flaggan när du är nöjd med diffen.

## Bevarande av format

Greph formaterar inte om filen. Matcharen registrerar byteintervallet för varje matchning i den ursprungliga källkoden, och omskrivaren infogar den renderade mallen direkt i dessa intervall. Oändrad kod förblir exakt som den var, inklusive:

- Indrag och avslutande blanksteg
- Kommentarer på samma rad och fristående kommentarer
- Placering av tomrader
- Strängarnas citationstecken och sammanfogning
- Stil för öppnings- och stängningstaggar i PHP

Det här är en avvägning som gjorts medvetet jämfört med fullständig omformatering: Greph ”städar” inte kod som den inte matchat, men skapar inte heller onödigt stora diffar på orelaterade rader.

### Inuti en matchning

Själva det matchade intervallet ersätts med mallen som renderas genom PHP-Parsers pretty printer, där de infångade noderna skrivs ut på nytt i stället för att kopieras som källtext. I dag innebär det följande ([#2](https://github.com/inline0/greph/issues/2)):

- En kommentar som inte är kopplad till en efterföljande nod inom matchningen tas bort, till exempel en avslutande kommentar efter det sista arrayelementet eller en kommentar i en annars tom array.
- Kommentarer som är kopplade till infångade noder behålls, men flyttas till en egen rad. Det kan göra att ett enradigt uttryck blir flera rader.
- Radbrytningar inom matchningen bevaras inte: en flerradig `array(...)` utan sådana kommentarer blir en enradig `[...]`.
- Literalernas stavning normaliseras: `0x1F` blir `0x1f`, `1_000` blir `1000`, escape-sekvenser som `"\x41"` skrivs ut som tecknet (`"A"`), och flexibel heredoc förlorar sitt indrag.

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

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

Granska utdata från `--dry-run` vid matchningar som innehåller kommentarer eller handformaterade literaler.

## Matchningar över flera rader och nästlade matchningar

Mönster över flera rader fungerar på samma sätt som mönster på en rad. Ersättningen renderas av pretty printern med metavariablerna insatta, så layouten för den ersatta koden kommer från printern, inte från den ursprungliga matchningen.

Greph skriver om varje fil i ett enda varv och tillämpar aldrig två överlappande matchningar. Den går igenom matchningarna baklänges från slutet av filen och hoppar över alla matchningar som överlappar en den redan skrivit om. Vid nästlade matchningar som börjar vid olika positioner skrivs den inre matchningen om, medan den yttre lämnas som den var:

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

När två matchningar börjar vid samma position (till exempel en metodkedja som matchas av `$obj->$method($$$ARGS)`) skrivs den yttre matchningen om i stället. När ett mönster kan nästlas kör du omskrivningen igen tills `--dry-run` inte skriver ut något ([#3](https://github.com/inline0/greph/issues/3)). ast-grep skriver om den yttre av två nästlade matchningar, så `vendor/bin/sg` och uppströms `sg` ger olika resultat här.

## Programmatisk användning

```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()` skriver inte filer själv. CLI ansvarar för att verkställa ändringarna (eller hoppa över dem vid `--dry-run` och avböjda `--interactive`-fall). Det gör fasaden säker att anropa från automatiserade agenter som vill granska diffen innan den tillämpas.

`RewriteResult` visar:

- `file`: absolut sökväg
- `originalContents`: filinnehållet före omskrivningen
- `rewrittenContents`: filinnehållet efter omskrivningen
- `replacementCount`: antal strukturella ersättningar
- `changed()`: hjälpfunktion som returnerar `true` när `originalContents !== rewrittenContents`

## När ska du använda rewrite

Rewrite är rätt verktyg när:

- Omvandlingen är lokal och strukturell (inte en omstrukturering av hela filen).
- Du vill ha en deterministisk, repeterbar refaktorering som du kan köra igen tills ingenting ändras.
- Du vill förhandsgranska ändringen innan du tillämpar den.
- Du vill integrera refaktoreringen i ett skript eller CI-jobb utan att anropa en extern binärfil.

Rewrite är inte rätt verktyg när:

- Omvandlingen är beroende av typinformation som PHP-Parser inte kan lösa från en enda fil.
- Omvandlingen behöver lägga till eller ta bort importer över filgränsen.
- Du vill ha en formatterare i stället för en refaktorering (använd PHP-CS-Fixer).
