
# Пошук 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` надає інтерфейс ast-grepCLI поверх того самого рушія.

## Короткі приклади

```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` (тож його можна обійти за допомогою PHP-Parser із `NodeVisitor`), а `$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/uk/docs/indexed/ast) і [кешований пошук AST](https://greph.inline0.dev/uk/docs/indexed/cache) пропускають більшість етапів 2 і 3.
