Gå til indhold

AST-søgning

AST-tilstanden i Greph søger i PHP-kilde efter struktur, ikke tekst. Mønstre skrives som almindelig PHP-kode med metavariablerne $VARIABLE og $$$VARIADIC. Mønsteret parses af nikic/php-parser til samme AST-struktur som kilden, der søges i, og matches derefter node for node.

Det er samme model som ast-grep, og kompatibilitetsomslaget sg viser ast-greps overflade CLI oven på den samme motor.

Hurtige eksempler

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ønstersyntaks

Et mønster er gyldig PHP-kilde. Alle steder, hvor et rigtigt udtryk eller en sætning kan stå, kan du indsætte en metavariabel for at indfange den node.

Syntaks Matcher Eksempel
$VAR Enhver enkelt AST-node, indfanget som VAR $x + $y matcher foo() + bar
$_ Enhver enkelt AST-node, ikke indfanget $_->method() matcher enhver modtager
$$$ARGS Nul eller flere noder (variadisk), indfanget som ARGS func($$$ARGS) matcher func(), func(1), func(1, 2, 3)
$VAR (gentaget) Samme $VAR skal matche den samme struktur begge gange $x == $x matcher a == a, men ikke a == b

Metavariablens navn skrives traditionelt med store bogstaver, men det er ikke et krav. $_ er et jokertegn, der ikke indfanger noget.

$$$VARIADIC fungerer alle steder, hvor PHP tillader en liste af noder: funktionsargumenter, arrayelementer, parameterlister og sætningsblokke. Indfangningen gemmer hele den matchede sekvens (også når den indeholder nul elementer).

Metavariabler for identifikatorer

Klasse-, funktions-, interface-, trait- og enum-navne er identifikatorer, ikke udtryk, så PHP ville normalt ikke acceptere $Name efter class. Greph omskriver disse mønstre før parsing, så metavariablen stadig fungerer:

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

Den samme omskrivning sker for class, interface, trait, enum og function.

Gentagne metavariabler

Hvis en metavariabel bruges igen i det samme mønster, kræves det, at begge forekomster strukturelt matcher den samme node. Det er nyttigt til at finde overflødig kode:

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

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

Den samme regel gælder for $$$VARIADIC: to forekomster af $$$ARGS skal matche den samme sekvens.

Filtrering af filer

AST-tilstanden gennemgår filtræet med samme gennemløber som teksttilstanden og bruger som standard filtypefilteret php. Tilsidesæt det med --type, --type-not, --glob, --no-ignore og --hidden. Gennemløberen respekterer stadig .gitignore og .grephignore og springer binære filer over.

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

Output

Standardoutputtet bruger samme file:line:content-format som teksttilstanden, hvor den matchede kode er samlet på én linje:

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

Send --json for at generere strukturerede match:

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

Parsefejl

Som standard springer Greph lydløst filer over, der ikke kan parses. Det er den rette adfærd ved kørsel på tværs af en stor kodebase, der indeholder eksperimentel eller genereret PHP. Send --strict-parse (med greph-index ast-index search og greph-index ast-cache search) for i stedet at udløse parsefejlen, eller angiv skipParseErrors: false på AstSearchOptions, når du bruger facaden direkte.

Programmatisk brug

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 er den indfangede PhpParser\Node (så du kan gennemløbe den med PHP-Parsers NodeVisitor), og $match->captures knytter hvert metavariabelnavn til dets indfangede node eller liste af noder.

Sådan fungerer det

Forløbet er:

  1. Parsing af mønster: Greph\Ast\PatternParser omskriver metavariabler for identifikatorer og parser derefter mønsteret med PHP-Parser.
  2. Parsing af kilde: hver kandidatfil parses én gang. I tilstanden ast-cache erstattes dette trin af en deserialisering fra cachelageret.
  3. Filtrering af kandidater: Greph\Ast\AstPatternPrefilter udtrækker en grov signatur fra mønsteret (kaldnavn, klassenavn, sætningstype) og bruger den til at springe filer over, der tydeligvis ikke kan indeholde et match. I tilstanden ast-index slår dette trin i stedet op i et forudberegnet faktalager.
  4. Matchning: Greph\Ast\PatternMatcher gennemløber kilde-AST'et og sammenligner det med mønster-AST'et. Undervejs bindes metavariablerne, og det sikres, at gentagne variabler stemmer overens.
  5. Generering af resultater: matchede noder konverteres til AstMatch-objekter med linje- og bytepositioner samt det oprindelige udsnit af kilden.

Ved gentagne kørsler springer indekseret AST-søgning og cached AST-søgning de fleste dele af trin 2 og 3 over.