Búsqueda AST
El modo AST de Greph busca código fuente PHP por su estructura, no por su texto. Los patrones se escriben como código PHP normal, con metavariables $VARIABLE y $$$VARIADIC. nikic/php-parser analiza el patrón y genera la misma estructura AST que la del código fuente donde busca; después compara los nodos uno por uno.
Este es el mismo modelo que ast-grep, y la capa de compatibilidad sg ofrece la interfaz CLI de ast-grep sobre el mismo motor.
Ejemplos rápidos
# 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
Sintaxis de patrones
Un patrón es código fuente PHP válido. En cualquier lugar donde pueda ir una expresión o instrucción real, puedes sustituir una metavariable para capturar ese nodo.
| Sintaxis | Coincide con | Ejemplo |
|---|---|---|
$VAR |
Cualquier nodo AST individual, capturado como VAR |
$x + $y coincide con foo() + bar |
$_ |
Cualquier nodo AST individual, sin capturarlo | $_->method() coincide con cualquier receptor |
$$$ARGS |
Cero o más nodos (variádicos), capturados como ARGS |
func($$$ARGS) coincide con func(), func(1), func(1, 2, 3) |
$VAR (repetido) |
El mismo $VAR debe coincidir con la misma estructura en ambas ocasiones |
$x == $x coincide con a == a, pero no con a == b |
Por convención, el nombre de la metavariable va en mayúsculas, pero no es obligatorio. $_ es un comodín que no captura.
$$$VARIADIC funciona dondequiera que PHP permita una lista de nodos: argumentos de funciones, elementos de matrices, listas de parámetros y cuerpos de instrucciones. La captura almacena toda la secuencia coincidente, incluso si no contiene elementos.
Metavariables de identificador
Los nombres de clases, funciones, interfaces, traits y enums son identificadores, no expresiones, por lo que PHP normalmente no aceptaría $Name después de class. Greph reescribe estos patrones antes del análisis para que la metavariable siga funcionando:
./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 misma reescritura se aplica a class, interface, trait, enum y function.
Metavariables repetidas
Reutilizar una metavariable dentro del mismo patrón exige que ambas apariciones coincidan estructuralmente con el mismo nodo. Esto resulta útil para detectar código redundante:
# Tautological comparisons
./vendor/bin/greph -p '$X == $X' src
# Redundant ternaries
./vendor/bin/greph -p '$X ? $X : null' src
La misma regla se aplica a $$$VARIADIC: dos apariciones de $$$ARGS deben coincidir con la misma secuencia.
Filtrado de archivos
El modo AST recorre el árbol de archivos con el mismo explorador que el modo de texto y, de forma predeterminada, filtra por el tipo de archivo php. Anula este valor con --type, --type-not, --glob, --no-ignore y --hidden. El explorador sigue respetando .gitignore y .grephignore, y omite los archivos binarios.
./vendor/bin/greph -p 'new $CLASS()' --glob 'src/**/*.php' .
./vendor/bin/greph -p 'new $CLASS()' --type-not phpt src
Salida
El formato de salida predeterminado usa el mismo formato file:line:content que el modo de texto y presenta el código coincidente en una sola línea:
src/Greph.php:38:return (new FileWalker())->walk($paths, $options);
src/Greph.php:49:$searcher = new TextSearcher();
Pasa --json para emitir coincidencias estructuradas:
[
{
"file": "src/Greph.php",
"start_line": 38,
"end_line": 38,
"start_file_pos": 1234,
"end_file_pos": 1252,
"code": "new FileWalker()"
}
]
Errores de análisis
De forma predeterminada, Greph omite silenciosamente los archivos que no se pueden analizar. Este es el comportamiento adecuado al recorrer una base de código grande que contiene código PHP experimental o generado. Pasa --strict-parse (en greph-index ast-index search y greph-index ast-cache search) para generar el error de análisis, o establece skipParseErrors: false en AstSearchOptions al usar directamente la fachada.
Uso programático
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 es el PhpParser\Node capturado (así que puedes recorrerlo con PHP-Parser de NodeVisitor), y $match->captures asigna cada nombre de metavariable al nodo o la lista de nodos capturados.
Cómo funciona
El proceso es el siguiente:
- Análisis del patrón:
Greph\Ast\PatternParserreescribe las metavariables de identificador y luego analiza el patrón con PHP-Parser. - Análisis del código fuente: cada archivo candidato se analiza una vez. En el modo
ast-cache, este paso se sustituye por la deserialización desde el almacén de caché. - Filtrado de candidatos:
Greph\Ast\AstPatternPrefilterextrae una firma general del patrón (nombre de llamada, nombre de clase, tipo de instrucción) y la usa para omitir archivos que claramente no pueden contener una coincidencia. En el modoast-index, este paso consulta un almacén de hechos precalculado. - Comparación:
Greph\Ast\PatternMatcherrecorre el AST del código fuente y lo compara con el AST del patrón; vincula las metavariables y comprueba la coherencia de las variables repetidas. - Generación de resultados: los nodos coincidentes se convierten en objetos
AstMatchcon posiciones de línea y byte, y el fragmento original del código fuente.
En cargas de trabajo repetidas, la búsqueda AST indexada y la búsqueda AST en caché omiten la mayoría de los pasos 2 y 3.