AST-omskrivning
AST-omskrivning er AST-søgning med en erstatningsskabelon. Greph bruger samme matcher som AST-søgning 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).
Hurtige eksempler
# 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). 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):
- 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:
0x1Fbliver til0x1f,1_000bliver til1000, escape-sekvenser som"\x41"skrives som tegnet ("A"), og fleksible heredocs mister deres indrykning.
// 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 indsatte 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:
// 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). ast-grep omskriver det yderste af to indlejrede match, så vendor/bin/sg og den oprindelige sg giver forskellige resultater her.
Programmatisk brug
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 stioriginalContents: filens indhold før omskrivningenrewrittenContents: filens indhold efter omskrivningenreplacementCount: antal strukturelle udskiftningerchanged(): hjælper, der returnerertruenåroriginalContents !== 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).