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
# 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:
./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:
# 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.
./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:
src/Greph.php:38:return (new FileWalker())->walk($paths, $options);
src/Greph.php:49:$searcher = new TextSearcher();
Passe --json para emitir correspondências estruturadas:
[
{
"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
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 é:
- Análise do padrão:
Greph\Ast\PatternParserreescreve as metavariáveis de identificador e, em seguida, analisa o padrão com PHP-Parser. - 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. - Filtragem de candidatos:
Greph\Ast\AstPatternPrefilterextrai 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 modoast-index, esta etapa consulta um armazenamento de fatos pré-computado. - Comparação:
Greph\Ast\PatternMatcherpercorre 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. - Emissão de resultados: os nós correspondentes são convertidos em objetos
AstMatchcom 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.