Ga naar de inhoud

AST-zoeken

De AST-modus van Greph doorzoekt PHP-broncode op basis van structuur, niet op basis van tekst. Patronen worden geschreven als gewone PHP-code met metavariabelen $VARIABLE en $$$VARIADIC. nikic/php-parser parseert het patroon tot dezelfde AST-vorm als de broncode die wordt doorzocht en vergelijkt ze vervolgens knoop voor knoop.

Dit is hetzelfde model als ast-grep, en de compatibiliteitswrapper sg biedt de ast-grep-interface CLI boven op dezelfde engine.

Snelle voorbeelden

Shell
# Find every constructor call
./vendor/bin/greph -p 'new $CLASS()' src

# Find every method call with any number of arguments
./vendor/bin/greph -p '$obj->$method($$$ARGS)' src

# Find old-style array() calls
./vendor/bin/greph -p 'array($$$ITEMS)' src

# Find void-returning functions
./vendor/bin/greph -p 'function $name($$$PARAMS): void {}' src

# Find isset ternaries that should be ??
./vendor/bin/greph -p 'isset($x) ? $x : $default' src

Patroonsyntaxis

Een patroon is geldige PHP-broncode. Overal waar een echte expressie of instructie kan staan, kun je een metavariabele invullen om die knoop vast te leggen.

Syntaxis Komt overeen met Voorbeeld
$VAR Elke afzonderlijke AST-knoop, vastgelegd als VAR $x + $y komt overeen met foo() + bar
$_ Elke afzonderlijke AST-knoop, niet vastgelegd $_->method() komt overeen met elke ontvanger
$$$ARGS Nul of meer knopen (variadisch), vastgelegd als ARGS func($$$ARGS) komt overeen met func(), func(1), func(1, 2, 3)
$VAR (herhaald) Dezelfde $VAR moet beide keren overeenkomen met dezelfde structuur $x == $x komt overeen met a == a, niet met a == b

De naam van de metavariabele is volgens afspraak in hoofdletters, maar dat hoeft niet. $_ is een jokerteken dat niets vastlegt.

$$$VARIADIC werkt overal waar PHP een lijst met knopen toestaat: functieargumenten, array-items, parameterlijsten en instructieblokken. De capture bevat de hele overeenkomende reeks, inclusief nul items.

Identifier-metavariabelen

Namen van klassen, functies, interfaces, traits en enums zijn identifiers, geen expressies. Daarom accepteert PHP normaal gesproken geen $Name na class. Greph herschrijft deze patronen voordat ze worden geparseerd, zodat de metavariabele toch werkt:

Shell
./vendor/bin/greph -p 'class $NAME extends BaseController {}' src
./vendor/bin/greph -p 'function $NAME(): void {}' src
./vendor/bin/greph -p 'interface $NAME {}' src

Dezelfde herschrijving vindt plaats voor class, interface, trait, enum en function.

Herhaalde metavariabelen

Als je een metavariabele binnen hetzelfde patroon opnieuw gebruikt, moeten beide voorkomens structureel met dezelfde knoop overeenkomen. Dit is handig om overbodige code op te sporen:

Shell
# Tautological comparisons
./vendor/bin/greph -p '$X == $X' src

# Redundant ternaries
./vendor/bin/greph -p '$X ? $X : null' src

Dezelfde regel geldt voor $$$VARIADIC: twee voorkomens van $$$ARGS moeten overeenkomen met dezelfde reeks.

Bestanden filteren

De AST-modus doorloopt de bestandsstructuur met dezelfde walker als de tekstmodus en gebruikt standaard het bestandstypefilter php. Overschrijf dit met --type, --type-not, --glob, --no-ignore en --hidden. De walker houdt nog steeds rekening met .gitignore en .grephignore en slaat binaire bestanden over.

Shell
./vendor/bin/greph -p 'new $CLASS()' --glob 'src/**/*.php' .
./vendor/bin/greph -p 'new $CLASS()' --type-not phpt src

Uitvoer

De standaarduitvoer gebruikt dezelfde file:line:content-indeling als de tekstmodus, waarbij de overeenkomende code op één regel wordt samengevouwen:

Text
src/Greph.php:38:return (new FileWalker())->walk($paths, $options);
src/Greph.php:49:$searcher = new TextSearcher();

Geef --json mee om overeenkomsten in een gestructureerd formaat uit te voeren:

JSON
[
  {
    "file": "src/Greph.php",
    "start_line": 38,
    "end_line": 38,
    "start_file_pos": 1234,
    "end_file_pos": 1252,
    "code": "new FileWalker()"
  }
]

Parseerfouten

Standaard slaat Greph bestanden die niet kunnen worden geparseerd stilzwijgend over. Dat is het juiste gedrag bij het doorzoeken van een grote codebase met experimentele of gegenereerde PHP. Geef --strict-parse mee (bij greph-index ast-index search en greph-index ast-cache search) om in plaats daarvan de parseerfout te melden, of stel skipParseErrors: false in op AstSearchOptions wanneer je de facade rechtstreeks gebruikt.

Programmatisch gebruik

PHP
use Greph\Greph;
use Greph\Ast\AstSearchOptions;

$matches = Greph::searchAst(
    '$obj->$method($$$ARGS)',
    'src',
    new AstSearchOptions(jobs: 4),
);

foreach ($matches as $match) {
    echo "{$match->file}:{$match->startLine}\n";
    echo $match->code . "\n";

    foreach ($match->captures as $name => $node) {
        echo "  {$name} = " . get_debug_type($node) . "\n";
    }
}

$match->node is de vastgelegde PhpParser\Node (zodat je erdoorheen kunt lopen met PHP-Parser-NodeVisitor), en $match->captures koppelt elke metavariabelenaam aan de vastgelegde knoop of lijst met knopen.

Werking

De pipeline bestaat uit:

  1. Patroon parsen: Greph\Ast\PatternParser herschrijft identifier-metavariabelen en parseert het patroon vervolgens met PHP-Parser.
  2. Broncode parsen: elk kandidaatbestand wordt één keer geparseerd. In de modus ast-cache wordt deze stap vervangen door deserialisatie uit de cacheopslag.
  3. Kandidaten filteren: Greph\Ast\AstPatternPrefilter haalt een grove signatuur uit het patroon (aanroepnaam, klassenaam, instructietype) en gebruikt deze om bestanden over te slaan die duidelijk geen overeenkomst kunnen bevatten. In de modus ast-index wordt in plaats daarvan een vooraf berekende feitenopslag doorzocht.
  4. Matchen: Greph\Ast\PatternMatcher doorloopt de AST van de broncode en vergelijkt deze met de AST van het patroon. Daarbij worden metavariabelen gebonden en wordt afgedwongen dat herhaalde variabelen overeenkomen.
  5. Resultaten uitvoeren: overeenkomende knopen worden omgezet in AstMatch-objecten met regel- en byteposities en het oorspronkelijke brondeel.

Bij herhaalde workloads slaan zoeken in een geïndexeerde AST en zoeken in een gecachte AST de meeste delen van stap 2 en 3 over.