
# 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/bg/docs/indexed/ast) и [търсенето в кеширан AST](https://greph.inline0.dev/bg/docs/indexed/cache) пропускат повечето от стъпки 2 и 3.
