Pular para o conteúdo

Pesquisa AST

O modo AST de Greph pesquisa código-fonte PHP por estrutura, não por texto. Os padrões são escritos como código PHP comum, com metavariáveis $VARIABLE e $$$VARIADIC. O padrão é analisado por nikic/php-parser na mesma forma AST do código-fonte pesquisado e, em seguida, comparado nó por nó.

Este é o mesmo modelo de ast-grep, e o wrapper de compatibilidade sg expõe a superfície de ast-grep CLI usando o mesmo mecanismo.

Exemplos 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

Sintaxe dos padrões

Um padrão é código-fonte PHP válido. Em qualquer lugar onde uma expressão ou instrução real poderia aparecer, você pode substituí-la por uma metavariável para capturar esse nó.

Sintaxe Corresponde a Exemplo
$VAR Qualquer nó AST único, capturado como VAR $x + $y corresponde a foo() + bar
$_ Qualquer nó AST único, sem captura $_->method() corresponde a qualquer receptor
$$$ARGS Zero ou mais nós (variádicos), capturados como ARGS func($$$ARGS) corresponde a func(), func(1), func(1, 2, 3)
$VAR (repetido) O mesmo $VAR deve corresponder à mesma estrutura nas duas ocorrências $x == $x corresponde a a == a, mas não a a == b

O nome da metavariável costuma ser em maiúsculas por convenção, mas não precisa ser. $_ é um curinga sem captura.

$$$VARIADIC funciona sempre que PHP permite uma lista de nós: argumentos de função, itens de array, listas de parâmetros e corpos de instruções. A captura armazena toda a sequência correspondente (inclusive se não houver itens).

Metavariáveis de identificador

Nomes de classes, funções, interfaces, traits e enums são identificadores, não expressões. Por isso, normalmente PHP não aceitaria $Name após class. Greph reescreve esses padrões antes da análise para que a metavariável continue 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

A mesma reescrita ocorre para class, interface, trait, enum e function.

Metavariáveis repetidas

Reutilizar uma metavariável no mesmo padrão afirma que as duas ocorrências devem corresponder estruturalmente ao mesmo nó. Isso é útil para identificar código redundante:

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

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

A mesma regra se aplica a $$$VARIADIC: duas ocorrências de $$$ARGS devem corresponder à mesma sequência.

Filtragem de arquivos

O modo AST percorre a árvore de arquivos com o mesmo percorridor do modo de texto e usa por padrão o filtro de tipos de arquivo php. Substitua-o com --type, --type-not, --glob, --no-ignore e --hidden. O percorridor ainda respeita .gitignore e .grephignore, além de ignorar arquivos binários.

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

Saída

A saída padrão usa o mesmo formato file:line:content do modo de texto, com o código correspondente recolhido em uma única linha:

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

Passe --json para emitir correspondências estruturadas:

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

Erros de análise

Por padrão, Greph ignora silenciosamente arquivos que não podem ser analisados. Esse é o comportamento adequado ao percorrer uma base de código grande que contenha PHP experimentais ou gerados. Passe --strict-parse (em greph-index ast-index search e greph-index ast-cache search) para gerar o erro de análise, ou defina skipParseErrors: false em AstSearchOptions ao usar a fachada diretamente.

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 é o PhpParser\Node capturado (para que você possa percorrê-lo com PHP-Parser NodeVisitor), e $match->captures associa cada nome de metavariável ao nó ou à lista de nós capturados.

Como funciona

O pipeline é:

  1. Análise do padrão: Greph\Ast\PatternParser reescreve as metavariáveis de identificador e, em seguida, analisa o padrão com PHP-Parser.
  2. Análise do código-fonte: cada arquivo candidato é analisado uma vez. No modo ast-cache, esta etapa é substituída pela desserialização do armazenamento em cache.
  3. Filtragem de candidatos: Greph\Ast\AstPatternPrefilter extrai do padrão uma assinatura aproximada (nome de chamada, nome de classe, tipo de instrução) e a usa para ignorar arquivos que claramente não podem conter uma correspondência. No modo ast-index, esta etapa consulta um armazenamento de fatos pré-computado.
  4. Comparação: Greph\Ast\PatternMatcher percorre a AST do código-fonte e a compara com a AST do padrão, associando metavariáveis e garantindo a consistência das variáveis repetidas.
  5. Emissão de resultados: os nós correspondentes são convertidos em objetos AstMatch com posições de linha/byte e o trecho original do código-fonte.

Em cargas de trabalho repetidas, a pesquisa AST indexada e a pesquisa AST em cache ignoram a maior parte das etapas 2 e 3.