Treci la conținut

Căutare AST

Modul AST din Greph caută în codul sursă PHP după structură, nu după text. Tiparele sunt scrise ca PHP obișnuit, cu metavariabile $VARIABLE și $$$VARIADIC. Tiparul este analizat de nikic/php-parser, formând aceeași structură AST ca sursa în care se caută, apoi este comparat nod cu nod.

Acesta este același model ca ast-grep, iar wrapperul de compatibilitate sg expune suprafața ast-grep CLI peste același motor.

Exemple rapide

Shell
# 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

Sintaxa tiparelor

Un tipar este cod sursă PHP valid. Oriunde poate apărea o expresie sau o instrucțiune reală, puteți înlocui acel nod cu o metavariabilă pentru a-l captura.

Sintaxă Potrivire Exemplu
$VAR Orice nod AST, capturat ca VAR $x + $y se potrivește cu foo() + bar
$_ Orice nod AST, fără captură $_->method() se potrivește cu orice receptor
$$$ARGS Zero sau mai multe noduri (variadic), capturate ca ARGS func($$$ARGS) se potrivește cu func(), func(1), func(1, 2, 3)
$VAR (repetată) Același $VAR trebuie să aibă aceeași structură în ambele locuri $x == $x se potrivește cu a == a, nu cu a == b

Numele metavariabilei este scris cu majuscule, prin convenție, dar nu este obligatoriu. $_ este un wildcard fără captură.

$$$VARIADIC funcționează oriunde PHP permite o listă de noduri: argumente de funcție, elemente de tablou, liste de parametri și corpuri de instrucțiuni. Captura stochează întreaga secvență potrivită, inclusiv când nu conține niciun element.

Metavariabile pentru identificatori

Numele claselor, funcțiilor, interfețelor, trăsăturilor și enumerărilor sunt identificatori, nu expresii, așa că PHP nu ar accepta în mod normal $Name după class. Greph rescrie aceste tipare înainte de analiză, astfel încât metavariabila să funcționeze în continuare:

Shell
./vendor/bin/greph -p 'class $NAME extends BaseController {}' src
./vendor/bin/greph -p 'function $NAME(): void {}' src
./vendor/bin/greph -p 'interface $NAME {}' src

Aceeași rescriere are loc pentru class, interface, trait, enum și function.

Metavariabile repetate

Reutilizarea unei metavariabile în același tipar impune ca ambele apariții să se potrivească structural cu același nod. Acest lucru este util pentru identificarea codului redundant:

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

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

Aceeași regulă se aplică pentru $$$VARIADIC: două apariții ale lui $$$ARGS trebuie să se potrivească cu aceeași secvență.

Filtrarea fișierelor

Modul AST parcurge arborele de fișiere cu același parcurgător ca modul text și folosește implicit filtrul pentru tipul de fișier php. Suprascrieți-l cu --type, --type-not, --glob, --no-ignore și --hidden. Parcurgătorul respectă în continuare .gitignore și .grephignore și omite fișierele binare.

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

Rezultat

Rezultatul implicit folosește același format file:line:content ca modul text, iar codul potrivit este condensat pe o singură linie:

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

Transmiteți --json pentru a emite potriviri structurate:

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

Erori de analiză

În mod implicit, Greph omite fără să afișeze un mesaj fișierele care nu pot fi analizate. Acesta este comportamentul potrivit când rulați căutarea într-o bază de cod mare, care conține PHP experimentale sau generate. Transmiteți --strict-parse (pentru greph-index ast-index search și greph-index ast-cache search) pentru a genera în schimb o eroare de analiză sau setați skipParseErrors: false în AstSearchOptions când folosiți direct facade-ul.

Utilizare programatică

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 este PhpParser\Node capturat (deci îl puteți parcurge cu PHP-ParserNodeVisitor), iar $match->captures asociază fiecare nume de metavariabilă nodului sau listei de noduri capturate.

Cum funcționează

Fluxul este:

  1. Analiza tiparului: Greph\Ast\PatternParser rescrie metavariabilele pentru identificatori, apoi analizează tiparul cu PHP-Parser.
  2. Analiza sursei: fiecare fișier candidat este analizat o singură dată. În modul ast-cache, acest pas este înlocuit de deserializarea din cache.
  3. Filtrarea candidaților: Greph\Ast\AstPatternPrefilter extrage o semnătură grosieră din tipar (numele apelului, numele clasei, tipul instrucțiunii) și o folosește pentru a omite fișierele care în mod evident nu pot conține o potrivire. În modul ast-index, acest pas interoghează în schimb o stocare de fapte precalculată.
  4. Potrivirea: Greph\Ast\PatternMatcher parcurge AST-ul sursei și îl compară cu AST-ul tiparului, asociind metavariabile pe parcurs și impunând consecvența variabilelor repetate.
  5. Emiterea rezultatelor: nodurile potrivite sunt convertite în obiecte AstMatch cu poziții de linie și octet și fragmentul original din sursă.

Pentru sarcini repetate, căutarea AST indexată și căutarea AST din cache omit majoritatea pașilor 2 și 3.