Przejdź do treści

Wyszukiwanie AST

Tryb AST w Greph przeszukuje kod źródłowy PHP według struktury, a nie tekstu. Wzorce zapisuje się jako zwykły kod PHP z metazmiennymi $VARIABLE i $$$VARIADIC. Wzorzec jest parsowany przez nikic/php-parser do takiej samej postaci AST jak przeszukiwane źródło, a następnie dopasowywany węzeł po węźle.

Działa to na tej samej zasadzie co ast-grep, a warstwa zgodności sg udostępnia interfejs CLI należący do ast-grep i korzysta z tego samego silnika.

Szybkie przykłady

Powłoka
# 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

Składnia wzorców

Wzorzec jest poprawnym źródłem PHP. W każdym miejscu, w którym może wystąpić prawdziwe wyrażenie lub instrukcja, możesz wstawić metazmienną, aby przechwycić ten węzeł.

Składnia Dopasowanie Przykład
$VAR Dowolny pojedynczy węzeł AST, przechwycony jako VAR $x + $y dopasowuje foo() + bar
$_ Dowolny pojedynczy węzeł AST bez przechwytywania $_->method() dopasowuje dowolny odbiornik
$$$ARGS Zero lub więcej węzłów (wariadycznie), przechwyconych jako ARGS func($$$ARGS) dopasowuje func(), func(1), func(1, 2, 3)
$VAR (powtórzona) Ta sama $VAR musi za każdym razem dopasować tę samą strukturę $x == $x dopasowuje a == a, ale nie a == b

Nazwa metazmiennej zgodnie z konwencją jest zapisywana wielkimi literami, ale nie jest to wymagane. $_ to symbol wieloznaczny bez przechwytywania.

$$$VARIADIC działa wszędzie tam, gdzie PHP dopuszcza listę węzłów: w argumentach funkcji, elementach tablicy, listach parametrów i treści instrukcji. Przechwycenie zapisuje całą dopasowaną sekwencję (również wtedy, gdy nie zawiera żadnych elementów).

Metazmienne identyfikatorów

Nazwy klas, funkcji, interfejsów, traitów i enumów są identyfikatorami, a nie wyrażeniami, dlatego PHP zwykle nie akceptuje $Name po class. Greph przepisuje te wzorce przed parsowaniem, dzięki czemu metazmienna nadal działa:

Powłoka
./vendor/bin/greph -p 'class $NAME extends BaseController {}' src
./vendor/bin/greph -p 'function $NAME(): void {}' src
./vendor/bin/greph -p 'interface $NAME {}' src

Ta sama transformacja zachodzi dla class, interface, trait, enum i function.

Powtarzane metazmienne

Ponowne użycie metazmiennej we wzorcu oznacza, że oba jej wystąpienia muszą strukturalnie dopasować ten sam węzeł. Przydaje się to do wykrywania nadmiarowego kodu:

Powłoka
# Tautological comparisons
./vendor/bin/greph -p '$X == $X' src

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

Ta sama zasada dotyczy $$$VARIADIC: dwa wystąpienia $$$ARGS muszą dopasować tę samą sekwencję.

Filtrowanie plików

Tryb AST przechodzi po drzewie plików za pomocą tego samego mechanizmu co tryb tekstowy i domyślnie filtruje pliki według typu php. Zastąp to ustawienie za pomocą --type, --type-not, --glob, --no-ignore i --hidden. Mechanizm nadal uwzględnia .gitignore i .grephignore oraz pomija pliki binarne.

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

Wynik

Domyślny wynik używa tego samego formatu file:line:content co tryb tekstowy, a dopasowany kod jest zwijany do jednego wiersza:

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

Przekaż --json, aby wyemitować dopasowania w uporządkowanym formacie:

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

Błędy parsowania

Domyślnie Greph po cichu pomija pliki, których nie da się sparsować. Takie działanie sprawdza się przy przeszukiwaniu dużej bazy kodu zawierającej eksperymentalne lub wygenerowane pliki PHP. Przekaż --strict-parse (w greph-index ast-index search i greph-index ast-cache search), aby zamiast tego zgłaszać błędy parsowania, albo ustaw skipParseErrors: false w AstSearchOptions, gdy używasz bezpośrednio fasady.

Użycie programowe

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 to przechwycony węzeł PhpParser\Node (możesz więc przejść po nim za pomocą PHP-Parser z NodeVisitor), a $match->captures mapuje każdą nazwę metazmiennej na przechwycony węzeł lub listę węzłów.

Jak to działa

Potok wygląda następująco:

  1. Parsowanie wzorca: Greph\Ast\PatternParser przepisuje metazmienne identyfikatorów, a następnie parsuje wzorzec za pomocą PHP-Parser.
  2. Parsowanie źródła: każdy plik-kandydat jest parsowany raz. W trybie ast-cache ten krok zastępuje deserializacja z magazynu cache.
  3. Filtrowanie kandydatów: Greph\Ast\AstPatternPrefilter wyodrębnia zgrubny podpis wzorca (nazwę wywołania, nazwę klasy, typ instrukcji) i wykorzystuje go do pomijania plików, które na pewno nie zawierają dopasowania. W trybie ast-index ten krok odpy tuje wstępnie obliczony magazyn faktów.
  4. Dopasowywanie: Greph\Ast\PatternMatcher przechodzi po AST źródła i porównuje je z AST wzorca, przypisując wartości metazmiennym i sprawdzając spójność powtarzających się zmiennych.
  5. Zwracanie wyników: dopasowane węzły są konwertowane na obiekty AstMatch zawierające numery wierszy i bajtów oraz oryginalny fragment źródła.

W przypadku powtarzanych zadań indeksowane wyszukiwanie AST i wyszukiwanie AST z cache pomijają większość kroków 2 i 3.