Přejít k obsahu

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

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

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:

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

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:

Shell
# 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.

Shell
./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:

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

Strukturované shody vypíšete pomocí --json:

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í

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 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:

  1. Parsování vzoru: Greph\Ast\PatternParser upraví metavariables identifikátorů a poté vzor parsuje pomocí PHP-Parser.
  2. Parsování zdroje: každý kandidátní soubor se parsuje jednou. V režimu ast-cache tento krok nahrazuje deserializace z úložiště mezipaměti.
  3. Filtrování kandidátů: Greph\Ast\AstPatternPrefilter extrahuje 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žimu ast-index se místo toho dotazuje na předem vypočítané úložiště faktů.
  4. Porovnání: Greph\Ast\PatternMatcher prochá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.
  5. Vytvoření výsledku: odpovídající uzly se převedou na objekty AstMatch s 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.