
# AST-Suche

Der AST-Modus von Greph durchsucht PHP-Quellcode nach Struktur statt nach Text. Muster werden als gewöhnlicher PHP-Code mit den Metavariablen `$VARIABLE` und `$$$VARIADIC` geschrieben. Das Muster wird von [nikic/php-parser](https://github.com/nikic/PHP-Parser) in dieselbe AST-Struktur wie der durchsuchte Quellcode geparst und anschließend Knoten für Knoten abgeglichen.

Dieses Modell entspricht dem von [ast-grep](https://ast-grep.github.io/). Der Kompatibilitäts-Wrapper `sg` stellt die ast-grep-Oberfläche CLI auf derselben Engine bereit.

## Schnellbeispiele

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

## Mustersyntax

Ein Muster ist gültiger PHP-Quellcode. Überall dort, wo ein echter Ausdruck oder eine Anweisung stehen könnte, kannst du eine Metavariable einsetzen, um diesen Knoten zu erfassen.

| Syntax | Treffer | Beispiel |
|---|---|---|
| `$VAR` | Beliebiger AST-Knoten, erfasst als `VAR` | `$x + $y` trifft auf `foo() + bar` zu |
| `$_` | Beliebiger AST-Knoten, wird nicht erfasst | `$_->method()` trifft auf einen beliebigen Empfänger zu |
| `$$$ARGS` | Null oder mehr Knoten (variadisch), erfasst als `ARGS` | `func($$$ARGS)` trifft auf `func()`, `func(1)`, `func(1, 2, 3)` zu |
| `$VAR` (wiederholt) | Dieselbe `$VAR` muss beide Male dieselbe Struktur treffen | `$x == $x` trifft auf `a == a`, aber nicht auf `a == b` zu |

Der Name der Metavariablen wird üblicherweise großgeschrieben, muss es aber nicht. `$_` ist ein Platzhalter ohne Erfassung.

`$$$VARIADIC` funktioniert überall dort, wo PHP eine Knotenliste erlaubt: Funktionsargumente, Array-Elemente, Parameterlisten und Anweisungskörper. Die Erfassung speichert die gesamte gefundene Sequenz, auch wenn sie keine Elemente enthält.

## Metavariablen für Bezeichner

Klassen-, Funktions-, Schnittstellen-, Trait- und Enum-Namen sind Bezeichner, keine Ausdrücke. Daher würde PHP nach `class` normalerweise kein `$Name` akzeptieren. Greph schreibt diese Muster vor dem Parsen um, damit die Metavariable trotzdem funktioniert:

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

Dieselbe Umschreibung findet auch für `class`, `interface`, `trait`, `enum` und `function` statt.

## Wiederholte Metavariablen

Wird eine Metavariable im selben Muster wiederverwendet, müssen beide Vorkommen strukturell denselben Knoten treffen. So kannst du überflüssigen Code erkennen:

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

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

Dieselbe Regel gilt für `$$$VARIADIC`: Zwei Vorkommen von `$$$ARGS` müssen dieselbe Sequenz treffen.

## Dateien filtern

Der AST-Modus durchläuft den Dateibaum mit demselben Walker wie der Textmodus und verwendet standardmäßig den Dateitypfilter `php`. Überschreibe ihn mit `--type`, `--type-not`, `--glob`, `--no-ignore` und `--hidden`. Der Walker berücksichtigt weiterhin `.gitignore` und `.grephignore` und überspringt Binärdateien.

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

## Ausgabe

Die Standardausgabe verwendet dasselbe Format `file:line:content` wie der Textmodus. Der gefundene Code wird dabei zu einer einzigen Zeile zusammengefasst:

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

Übergib `--json`, um strukturierte Treffer auszugeben:

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

## Parsing-Fehler

Standardmäßig überspringt Greph Dateien, die nicht geparst werden können, ohne eine Meldung auszugeben. Das ist sinnvoll bei der Suche in großen Codebasen mit experimentellem oder generiertem PHP. Übergib `--strict-parse` (bei `greph-index ast-index search` und `greph-index ast-cache search`), um stattdessen den Parsing-Fehler auszulösen. Wenn du die Fassade direkt verwendest, setze `skipParseErrors: false` bei `AstSearchOptions`.

## Programmatische Verwendung

```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` ist der erfasste `PhpParser\Node` (du kannst ihn also mit PHP-Parsers `NodeVisitor` durchlaufen), und `$match->captures` ordnet jeden Namen einer Metavariablen dem erfassten Knoten oder der Liste erfasster Knoten zu.

## So funktioniert es

Die Pipeline sieht so aus:

1. **Muster parsen**: `Greph\Ast\PatternParser` schreibt Metavariablen für Bezeichner um und parst dann das Muster mit PHP-Parser.
2. **Quellcode parsen**: Jede Kandidatendatei wird einmal geparst. Im Modus `ast-cache` wird dieser Schritt durch das Deserialisieren aus dem Cache-Speicher ersetzt.
3. **Kandidaten filtern**: `Greph\Ast\AstPatternPrefilter` extrahiert eine grobe Signatur aus dem Muster (Aufrufname, Klassenname, Anweisungstyp) und überspringt damit Dateien, die offensichtlich keinen Treffer enthalten können. Im Modus `ast-index` wird stattdessen ein vorberechneter Faktenspeicher abgefragt.
4. **Abgleichen**: `Greph\Ast\PatternMatcher` durchläuft den Quellcode-AST und vergleicht ihn mit dem Muster-AST. Dabei bindet es Metavariablen und stellt sicher, dass wiederholte Variablen übereinstimmen.
5. **Ergebnisse ausgeben**: Gefundene Knoten werden in `AstMatch`-Objekte mit Zeilen- und Bytepositionen sowie dem Originalausschnitt aus dem Quellcode umgewandelt.

Bei wiederholten Workloads überspringen die [indizierte AST-Suche](https://greph.inline0.dev/de/docs/indexed/ast) und die [AST-Suche mit Cache](https://greph.inline0.dev/de/docs/indexed/cache) die meisten Schritte 2 und 3.
