Ga naar de inhoud

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

Shell
# 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: 0x1F wordt 0x1f, 1_000 wordt 1000, escape-sequenties zoals "\x41" worden uitgeschreven als het teken ("A"), en flexibele heredocs verliezen hun inspringing.
PHP
// 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:

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

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

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() 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 pad
  • originalContents: de bestandsinhoud vóór het herschrijven
  • rewrittenContents: de bestandsinhoud na het herschrijven
  • replacementCount: aantal structurele vervangingen
  • changed(): helper die true retourneert wanneer originalContents !== 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).