AST-herschrijven
AST-herschrijven is AST-zoeken met een vervangingssjabloon. Greph gebruikt dezelfde matcher als AST-zoeken en voegt de herschreven sjabloon vervolgens in de broncode in op de overeenkomende bytebereiken. Omringende witruimte, inspringing en opmerkingen blijven behouden; de overeenkomende code zelf wordt opnieuw afgedrukt (zie Binnen een overeenkomst).
Snelle voorbeelden
# 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
Vervangingssjablonen
De vervangingssjabloon gebruikt dezelfde metavariabelsyntaxis als het zoekpatroon. Vastgelegde knopen worden op naam ingevoegd:
| In het patroon | In de sjabloon | Effect |
|---|---|---|
$X |
$X |
De vastgelegde knoop invoegen |
$$$ARGS |
$$$ARGS |
De vastgelegde reeks knopen invoegen |
$NAME (identifier) |
$NAME |
De vastgelegde identifier invoegen |
Metavariabelen die wel in het patroon maar niet in de sjabloon voorkomen, worden uit de herschreven code verwijderd. Een metavariabele die wel in de sjabloon maar niet in het patroon voorkomt, wordt momenteel niet afgewezen: deze wordt letterlijk afgedrukt. -r '$o->$m($$$A, $EXTRA)' schrijft dus een variabele $EXTRA in de code (#4). Controleer sjablonen eerst met --dry-run.
Modi
| Vlag | Gedrag |
|---|---|
| (geen) | De herschrijving toepassen op elk overeenkomend bestand |
--dry-run |
De inhoud van het herschreven bestand naar stdout afdrukken, niet schrijven |
--interactive |
Vragen om Rewrite <file>? [y/N] voordat elk bestand wordt geschreven |
Standaard wordt alles wat verandert geschreven. Gebruik eerst --dry-run wanneer je met een patroon aan het werk bent; haal de vlag weg zodra je tevreden bent met het verschil.
Opmaak behouden
Greph maakt de bestandsopmaak niet opnieuw. De matcher legt het bytebereik van elke overeenkomst in de oorspronkelijke broncode vast en de herschrijver voegt de weergegeven sjabloon rechtstreeks in die bereiken in. Onaangeroerde code blijft precies zoals die was, waaronder:
- Inspringing en afsluitende witruimte
- Inline- en zelfstandige opmerkingen
- Indeling van lege regels
- Aanhalingstekens en samenvoeging van strings
- Stijl van PHP-openings- en sluittags
Dit is een bewuste afweging ten opzichte van volledige heropmaak: Greph ruimt code die niet is gematcht niet op, maar veroorzaakt ook geen onnodige verschillen op niet-gerelateerde regels.
Binnen een overeenkomst
Het overeenkomende bereik zelf wordt vervangen door de sjabloon die via de pretty printer van PHP-Parser is weergegeven. De vastgelegde knopen worden opnieuw afgedrukt in plaats van als brontekst gekopieerd. Momenteel betekent dit (#2):
- Een opmerking die niet is gekoppeld aan een volgende node binnen de match, wordt verwijderd, bijvoorbeeld een afsluitende opmerking na het laatste array-item of een opmerking in een verder lege array.
- Opmerkingen die zijn gekoppeld aan vastgelegde nodes blijven behouden, maar verplaatsen zich naar een eigen regel. Daardoor kan een expressie op één regel meerdere regels beslaan.
- Regeleinden binnen de match blijven niet behouden: een meerregelige
array(...)zonder dergelijke opmerkingen wordt een[...]op één regel. - De letterlijke schrijfwijze wordt genormaliseerd:
0x1Fwordt0x1f,1_000wordt1000, escape-sequenties zoals"\x41"worden uitgeschreven als het teken ("A"), en flexibele heredocs verliezen hun inspringing.
// before
$d = array(
"a" => 1, // trailing comment
);
// after -p 'array($$$ITEMS)' -r '[$$$ITEMS]'
$d = ["a" => 1];
Controleer de uitvoer van --dry-run op overeenkomsten met opmerkingen of handmatig opgemaakte letterlijke waarden.
Overeenkomsten over meerdere regels en geneste overeenkomsten
Patronen over meerdere regels werken hetzelfde als patronen op één regel. De vervanging wordt weergegeven door de pretty printer met daarin de ingevulde metavariabelen. De indeling van de vervangen code wordt dus door de printer bepaald, niet door de oorspronkelijke overeenkomst.
Greph herschrijft elk bestand in één doorgang en past nooit twee overlappende overeenkomsten toe. De overeenkomsten worden vanaf het einde van het bestand achterwaarts doorlopen; elke overeenkomst die overlapt met een al herschreven overeenkomst wordt overgeslagen. Bij geneste overeenkomsten die op verschillende offsets beginnen, wordt de binnenste overeenkomst herschreven en blijft de buitenste ongewijzigd:
// 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]];
Wanneer twee overeenkomsten op dezelfde offset beginnen (bijvoorbeeld een method chain die door $obj->$method($$$ARGS) wordt gematcht), wordt de buitenste overeenkomst herschreven. Als een patroon genest kan voorkomen, voer de herschrijving dan opnieuw uit totdat --dry-run niets afdrukt (#3). ast-grep herschrijft de buitenste van twee geneste overeenkomsten, dus vendor/bin/sg en de upstreamversie sg leveren hier verschillende resultaten op.
Programmatisch gebruik
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() schrijft zelf geen bestanden. De CLI is verantwoordelijk voor het doorvoeren van de wijzigingen (of het overslaan ervan bij --dry-run-gevallen en afgewezen gevallen met --interactive). Daardoor kan de facade veilig worden aangeroepen door geautomatiseerde agents die het verschil willen bekijken voordat ze het toepassen.
RewriteResult biedt:
file: absoluut padoriginalContents: de bestandsinhoud vóór het herschrijvenrewrittenContents: de bestandsinhoud na het herschrijvenreplacementCount: aantal structurele vervangingenchanged(): helper dietrueretourneert wanneeroriginalContents !== rewrittenContents
Wanneer u rewrite gebruikt
Rewrite is de juiste tool wanneer:
- De transformatie lokaal en structureel is (geen herstructurering van het hele bestand).
- U een deterministische, herhaalbare refactor wilt die u opnieuw kunt uitvoeren totdat er niets meer verandert.
- U de wijziging wilt bekijken voordat u deze toepast.
- U de refactor in een script of CI-taak wilt integreren zonder een extern binair bestand aan te roepen.
Rewrite is niet de juiste tool wanneer:
- De transformatie afhankelijk is van type-informatie die PHP-Parser niet uit één bestand kan afleiden.
- De transformatie imports over de bestandsgrens heen moet toevoegen of verwijderen.
- U een formatter wilt in plaats van een refactor (gebruik PHP-CS-Fixer).