Hoppa till innehåll

AST-sökning

Grephs AST-läge söker i PHP-källkod efter struktur, inte text. Mönstren skrivs som vanlig PHP-kod med metavariablerna $VARIABLE och $$$VARIADIC. Mönstret parsas av nikic/php-parser till samma AST-form som källkoden som genomsöks, och matchas sedan nod för nod.

Det här är samma modell som ast-grep, och kompatibilitetsomslaget sg exponerar ast-greps CLI-gränssnitt ovanpå samma motor.

Snabba exempel

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

Mönstersyntax

Ett mönster är giltig PHP-källkod. Överallt där ett riktigt uttryck eller en sats kan stå kan du ersätta det med en metavariabel som fångar upp den noden.

Syntax Matchar Exempel
$VAR Valfri enskild AST-nod, infångad som VAR $x + $y matchar foo() + bar
$_ Valfri enskild AST-nod, utan infångning $_->method() matchar vilken mottagare som helst
$$$ARGS Noll eller fler noder (variadisk), infångade som ARGS func($$$ARGS) matchar func(), func(1), func(1, 2, 3)
$VAR (upprepad) Samma $VAR måste matcha samma struktur båda gångerna $x == $x matchar a == a, inte a == b

Metavariabelnamnet skrivs enligt konvention med versaler, men det behöver inte göra det. $_ är ett jokertecken som inte fångas upp.

$$$VARIADIC fungerar där PHP tillåter en lista med noder: funktionsargument, arrayelement, parameterlistor och satskroppar. Infångningen lagrar hela den matchade sekvensen (även noll element).

Identifierarmetavariabler

Klass-, funktions-, gränssnitts-, trait- och enum-namn är identifierare, inte uttryck. Därför accepterar PHP normalt inte $Name efter class. Greph skriver om dessa mönster före parsningen så att metavariabeln fortfarande fungerar:

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

Samma omskrivning görs för class, interface, trait, enum och function.

Upprepade metavariabler

Om du återanvänder en metavariabel i samma mönster innebär det att båda förekomsterna måste matcha samma nod strukturellt. Det är användbart för att upptäcka redundant kod:

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

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

Samma regel gäller för $$$VARIADIC: två förekomster av $$$ARGS måste matcha samma sekvens.

Filtrera filer

AST-läget går igenom filträdet med samma genomgångare som textläget och använder som standard filtret för filtypen php. Åsidosätt det med --type, --type-not, --glob, --no-ignore och --hidden. Genomgångaren respekterar fortfarande .gitignore och .grephignore och hoppar över binärfiler.

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

Utdata

Standardutdata använder samma format file:line:content som textläget, med den matchade koden hopslagen till en enda rad:

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

Skicka med --json för att generera strukturerade matchningar:

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

Parsefel

Som standard hoppar Greph tyst över filer som inte går att parsa. Det är rätt beteende när du kör mot en stor kodbas med experimentell eller genererad PHP. Skicka med --strict-parse (med greph-index ast-index search och greph-index ast-cache search) för att i stället utlösa parsefelet, eller ange skipParseErrors: false för AstSearchOptions när du använder fasaden direkt.

Programmatisk användning

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 är det infångade PhpParser\Node (så att du kan gå igenom det med PHP-Parsers NodeVisitor), och $match->captures mappar varje metavariabelnamn till dess infångade nod eller lista med noder.

Så fungerar det

Kedjan består av:

  1. Mönsterparsning: Greph\Ast\PatternParser skriver om identifierarmetavariabler och parsar sedan mönstret med PHP-Parser.
  2. Källparsning: varje kandidatfil parsas en gång. I läget ast-cache ersätts det här steget med en deserialisering från cachen.
  3. Kandidatfiltrering: Greph\Ast\AstPatternPrefilter extraherar en grov signatur från mönstret (anropsnamn, klassnamn, satstyp) och använder den för att hoppa över filer som uppenbart inte kan innehålla en matchning. I läget ast-index görs i stället en sökning i ett förberäknat faktalager.
  4. Matchning: Greph\Ast\PatternMatcher går igenom källans AST och jämför den med mönstrets AST, binder metavariabler längs vägen och säkerställer att upprepade variabler stämmer överens.
  5. Resultatgenerering: matchade noder omvandlas till objekt av typen AstMatch med rad- och bytepositioner samt det ursprungliga källkodsstycket.

För upprepade körningar hoppar indexerad AST-sökning och cachad AST-sökning över de flesta delarna av steg 2 och 3.