Recherche AST
Le mode AST de Greph recherche le code source PHP par structure, et non par texte. Les motifs s'écrivent comme du code PHP ordinaire avec des métavariables $VARIABLE et $$$VARIADIC. Le motif est analysé par nikic/php-parser pour obtenir la même forme d'AST que le code source recherché, puis les nœuds sont comparés un à un.
Ce modèle est le même que celui de ast-grep, et la couche de compatibilité sg expose l'interface CLI de ast-grep sur le même moteur.
Exemples rapides
# 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
Syntaxe des motifs
Un motif est du code source PHP valide. Partout où une expression ou une instruction réelle pourrait apparaître, vous pouvez substituer une métavariable pour capturer ce nœud.
| Syntaxe | Correspond à | Exemple |
|---|---|---|
$VAR |
Tout nœud AST unique, capturé sous le nom VAR |
$x + $y correspond à foo() + bar |
$_ |
Tout nœud AST unique, sans capture | $_->method() correspond à n'importe quel receveur |
$$$ARGS |
Zéro ou plusieurs nœuds (variadique), capturés sous le nom ARGS |
func($$$ARGS) correspond à func(), func(1), func(1, 2, 3) |
$VAR (répété) |
Le même $VAR doit correspondre à la même structure aux deux occurrences |
$x == $x correspond à a == a, mais pas à a == b |
Par convention, le nom de la métavariable est en majuscules, mais ce n'est pas obligatoire. $_ est un joker sans capture.
$$$VARIADIC fonctionne partout où PHP autorise une liste de nœuds : arguments de fonction, éléments de tableau, listes de paramètres, corps d'instructions. La capture enregistre toute la séquence correspondante, même si elle est vide.
Métavariables d'identifiant
Les noms de classes, de fonctions, d'interfaces, de traits et d'énumérations sont des identifiants, et non des expressions ; PHP n'accepterait donc normalement pas $Name après class. Greph réécrit ces motifs avant l'analyse pour que la mét variable fonctionne tout de même :
./vendor/bin/greph -p 'class $NAME extends BaseController {}' src
./vendor/bin/greph -p 'function $NAME(): void {}' src
./vendor/bin/greph -p 'interface $NAME {}' src
La même réécriture s'applique à class, interface, trait, enum et function.
Métavariables répétées
Réutiliser une métavariable dans un même motif impose que ses deux occurrences correspondent structurellement au même nœud. C'est utile pour repérer du code redondant :
# Tautological comparisons
./vendor/bin/greph -p '$X == $X' src
# Redundant ternaries
./vendor/bin/greph -p '$X ? $X : null' src
La même règle s'applique à $$$VARIADIC : deux occurrences de $$$ARGS doivent correspondre à la même séquence.
Filtrer les fichiers
Le mode AST parcourt l'arborescence des fichiers avec le même outil que le mode textuel et applique par défaut le filtre de type de fichier php. Remplacez-le avec --type, --type-not, --glob, --no-ignore et --hidden. Le parcours respecte toujours .gitignore et .grephignore et ignore les fichiers binaires.
./vendor/bin/greph -p 'new $CLASS()' --glob 'src/**/*.php' .
./vendor/bin/greph -p 'new $CLASS()' --type-not phpt src
Sortie
Le format de sortie par défaut est le même que celui de file:line:content en mode textuel, avec le code correspondant regroupé sur une seule ligne :
src/Greph.php:38:return (new FileWalker())->walk($paths, $options);
src/Greph.php:49:$searcher = new TextSearcher();
Passez --json pour produire les correspondances structurées :
[
{
"file": "src/Greph.php",
"start_line": 38,
"end_line": 38,
"start_file_pos": 1234,
"end_file_pos": 1252,
"code": "new FileWalker()"
}
]
Erreurs d'analyse
Par défaut, Greph ignore silencieusement les fichiers dont l'analyse échoue. C'est le comportement adapté pour parcourir une grande base de code contenant du code PHP expérimental ou généré. Passez --strict-parse (avec greph-index ast-index search et greph-index ast-cache search) pour renvoyer l'erreur d'analyse à la place, ou définissez skipParseErrors: false sur AstSearchOptions en utilisant directement la façade.
Utilisation par programmation
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 correspond à l'PhpParser\Node capturée (vous pouvez donc la parcourir avec PHP-Parser de NodeVisitor), et $match->captures associe chaque nom de métavariable au nœud ou à la liste de nœuds capturés.
Fonctionnement
Le pipeline est le suivant :
- Analyse du motif :
Greph\Ast\PatternParserréécrit les métavariables d'identifiant, puis analyse le motif avec PHP-Parser. - Analyse du code source : chaque fichier candidat est analysé une seule fois. En mode
ast-cache, cette étape est remplacée par une désérialisation depuis le cache. - Filtrage des candidats :
Greph\Ast\AstPatternPrefilterextrait une signature générale du motif (nom d'appel, nom de classe, type d'instruction) et s'en sert pour ignorer les fichiers qui ne peuvent manifestement pas contenir de correspondance. En modeast-index, cette étape interroge plutôt un stockage de faits pré calculé. - Comparaison :
Greph\Ast\PatternMatcherparcourt l'AST du code source et le compare à l'AST du motif, en liant les métavariables au passage et en vérifiant la cohérence des variables répétées. - Production des résultats : les nœuds correspondants sont convertis en objets
AstMatchavec leurs positions en lignes et en octets ainsi que l'extrait source d'origine.
Pour les charges de travail répétées, la recherche AST indexée et la recherche AST avec cache ignorent la plupart des étapes 2 et 3.