
# AST-søgning

AST-tilstanden i Greph søger i PHP-kilde efter struktur, ikke tekst. Mønstre skrives som almindelig PHP-kode med metavariablerne `$VARIABLE` og `$$$VARIADIC`. Mønsteret parses af [nikic/php-parser](https://github.com/nikic/PHP-Parser) til samme AST-struktur som kilden, der søges i, og matches derefter node for node.

Det er samme model som [ast-grep](https://ast-grep.github.io/), og kompatibilitetsomslaget `sg` viser ast-greps overflade CLI oven på den samme motor.

## Hurtige eksempler

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

## Mønstersyntaks

Et mønster er gyldig PHP-kilde. Alle steder, hvor et rigtigt udtryk eller en sætning kan stå, kan du indsætte en metavariabel for at indfange den node.

| Syntaks | Matcher | Eksempel |
|---|---|---|
| `$VAR` | Enhver enkelt AST-node, indfanget som `VAR` | `$x + $y` matcher `foo() + bar` |
| `$_` | Enhver enkelt AST-node, ikke indfanget | `$_->method()` matcher enhver modtager |
| `$$$ARGS` | Nul eller flere noder (variadisk), indfanget som `ARGS` | `func($$$ARGS)` matcher `func()`, `func(1)`, `func(1, 2, 3)` |
| `$VAR` (gentaget) | Samme `$VAR` skal matche den samme struktur begge gange | `$x == $x` matcher `a == a`, men ikke `a == b` |

Metavariablens navn skrives traditionelt med store bogstaver, men det er ikke et krav. `$_` er et jokertegn, der ikke indfanger noget.

`$$$VARIADIC` fungerer alle steder, hvor PHP tillader en liste af noder: funktionsargumenter, arrayelementer, parameterlister og sætningsblokke. Indfangningen gemmer hele den matchede sekvens (også når den indeholder nul elementer).

## Metavariabler for identifikatorer

Klasse-, funktions-, interface-, trait- og enum-navne er identifikatorer, ikke udtryk, så PHP ville normalt ikke acceptere `$Name` efter `class`. Greph omskriver disse mønstre før parsing, så metavariablen stadig fungerer:

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

Den samme omskrivning sker for `class`, `interface`, `trait`, `enum` og `function`.

## Gentagne metavariabler

Hvis en metavariabel bruges igen i det samme mønster, kræves det, at begge forekomster strukturelt matcher den samme node. Det er nyttigt til at finde overflødig kode:

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

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

Den samme regel gælder for `$$$VARIADIC`: to forekomster af `$$$ARGS` skal matche den samme sekvens.

## Filtrering af filer

AST-tilstanden gennemgår filtræet med samme gennemløber som teksttilstanden og bruger som standard filtypefilteret `php`. Tilsidesæt det med `--type`, `--type-not`, `--glob`, `--no-ignore` og `--hidden`. Gennemløberen respekterer stadig `.gitignore` og `.grephignore` og springer binære filer over.

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

## Output

Standardoutputtet bruger samme `file:line:content`-format som teksttilstanden, hvor den matchede kode er samlet på én linje:

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

Send `--json` for at generere strukturerede match:

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

## Parsefejl

Som standard springer Greph lydløst filer over, der ikke kan parses. Det er den rette adfærd ved kørsel på tværs af en stor kodebase, der indeholder eksperimentel eller genereret PHP. Send `--strict-parse` (med `greph-index ast-index search` og `greph-index ast-cache search`) for i stedet at udløse parsefejlen, eller angiv `skipParseErrors: false` på `AstSearchOptions`, når du bruger facaden direkte.

## Programmatisk brug

```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` er den indfangede `PhpParser\Node` (så du kan gennemløbe den med PHP-Parsers `NodeVisitor`), og `$match->captures` knytter hvert metavariabelnavn til dets indfangede node eller liste af noder.

## Sådan fungerer det

Forløbet er:

1. **Parsing af mønster**: `Greph\Ast\PatternParser` omskriver metavariabler for identifikatorer og parser derefter mønsteret med PHP-Parser.
2. **Parsing af kilde**: hver kandidatfil parses én gang. I tilstanden `ast-cache` erstattes dette trin af en deserialisering fra cachelageret.
3. **Filtrering af kandidater**: `Greph\Ast\AstPatternPrefilter` udtrækker en grov signatur fra mønsteret (kaldnavn, klassenavn, sætningstype) og bruger den til at springe filer over, der tydeligvis ikke kan indeholde et match. I tilstanden `ast-index` slår dette trin i stedet op i et forudberegnet faktalager.
4. **Matchning**: `Greph\Ast\PatternMatcher` gennemløber kilde-AST'et og sammenligner det med mønster-AST'et. Undervejs bindes metavariablerne, og det sikres, at gentagne variabler stemmer overens.
5. **Generering af resultater**: matchede noder konverteres til `AstMatch`-objekter med linje- og bytepositioner samt det oprindelige udsnit af kilden.

Ved gentagne kørsler springer [indekseret AST-søgning](https://greph.inline0.dev/da/docs/indexed/ast) og [cached AST-søgning](https://greph.inline0.dev/da/docs/indexed/cache) de fleste dele af trin 2 og 3 over.
