
# AST-omskrivning

AST-omskrivning er AST-søgning med en erstatningsskabelon. Greph bruger samme matcher som [AST-søgning](https://greph.inline0.dev/da/docs/modes/ast) og indsætter derefter den omskrevne skabelon i kilden igen ved de matchede byteområder. Omkringliggende mellemrum, indrykning og kommentarer bevares; den matchede kode udskrives igen (se [Inde i et match](#inside-a-match)).

## Hurtige eksempler

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

## Erstatningsskabeloner

Erstatningsskabelonen bruger samme metavariabelsyntaks som søgemønsteret. Indfangede noder indsættes igen efter navn:

| I mønsteret | I skabelonen | Virkning |
|---|---|---|
| `$X` | `$X` | Indsæt den indfangede node |
| `$$$ARGS` | `$$$ARGS` | Indsæt den indfangede sekvens af noder |
| `$NAME` (identifikator) | `$NAME` | Indsæt den indfangede identifikator |

Metavariabler, der findes i mønsteret, men ikke i skabelonen, udelades fra den omskrevne kode. En metavariabel, der findes i skabelonen, men ikke i mønsteret, afvises ikke i øjeblikket: den udskrives bogstaveligt, så `-r '$o->$m($$$A, $EXTRA)'` skriver en variabel af typen `$EXTRA` ind i koden ([#4](https://github.com/inline0/greph/issues/4)). Kontrollér først skabeloner med `--dry-run`.

## Tilstande

| Flag | Adfærd |
|---|---|
| (ingen) | Anvend omskrivningen på alle filer med match |
| `--dry-run` | Udskriv det omskrevne filindhold til stdout; skriv ikke til filen |
| `--interactive` | Bed `Rewrite <file>? [y/N]` om at bekræfte før hver fil skrives |

Standardadfærden er "skriv alle ændringer". Brug først `--dry-run`, mens du arbejder på et mønster, og fjern derefter flaget, når du er tilfreds med forskellene.

## Bevarelse af formatering

Greph omformaterer ikke filen. Matcheren registrerer byteområdet for hvert match i den oprindelige kilde, og omskriveren indsætter den gengivne skabelon direkte i disse områder. Urørt kode forbliver præcis, som den var, herunder:

- Indrykning og efterfølgende mellemrum
- Indlejrede kommentarer og kommentarer på egne linjer
- Tomme linjers placering
- Strengens citationstegn og sammenkædning
- Stilen for åbnings- og lukketagget PHP

Det er et bevidst kompromis i forhold til fuld omformatering: Greph rydder ikke op i kode, der ikke blev matchet, men indfører heller ikke støjende forskelle i uvedkommende linjer.

### Inde i et match

Selve det matchede område erstattes af skabelonen, gengivet med PHP-Parsers pretty printer. De indfangede noder udskrives igen i stedet for at blive kopieret som kildetekst. Det betyder i øjeblikket ([#2](https://github.com/inline0/greph/issues/2)):

- En kommentar, der ikke er knyttet til en efterfølgende node inde i matchet, fjernes; det kan for eksempel være en efterfølgende kommentar efter det sidste arrayelement eller en kommentar i et ellers tomt array.
- Kommentarer, der er knyttet til indfangede noder, bevares, men flyttes til deres egen linje. Et udtryk på én linje kan derfor blive til flere linjer.
- Linjeskift inde i matchet bevares ikke: et flerlinjet `array(...)` uden sådanne kommentarer bliver til et etlinjet `[...]`.
- Bogstavelig skrivemåde normaliseres: `0x1F` bliver til `0x1f`, `1_000` bliver til `1000`, escape-sekvenser som `"\x41"` skrives som tegnet (`"A"`), og fleksible heredocs mister deres indrykning.

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

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

Gennemgå outputtet fra `--dry-run` for match, der indeholder kommentarer eller manuelt formaterede bogstavelige værdier.

## Flerlinjede og indlejrede match

Flerlinjede mønstre fungerer på samme måde som mønstre på én linje. Erstatningen gengives af pretty printeren med de indsat­te metavariabler, så layoutet af den erstattede kode kommer fra printeren, ikke fra det oprindelige match.

Greph omskriver hver fil i én gennemgang og anvender aldrig to overlappende match. Den gennemgår matchene baglæns fra slutningen af filen og springer alle match over, der overlapper et allerede omskrevet match. Ved indlejrede match, der begynder ved forskellige positioner, omskrives det inderste match, mens det yderste forbliver uændret:

```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 to match begynder ved samme position (for eksempel en metodekæde, der matches af `$obj->$method($$$ARGS)`), omskrives det yderste match i stedet. Hvis et mønster kan være indlejret, skal du køre omskrivningen igen, indtil `--dry-run` ikke udskriver noget ([#3](https://github.com/inline0/greph/issues/3)). ast-grep omskriver det yderste af to indlejrede match, så `vendor/bin/sg` og den oprindelige `sg` giver forskellige resultater her.

## Programmatisk brug

```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 ikke selv filer. CLI har ansvaret for at gennemføre ændringerne (eller springe dem over i tilfælde af `--dry-run` og afvist `--interactive`). Det gør facaden sikker at kalde fra automatiserede agenter, der vil inspicere forskellene, før de anvender ændringerne.

`RewriteResult` viser:

- `file`: absolut sti
- `originalContents`: filens indhold før omskrivningen
- `rewrittenContents`: filens indhold efter omskrivningen
- `replacementCount`: antal strukturelle udskiftninger
- `changed()`: hjælper, der returnerer `true` når `originalContents !== rewrittenContents`

## Hvornår skal rewrite bruges

Rewrite er det rette værktøj, når:

- Transformationen er lokal og strukturel (ikke en omstrukturering af hele filen).
- Du vil have en deterministisk, gentagelig refaktorering, som du kan køre igen, indtil intet ændres.
- Du vil forhåndsvise ændringen, før du anvender den.
- Du vil integrere refaktoreringen i et script eller CI-job uden at starte en ekstern binærfil.

Rewrite er ikke det rette værktøj, når:

- Transformationen afhænger af typeinformation, som PHP-Parser ikke kan udlede fra en enkelt fil.
- Transformationen kræver, at imports tilføjes eller fjernes på tværs af filgrænser.
- Du vil have en formattering frem for en refaktorering (brug PHP-CS-Fixer).
