Saltar al contenido

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

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

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:

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

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:

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

Shell
./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:

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

Pasa --json para emitir coincidencias estructuradas:

JSON
[
  {
    "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

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

  1. Análisis del patrón: Greph\Ast\PatternParser reescribe las metavariables de identificador y luego analiza el patrón con PHP-Parser.
  2. 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é.
  3. Filtrado de candidatos: Greph\Ast\AstPatternPrefilter extrae 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 modo ast-index, este paso consulta un almacén de hechos precalculado.
  4. Comparación: Greph\Ast\PatternMatcher recorre 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.
  5. Generación de resultados: los nodos coincidentes se convierten en objetos AstMatch con 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.