
# 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](https://github.com/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](https://ast-grep.github.io/), a warstwa zgodności `sg` udostępnia interfejs CLI należący do ast-grep i korzysta z tego samego silnika.

## Szybkie przykłady

```bash
# 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:

```bash
./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:

```bash
# 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.

```bash
./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](https://greph.inline0.dev/pl/docs/indexed/ast) i [wyszukiwanie AST z cache](https://greph.inline0.dev/pl/docs/indexed/cache) pomijają większość kroków 2 i 3.
