Iskanje AST
Način AST v Greph išče v izvorni kodi PHP po strukturi, ne po besedilu. Vzorci so zapisani kot običajna koda PHP z metaspremenljivkami $VARIABLE in $$$VARIADIC. nikic/php-parser vzorec razčleni v enako obliko AST kot izvorno kodo, po kateri išče, nato pa primerja vozlišče za vozliščem.
Gre za enak model kot pri ast-grep, združljivostni ovoj sg pa na istem mehanizmu ponuja vmesnik ast-grepCLI.
Hitri primeri
# 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
Sintaksa vzorcev
Vzorec je veljavna izvorna koda PHP. Kjer koli bi lahko zapisali pravi izraz ali stavek, lahko za zajem tega vozlišča uporabite metaspremenljivko.
| Sintaksa | Ujema se z | Primer |
|---|---|---|
$VAR |
Katerim koli posameznim vozliščem AST, zajetim kot VAR |
$x + $y se ujema z foo() + bar |
$_ |
Katerim koli posameznim vozliščem AST, brez zajema | $_->method() se ujema s katerim koli sprejemnikom |
$$$ARGS |
Nič ali več vozlišč (variadično), zajetih kot ARGS |
func($$$ARGS) se ujema z func(), func(1), func(1, 2, 3) |
$VAR (ponovljeno) |
Isti $VAR se mora obakrat ujemati z isto strukturo |
$x == $x se ujema z a == a, ne pa z a == b |
Ime metaspremenljivke je po dogovoru zapisano z velikimi črkami, vendar ni nujno. $_ je nadomestni znak brez zajema.
$$$VARIADIC deluje povsod, kjer PHP dovoljuje seznam vozlišč: v argumentih funkcij, elementih tabel, seznamih parametrov in telesih stavkov. Zajem shrani celotno ujemajoče se zaporedje (tudi če ne vsebuje nobenega elementa).
Metaspremenljivke identifikatorjev
Imena razredov, funkcij, vmesnikov, traitov in enumov so identifikatorji, ne izrazi, zato PHP običajno ne bi sprejel $Name za class. Greph pred razčlenitvijo preoblikuje te vzorce, zato metaspremenljivka še vedno deluje:
./vendor/bin/greph -p 'class $NAME extends BaseController {}' src
./vendor/bin/greph -p 'function $NAME(): void {}' src
./vendor/bin/greph -p 'interface $NAME {}' src
Enaka preobrazba se izvede za class, interface, trait, enum in function.
Ponavljajoče se metaspremenljivke
Ponovna uporaba metaspremenljivke v istem vzorcu pomeni, da se morata obe pojavitvi strukturno ujemati z istim vozliščem. To je uporabno za odkrivanje odvečne kode:
# Tautological comparisons
./vendor/bin/greph -p '$X == $X' src
# Redundant ternaries
./vendor/bin/greph -p '$X ? $X : null' src
Isto pravilo velja za $$$VARIADIC: dve pojavitvi $$$ARGS se morata ujemati z istim zaporedjem.
Filtriranje datotek
Način AST pregleda drevo datotek z istim sprehajalnikom kot besedilni način in kot privzeti filter vrst datotek uporablja php. Preglasite ga z možnostmi --type, --type-not, --glob, --no-ignore in --hidden. Spraševalnik še vedno upošteva .gitignore in .grephignore ter preskoči binarne datoteke.
./vendor/bin/greph -p 'new $CLASS()' --glob 'src/**/*.php' .
./vendor/bin/greph -p 'new $CLASS()' --type-not phpt src
Izhod
Privzeti izhod uporablja enako obliko file:line:content kot besedilni način, pri čemer je ujemajoča se koda strnjena v eno vrstico:
src/Greph.php:38:return (new FileWalker())->walk($paths, $options);
src/Greph.php:49:$searcher = new TextSearcher();
Podajte --json za izpis strukturiranih ujemanj:
[
{
"file": "src/Greph.php",
"start_line": 38,
"end_line": 38,
"start_file_pos": 1234,
"end_file_pos": 1252,
"code": "new FileWalker()"
}
]
Napake pri razčlenjevanju
Privzeto Greph tiho preskoči datoteke, ki jih ni mogoče razčleniti. To je primerno pri izvajanju nad veliko kodno zbirko, ki vsebuje eksperimentalne ali ustvarjene datoteke PHP. Podajte --strict-parse (za greph-index ast-index search in greph-index ast-cache search), da se namesto tega sproži napaka pri razčlenjevanju, ali nastavite skipParseErrors: false v AstSearchOptions, kadar neposredno uporabljate fasado.
Uporaba v kodi
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 je zajeto PhpParser\Node (zato se lahko sprehodite po njem z PHP-Parser NodeVisitor), $match->captures pa preslika vsako ime metaspremenljivke v zajeto vozlišče ali seznam vozlišč.
Kako deluje
Cevovod poteka takole:
- Razčlenitev vzorca:
Greph\Ast\PatternParserpreoblikuje metaspremenljivke identifikatorjev, nato pa vzorec razčleni z PHP-Parser. - Razčlenitev izvorne kode: vsaka kandidatna datoteka se razčleni enkrat. V načinu
ast-cacheta korak nadomesti deserializacija iz shrambe predpomnilnika. - Filtriranje kandidatov:
Greph\Ast\AstPatternPrefilterizlušči grobi podpis vzorca (ime klica, ime razreda, vrsto stavka) in ga uporabi za preskakovanje datotek, ki očitno ne morejo vsebovati ujemanja. V načinuast-indexta korak namesto tega poizveduje po vnaprej izračunani shrambi dejstev. - Ujemanje:
Greph\Ast\PatternMatcherpregleda izvorni AST in ga primerja z AST vzorca, pri tem veže metaspremenljivke ter zagotavlja doslednost ponavljajočih se spremenljivk. - Izpis rezultatov: ujemajoča se vozlišča se pretvorijo v objekte
AstMatchz mesti vrstic in bajtov ter izvirnim izsekom izvorne kode.
Pri ponavljajočih se opravilih indeksirano iskanje AST in iskanje AST iz predpomnilnika preskočita večino 2. in 3. koraka.