
# AST meklēšana

Greph AST režīms meklē PHP pirmkodu pēc struktūras, nevis teksta. Raksti tiek veidoti kā parasts PHP kods ar metavariabliem `$VARIABLE` un `$$$VARIADIC`. Rakstu parsē, izmantojot [nikic/php-parser](https://github.com/nikic/PHP-Parser), iegūstot tādu pašu AST struktūru kā meklētajam pirmkodam, un pēc tam salīdzina pa mezgliem.

Tas ir tāds pats modelis kā [ast-grep](https://ast-grep.github.io/), un `sg` saderības iesaiņotājs nodrošina ast-grep CLI saskarni virs tā paša dzinēja.

## Īsi piemēri

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

## Rakstu sintakse

Raksts ir derīgs PHP pirmkods. Jebkurā vietā, kur varētu atrasties īsta izteiksme vai priekšraksts, tā mezgla uztveršanai var ievietot metavariablu.

| Sintakse | Atbilstība | Piemērs |
|---|---|---|
| `$VAR` | Jebkurš atsevišķs AST mezgls, kas tiek uztverts kā `VAR` | `$x + $y` atbilst `foo() + bar` |
| `$_` | Jebkurš atsevišķs AST mezgls, kas netiek uztverts | `$_->method()` atbilst jebkuram saņēmējam |
| `$$$ARGS` | Nulle vai vairāk mezglu (variantiski), kas tiek uztverti kā `ARGS` | `func($$$ARGS)` atbilst `func()`, `func(1)`, `func(1, 2, 3)` |
| `$VAR` (atkārtots) | Tas pats `$VAR` abās vietās jāatbilst vienādai struktūrai | `$x == $x` atbilst `a == a`, bet ne `a == b` |

Metavariablas nosaukumā pēc vienošanās izmanto lielos burtus, taču tā nav obligāta prasība. `$_` ir universāls aizstājējzīmes elements, kas netiek uztverts.

`$$$VARIADIC` darbojas visur, kur PHP pieļauj mezglu sarakstu: funkciju argumentos, masīva elementos, parametru sarakstos un priekšrakstu blokos. Tvērums saglabā visu atbilstošo secību, arī tad, ja tajā nav neviena elementa.

## Identifikatoru metavariabli

Klašu, funkciju, saskarņu, trašu un uzskaitījumu nosaukumi ir identifikatori, nevis izteiksmes, tāpēc PHP parasti nepieņemtu `$Name` pēc `class`. Greph pirms parsēšanas pārveido šos rakstus, lai metavariabla joprojām darbotos:

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

Tāda pati pārveide notiek attiecībā uz `class`, `interface`, `trait`, `enum` un `function`.

## Atkārtotas metavariabli

Metavariablas atkārtota izmantošana vienā rakstā nozīmē, ka abiem gadījumiem strukturāli jāatbilst tam pašam mezglam. Tas noder lieka koda atrašanai:

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

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

Tas pats noteikums attiecas uz `$$$VARIADIC`: diviem `$$$ARGS` gadījumiem jāatbilst vienādai secībai.

## Failu filtrēšana

AST režīms izmanto to pašu failu koka pārlūku, ko teksta režīms, un pēc noklusējuma lieto failu tipa filtru `php`. To var mainīt ar `--type`, `--type-not`, `--glob`, `--no-ignore` un `--hidden`. Pārlūks joprojām ievēro `.gitignore` un `.grephignore`, kā arī izlaiž bināros failus.

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

## Izvade

Noklusējuma izvadē tiek izmantots tāds pats formāts `file:line:content` kā teksta režīmā, bet atbilstošais kods ir apvienots vienā rindā:

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

Nododiet `--json`, lai izvadītu strukturētas atbilstības:

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

## Parsēšanas kļūdas

Pēc noklusējuma Greph klusi izlaiž failus, kurus neizdodas parsēt. Tā ir piemērota rīcība, strādājot ar lielu koda bāzi, kurā ir eksperimentāls vai ģenerēts PHP. Nododiet `--strict-parse` (komandām `greph-index ast-index search` un `greph-index ast-cache search`), lai kļūdas vietā tiktu parādīta parsēšanas kļūda, vai, tieši izmantojot fasādi, iestatiet `skipParseErrors: false` laukā `AstSearchOptions`.

## Izmantošana programmā

```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` ir uztvertais `PhpParser\Node` (tāpēc to var pārlūkot ar PHP-Parser `NodeVisitor`), bet `$match->captures` katru metavariablas nosaukumu sasaista ar uztverto mezglu vai mezglu sarakstu.

## Kā tas darbojas

Apstrādes konveijera darbības:

1. **Raksta parsēšana**: `Greph\Ast\PatternParser` pārveido identifikatoru metavariablas, pēc tam parsē rakstu ar PHP-Parser.
2. **Avota parsēšana**: katrs kandidātfails tiek parsēts vienreiz. `ast-cache` režīmā šo darbību aizstāj deserializēšana no kešatmiņas krātuves.
3. **Kandidātu filtrēšana**: `Greph\Ast\AstPatternPrefilter` izvelk no raksta aptuvenu parakstu (izsaukuma nosaukumu, klases nosaukumu, priekšraksta tipu) un izmanto to, lai izlaistu failus, kuros acīmredzami nevar būt atbilstības. `ast-index` režīmā šajā darbībā tiek vaicāta iepriekš aprēķināta faktu krātuve.
4. **Salīdzināšana**: `Greph\Ast\PatternMatcher` pārlūko avota AST un salīdzina to ar raksta AST, procesā sasaistot metavariablas un nodrošinot, ka atkārtotās variablēs ir vienādas vērtības.
5. **Rezultātu izvadīšana**: atbilstošie mezgli tiek pārveidoti par `AstMatch` objektiem ar rindu un baitu pozīcijām un sākotnējā pirmkoda fragmentu.

Atkārtotiem darbiem [indeksētā AST meklēšana](https://greph.inline0.dev/lv/docs/indexed/ast) un [kešotā AST meklēšana](https://greph.inline0.dev/lv/docs/indexed/cache) izlaiž lielāko daļu 2. un 3. darbības.
