Vyhledávání AST
Režim AST v Greph prohledává zdrojový kód PHP podle struktury, nikoli textu. Vzory se zapisují jako běžný kód PHP s metavariables $VARIABLE a $$$VARIADIC. Vzor se pomocí nikic/php-parser parsuje do stejného tvaru AST, jaký má prohledávaný zdroj, a potom se porovnává uzel po uzlu.
Funguje to na stejném principu jako ast-grep a obal kompatibility sg zpřístupňuje povrch CLI v ast-grep nad stejným modulem.
Rychlé příklady
# 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
Syntaxe vzorů
Vzor je platný zdrojový kód PHP. Kdekoli může být výraz nebo příkaz, lze ho nahradit metavariable, která tento uzel zachytí.
| Syntaxe | Odpovídá | Příklad |
|---|---|---|
$VAR |
Libovolnému uzlu AST, který se zachytí jako VAR |
$x + $y odpovídá foo() + bar |
$_ |
Libovolnému uzlu AST bez zachycení | $_->method() odpovídá libovolnému příjemci |
$$$ARGS |
Nule nebo více uzlům (variadicky), zachyceným jako ARGS |
func($$$ARGS) odpovídá func(), func(1), func(1, 2, 3) |
$VAR (opakovaně) |
Stejné $VAR musí v obou výskytech odpovídat stejné struktuře |
$x == $x odpovídá a == a, nikoli a == b |
Názvy metavariables se podle konvence píší velkými písmeny, není to však nutné. $_ je zástupný symbol bez zachycení.
$$$VARIADIC funguje všude, kde PHP umožňuje seznam uzlů: v argumentech funkcí, položkách polí, seznamech parametrů i tělech příkazů. Zachycení uloží celou odpovídající sekvenci, včetně prázdné.
Metavariables identifikátorů
Názvy tříd, funkcí, rozhraní, traitů a výčtových typů jsou identifikátory, nikoli výrazy, takže PHP by obvykle za class nepřijalo $Name. Greph tyto vzory před parsováním upraví, aby metavariable fungovala:
./vendor/bin/greph -p 'class $NAME extends BaseController {}' src
./vendor/bin/greph -p 'function $NAME(): void {}' src
./vendor/bin/greph -p 'interface $NAME {}' src
Stejná úprava se provádí pro class, interface, trait, enum a function.
Opakované metavariables
Opětovné použití metavariable ve stejném vzoru znamená, že oba výskyty musí strukturálně odpovídat stejnému uzlu. To se hodí při hledání nadbytečného kódu:
# Tautological comparisons
./vendor/bin/greph -p '$X == $X' src
# Redundant ternaries
./vendor/bin/greph -p '$X ? $X : null' src
Stejné pravidlo platí pro $$$VARIADIC: dva výskyty $$$ARGS musí odpovídat stejné sekvenci.
Filtrování souborů
Režim AST prochází strom souborů stejným průchodem jako textový režim a ve výchozím nastavení filtruje soubory typu php. Filtr lze změnit pomocí --type, --type-not, --glob, --no-ignore a --hidden. Průchod stále respektuje .gitignore a .grephignore a přeskakuje binární soubory.
./vendor/bin/greph -p 'new $CLASS()' --glob 'src/**/*.php' .
./vendor/bin/greph -p 'new $CLASS()' --type-not phpt src
Výstup
Výchozí výstup používá stejný formát file:line:content jako textový režim; odpovídající kód je sloučen do jednoho řádku:
src/Greph.php:38:return (new FileWalker())->walk($paths, $options);
src/Greph.php:49:$searcher = new TextSearcher();
Strukturované shody vypíšete pomocí --json:
[
{
"file": "src/Greph.php",
"start_line": 38,
"end_line": 38,
"start_file_pos": 1234,
"end_file_pos": 1252,
"code": "new FileWalker()"
}
]
Chyby parsování
Ve výchozím nastavení Greph tiše přeskakuje soubory, které nelze parsovat. To je vhodné při prohledávání rozsáhlé kódové základny s experimentálním nebo generovaným kódem PHP. Chcete-li místo toho vyvolat chybu parsování, použijte --strict-parse (v greph-index ast-index search a greph-index ast-cache search), nebo při přímém použití fasády nastavte skipParseErrors: false na AstSearchOptions.
Programové použití
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 zachycené PhpParser\Node (můžete ho tedy procházet pomocí PHP-Parser z NodeVisitor) a $match->captures mapuje každý název metavariable na zachycený uzel nebo seznam uzlů.
Jak to funguje
Postup:
- Parsování vzoru:
Greph\Ast\PatternParserupraví metavariables identifikátorů a poté vzor parsuje pomocí PHP-Parser. - Parsování zdroje: každý kandidátní soubor se parsuje jednou. V režimu
ast-cachetento krok nahrazuje deserializace z úložiště mezipaměti. - Filtrování kandidátů:
Greph\Ast\AstPatternPrefilterextrahuje z vzoru hrubý podpis (název volání, název třídy, typ příkazu) a vynechá soubory, které zjevně nemohou obsahovat shodu. V režimuast-indexse místo toho dotazuje na předem vypočítané úložiště faktů. - Porovnání:
Greph\Ast\PatternMatcherprochází zdrojový AST a porovnává ho s AST vzoru, přitom přiřazuje hodnoty metavariables a ověřuje konzistenci opakovaných proměnných. - Vytvoření výsledku: odpovídající uzly se převedou na objekty
AstMatchs pozicemi řádků a bajtů a původním výřezem zdrojového kódu.
Při opakovaném použití indexované vyhledávání AST a vyhledávání AST z mezipaměti přeskočí většinu kroků 2 a 3.