索引 AST 搜索
索引 AST 搜索是 AST 搜索的预热缓存版本。首次调用会将节点级事实(调用名称、实例化、类声明、方法声明)提取到磁盘存储中;后续调用使用该存储跳过显然不可能包含结构匹配的文件。随后,匹配器会根据源代码重新验证候选项。
事实索引通过 greph-index ast-index CLI 子命令以及 Greph::buildAstIndex() / Greph::searchAstIndexed() 门面方法提供。它也支持预热多索引和基于清单的集合工作流程。
构建索引
# Full build at the current directory
./vendor/bin/greph-index ast-index build .
# Full build at an explicit root
./vendor/bin/greph-index ast-index build path/to/repo
# Use a non-default index directory
./vendor/bin/greph-index ast-index build . --index-dir /tmp/greph-ast-index
# Build a mutable overlay that can refresh cheaply on demand
./vendor/bin/greph-index ast-index build . \
--lifecycle opportunistic-refresh \
--auto-refresh-max-files 32 \
--auto-refresh-max-bytes 1048576
默认情况下,事实索引存储在 <root>/.greph-ast-index/。
构建成功后会输出一行摘要:
Built AST index for 2547 files in .greph-ast-index (84931 fact rows, +2547 ~0 -0 =0)
这四项计数分别表示自上次构建以来新增、更新、删除和未更改的文件。
刷新索引
./vendor/bin/greph-index ast-index refresh .
refresh会重新遍历索引根目录,重新解析已更改的文件,并且只重写发生变化的事实行。输出使用与文本索引相同的四项计数。
生命周期配置与预热文本索引的行为相同:
static:从不自动检查新鲜度或修改数据manual-refresh:在stats中报告过期状态,但搜索时从不刷新opportunistic-refresh:仅当变更集仍足够小时,才在搜索时刷新strict-stale-check:拒绝过期搜索,而不执行刷新
查询索引
./vendor/bin/greph-index ast-index search 'new $CLASS()' src
./vendor/bin/greph-index ast-index search '$obj->$method($$$ARGS)' src
./vendor/bin/greph-index ast-index search --json 'array($$$ITEMS)' src
./vendor/bin/greph-index ast-index search -l 'array($$$ITEMS)' src
./vendor/bin/greph-index ast-index search --trace-plan 'new $CLASS()' src
元变量语法与原生 AST 搜索完全相同。完整语法请参阅 模式 / AST 搜索。
选项
| 标志 | 含义 |
|---|---|
--lang NAME |
AST 语言。默认值:php |
-j N、--jobs N |
并行工作进程数 |
-l、--files-with-matches |
仅列出匹配的文件 |
--glob GLOB |
仅包含路径与 GLOB 匹配的文件 |
--type NAME / --type-not NAME |
文件类型筛选器 |
--json |
输出 JSON 格式 |
--no-ignore |
忽略 .gitignore 和 .grephignore |
--hidden |
包含隐藏文件 |
--strict-parse |
遇到解析错误时失败,而不是跳过错误 |
--fallback MODE |
索引缺失时的行为:fail(默认)或 scan |
--index-dir DIR |
使用非默认索引目录 |
--trace-plan |
将预热 AST 规划器诊断信息输出到 stderr |
--show-index-origin |
在多索引或集合模式下,在输出前添加匹配的索引标签 |
索引缺失时的回退行为
默认情况下,如果请求位置不存在索引,ast-index search 会报错。传入 --fallback scan 可改为回退到原生 AST 扫描。对于希望在索引可用时进行索引搜索、但在尚未构建索引时仍需生成结果的工具,这是合适的设置。
./vendor/bin/greph-index ast-index search --fallback scan 'new $CLASS()' src
多索引搜索
重复指定 --index-dir,即可在一条命令中搜索多个预热 AST 索引。这适用于 WordPress 核心加插件或主题等目录布局。
./vendor/bin/greph-index ast-index search \
--index-dir ../wordpress/.greph-ast-index \
--index-dir ./.greph-ast-index \
--show-index-origin \
'new $CLASS()' .
启用 --show-index-origin 后,输出会添加匹配的预热索引条目名称前缀,便于查看命中结果来自哪个根目录。
基于清单的索引集合
对于稳定的预热配置,可以定义清单,并让 greph-index set 对分组索引进行操作:
./vendor/bin/greph-index set build
./vendor/bin/greph-index set stats --mode ast-index --dry-refresh
./vendor/bin/greph-index set search --mode ast-index --show-index-origin 'new $CLASS()' .
集合子命令与直接使用 ast-index 时,共享相同的生命周期处理、过期检查和规划器诊断功能。
规划器诊断
--trace-plan显示 Greph 在完整匹配器运行前如何缩小查询范围。对于 ast-index,通常包括:
- 从模式中提取的粗略事实签名
- 搜索的预热索引数量
- 被事实筛选器排除的文件数量
- 为精确结构验证而重新解析的候选文件数量
索引存储内容
对于每个已索引文件,AST 事实提取器都会为每个“有意义的”节点记录一行。事实类型涵盖 AST 模式最常针对的结构:
- 函数和方法调用(按调用名称)
- 类实例化(按类名)
- 类、接口、trait 和 enum 声明(按名称)
- 函数声明(按名称)
- 方法声明(按名称和可见性)
- use 语句
收到查询后,Greph\Ast\AstFactQuery会将搜索模式转换为粗略事实签名(例如“必须包含任意类的 new”),并与事实存储求交集。通过交集筛选的文件会重新解析,并根据完整模式进行匹配。
这意味着事实索引是一个预筛选器,而非完整保真度的匹配存储。匹配步骤仍会解析源代码,因此结构正确性与原生 AST 搜索相同;不同之处只在于候选集合更小。
适用场景
以下情况适合使用索引 AST 模式:
- 你会针对同一代码库反复运行相同的结构化查询。
- 模式针对可识别的结构(调用名称、类名、声明类型),而这些正是事实提取器存储的内容。
- 你希望降低延迟,无需每次重新解析所有文件。
对于区分特征不在事实集合中的模式(例如没有可识别头部的复杂嵌套表达式),缓存 AST 模式通常更合适,因为它完全跳过解析步骤,无需依赖粗略签名。
编程方式使用
use Greph\Greph;
use Greph\Ast\AstSearchOptions;
use Greph\Index\IndexLifecycleProfile;
Greph::buildAstIndex('.', lifecycle: IndexLifecycleProfile::OpportunisticRefresh);
$matches = Greph::searchAstIndexed(
'new $CLASS()',
'src',
new AstSearchOptions(jobs: 4, tracePlan: true),
);
searchAstIndexed接受与原生 AST 搜索相同的 AstSearchOptions,并返回相同的 list<AstMatch> 结构。对于多索引或基于清单的流程,请使用 searchAstIndexedMany(...) 和 searchAstIndexedSet(...)。