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
# 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:
./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:
# 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.
./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:
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:
[
{
"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ă
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:
- Analiza tiparului:
Greph\Ast\PatternParserrescrie metavariabilele pentru identificatori, apoi analizează tiparul cu PHP-Parser. - Analiza sursei: fiecare fișier candidat este analizat o singură dată. În modul
ast-cache, acest pas este înlocuit de deserializarea din cache. - Filtrarea candidaților:
Greph\Ast\AstPatternPrefilterextrage 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 modulast-index, acest pas interoghează în schimb o stocare de fapte precalculată. - Potrivirea:
Greph\Ast\PatternMatcherparcurge AST-ul sursei și îl compară cu AST-ul tiparului, asociind metavariabile pe parcurs și impunând consecvența variabilelor repetate. - Emiterea rezultatelor: nodurile potrivite sunt convertite în obiecte
AstMatchcu 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.