
# Поиск по AST

В режиме AST Greph ищет в исходном коде PHP по структуре, а не по тексту. Шаблоны записываются как обычный код на PHP с `$VARIABLE` и `$$$VARIADIC` метапеременными. Шаблон разбирается с помощью [nikic/php-parser](https://github.com/nikic/PHP-Parser) в ту же структуру AST, что и исходный код, по которому выполняется поиск, а затем сопоставляется узел за узлом.

Это та же модель, что и у [ast-grep](https://ast-grep.github.io/), а оболочка совместимости `sg` предоставляет интерфейс CLI из ast-grep поверх того же механизма.

## Краткие примеры

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

## Синтаксис шаблонов

Шаблон — это допустимый исходный код PHP. Там, где может стоять настоящее выражение или инструкция, можно подставить метапеременную, которая захватит этот узел.

| Синтаксис | Совпадает с | Пример |
|---|---|---|
| `$VAR` | Любой одиночный узел AST, захваченный как `VAR` | `$x + $y` совпадает с `foo() + bar` |
| `$_` | Любой одиночный узел AST без захвата | `$_->method()` совпадает с любым получателем |
| `$$$ARGS` | Ноль или более узлов (вариативный набор), захваченных как `ARGS` | `func($$$ARGS)` совпадает с `func()`, `func(1)`, `func(1, 2, 3)` |
| `$VAR` (повторная) | Одна и та же `$VAR` должна оба раза совпадать с одинаковой структурой | `$x == $x` совпадает с `a == a`, но не с `a == b` |

По соглашению имя метапеременной пишется заглавными буквами, но это необязательно. `$_` — это подстановочный знак без захвата.

`$$$VARIADIC` работает везде, где PHP допускает список узлов: аргументы функции, элементы массива, списки параметров, тела инструкций. Захват сохраняет всю найденную последовательность, включая ноль элементов.

## Метапеременные идентификаторов

Имена классов, функций, интерфейсов, трейтов и перечислений — это идентификаторы, а не выражения, поэтому обычно PHP не принимает `$Name` после `class`. Greph переписывает такие шаблоны перед разбором, чтобы метапеременная работала:

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

То же переписывание выполняется для `class`, `interface`, `trait`, `enum` и `function`.

## Повторные метапеременные

Повторное использование метапеременной в одном шаблоне означает, что оба вхождения должны структурно совпадать с одним и тем же узлом. Это полезно для поиска избыточного кода:

```bash
# Tautological comparisons
./vendor/bin/greph -p '$X == $X' src

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

То же правило действует для `$$$VARIADIC`: оба вхождения `$$$ARGS` должны совпадать с одной и той же последовательностью.

## Фильтрация файлов

Режим AST обходит дерево файлов тем же способом, что и текстовый режим, и по умолчанию использует фильтр типов файлов `php`. Переопределите его с помощью `--type`, `--type-not`, `--glob`, `--no-ignore` и `--hidden`. Обход по-прежнему учитывает `.gitignore` и `.grephignore` и пропускает двоичные файлы.

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

## Вывод

По умолчанию используется тот же формат `file:line:content`, что и в текстовом режиме; найденный код сворачивается в одну строку:

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

Передайте `--json`, чтобы вывести структурированные совпадения:

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

## Ошибки разбора

По умолчанию Greph молча пропускает файлы, которые не удается разобрать. Это удобно при работе с крупной кодовой базой, содержащей экспериментальные или сгенерированные файлы PHP. Передайте `--strict-parse` (для `greph-index ast-index search` и `greph-index ast-cache search`), чтобы вместо этого вызвать ошибку разбора, или задайте `skipParseErrors: false` для `AstSearchOptions` при непосредственном использовании фасада.

## Программное использование

```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` — это захваченный `PhpParser\Node` (поэтому его можно обойти с помощью `NodeVisitor` из PHP-Parser), а `$match->captures` сопоставляет каждое имя метапеременной с захваченным узлом или списком узлов.

## Как это работает

Конвейер состоит из следующих этапов:

1. **Разбор шаблона**: `Greph\Ast\PatternParser` переписывает метапеременные идентификаторов, затем разбирает шаблон с помощью PHP-Parser.
2. **Разбор исходного кода**: каждый файл-кандидат разбирается один раз. В режиме `ast-cache` этот этап заменяется десериализацией из кэшированного хранилища.
3. **Фильтрация кандидатов**: `Greph\Ast\AstPatternPrefilter` извлекает из шаблона грубую сигнатуру (имя вызова, имя класса, тип инструкции) и использует ее, чтобы пропускать файлы, в которых заведомо не может быть совпадений. В режиме `ast-index` вместо этого этапа запрашивается предварительно вычисленное хранилище фактов.
4. **Сопоставление**: `Greph\Ast\PatternMatcher` обходит AST исходного кода и сравнивает его с AST шаблона, попутно связывая метапеременные и проверяя согласованность повторных переменных.
5. **Вывод результатов**: найденные узлы преобразуются в объекты `AstMatch` с позициями строк и байтов, а также исходным фрагментом кода.

При повторяющихся задачах [индексированный поиск по AST](https://greph.inline0.dev/ru/docs/indexed/ast) и [поиск по кэшированному AST](https://greph.inline0.dev/ru/docs/indexed/cache) пропускают большую часть этапов 2 и 3.
