#tree-sitter #search #grep #query-language

bin+lib tree-sitter-grep

tree-sitter-grep 是一个类似于 grep 的搜索工具,它递归地搜索当前目录中的 tree-sitter 查询模式。像 ripgrep 一样,它尊重 gitignore 规则。

2 个版本

0.1.0 2023 年 7 月 14 日
0.1.0-dev.02023 年 7 月 12 日

#1111 in 开发工具

Unlicense 或 MIT 协议

145KB
4K SLoC

tree-sitter-grep

tree-sitter-grep 是一个类似于 grep 的搜索工具,它递归地搜索当前目录中的 tree-sitter 查询 模式。

Build status Crates.io

MIT 或 UNLICENSE 协议双授权。

安装

安装 Rust 工具链后,运行

$ cargo install tree-sitter-grep

用法

$ tree-sitter-grep -q '(trait_bounds) @t'
src/core.rs:14:pub struct Core<'s, M: 's, S> {
src/core.rs:30:impl<'s, M: Matcher, S: Sink> Core<'s, M, S> {
src/mod.rs:622:        P: AsRef<Path>,
src/mod.rs:623:        M: Matcher,
src/mod.rs:624:        S: Sink,
src/mod.rs:644:        M: Matcher,
[...]

指定查询

tree-sitter-grep 使用 tree-sitter 查询 来指定“模式”进行匹配

您可以通过 -q/--query 参数“内联”指定查询

$ tree-sitter-grep -q '(trait_bounds) @t'

或者通过 -Q/--query-file 参数指定到 tree-sitter 查询文件的路径(通常是 *.scm

$ cat queries/trait_bounds.scm
(trait_bounds) @t
$ tree-sitter-grep -Q queries/trait_bounds.scm

tree-sitter-grep 使用 tree-sitter 查询“捕获”(@ whatever)来指定“匹配”的 tree-sitter AST 节点

因此,您的查询必须始终包含至少一个捕获

如果您的查询包含多个捕获(例如,如果您正在使用“预组合”查询或正在使用谓词),tree-sitter-grep 默认将查询中的第一个捕获(我认为是字典序)作为其“目标捕获”

要覆盖此行为,您可以传递 -c/--capture 参数

$ tree-sitter-grep -q '((field_declaration name: (field_identifier) @field_name (#eq? @field_name "pos")) @f)' --capture f
我如何确定我想要的查询是什么?

tree-sitter 查询文档 开始阅读是值得的

然后,为了确定您想要编写的查询的相关 tree-sitter AST 结构,一个 tree-sitter“游乐场”非常有价值,例如交互式在线版本,或者我使用 neovim 的 :InspectTree

在我的经验中,虽然树形查询是一个很好的起点,但它们并不总是足够“表达性”,无法精确指定您想要匹配的AST节点集合

因此,我们也支持指定 过滤器插件,在这里您可以完全“程序化控制”什么构成匹配或不匹配

支持的查询“谓词”

Tree-sitter查询 谓词 允许对匹配的Tree-sitter AST节点进行一些“过滤”

我们使用 Rust tree-sitter绑定,因此“我们支持它们所做的任何谓词”

具体包括以下内容

  • #eq?
$ tree-sitter-grep -q '((field_declaration name: (field_identifier) @field_name (#eq? @field_name "pos")) @f)' --capture f
src/core.rs:20:    pos: usize,
  • #match?
$ tree-sitter-grep -q '((field_declaration name: (field_identifier) @field_name (#match? @field_name "^p")) @f)' --capture f
src/core.rs:20:    pos: usize,
src/mod.rs:157:    passthru: bool,
过滤器插件

当您需要“程序语言的强大功能”来完全指定匹配的“标准”时,您可以编写一个“过滤器插件”

使用过滤器插件

如果您有一个现有的过滤器插件,您可以通过 -f/--filter 参数(带编译后的过滤器动态库文件的路径 .so/.dll/.dylib)指定您想要使用它

$ tree-sitter-grep -q '(trait_bounds) @t' -f path/to/libmy-filter.so

如果过滤器插件期望传递一个“过滤器参数”(例如,以某种方式参数化/配置其行为),则您可以使用 -/--filter-arg 参数指定它

$ tree-sitter-grep -q '(trait_bounds) @t' -f path/to/libmy-filter-that-expects-argument.so -a '{ the_filter_plugin_can_parse_this: "however_it_wants" }'

值得注意的是,如果您提供过滤器插件参数,您实际上不必传递任何树形查询参数(在这种情况下,过滤器插件将对“每个”树形AST节点进行调用)

编写过滤器插件

待办事项:添加一个“指南”

简而言之

虽然在理论上您可能可以在其他语言中编写过滤器插件,但“理想路径”是使用Rust编写它们,并使用来自 examples/ 的示例过滤器插件作为起点/参考

基本思想是,对于每个根据提供的查询参数可能是匹配的树形AST节点,过滤器插件随后会进一步调用,并表明它是否认为该节点是匹配的(基本上作为一个 (&tree_sitter::Node) -> bool “谓词”)

支持的目标语言

目前,tree-sitter-grep "内置"了对以下语言的搜索支持

  • C
  • C++
  • C#
  • CSS
  • Dockerfile
  • Elisp
  • Elm
  • Go
  • HTML
  • Java
  • JavaScript
  • JSON
  • Kotlin
  • Lua
  • Objective-C
  • Python
  • Ruby
  • Rust
  • Swift
  • Toml
  • tree-sitter查询(多么元!)
  • TypeScript

理论上,任何发布/可用的树形语法crate的语言都应该是“公平游戏”。在将来,我们可能支持动态指定/加载额外的语言

或者,您可以随意 提交一个问题,要求“内置”对其他语言的搜索支持

将查询限制为特定文件/语言

默认情况下,tree-sitter-grep 将递归搜索所有“非忽略/隐藏”文件的支持语言/类型,如果它可以对该语言的语法执行提供的查询,则它将搜索该文件的目录以查找匹配项

要显式指定/限制为单一语言,请使用 -l/--language 参数

$ tree-sitter-grep -q '(trait_bounds) @t' -l rust

您还可以通过提供路径参数来限制搜索到某些文件/目录

$ tree-sitter-grep -q '(trait_bounds) @t' src/main.rs src/compiler

其他标志/参数

要获取与自定义匹配输出相关的附加参数的文档,请运行

$ tree-sitter-grep --help

总的来说,我们希望与ripgrep兼容

性能

我还没有进行任何“真正的”基准测试,但普遍的看法似乎是tree-sitter-grep非常快,令人惊讶(尤其是在考虑到tree-sitter并不是针对“从头开始解析”的用例进行优化的情况下)

对于“非巨大”的代码库,我通常看到它在 < 100ms 内运行

对于“巨大”的代码库,例如扫描 > 300k 行代码并输出 > 7000 个匹配项,我看到它在约 360ms 内运行,这仍然感觉“相当快”

编辑器集成

TODO,我相信 @peterstuart 已经编写了一个Emacs插件的初始版本,我刚开始尝试编写一个neovim插件

基本思路可能是,您将能够以与eg grep/ripgrep匹配项交互的方式与tree-sitter-grep的匹配项交互

欢迎贡献/如果您为您选择的编辑器编写了插件,请告诉我们

非目标

  • 试图支持“一切和厨房用品”功能(是的,这有点像ast-grep的阴影)

    我们认为tree-sitter-grep本身肯定有潜力成为一个有用的grep-like工具,而且除此之外,我们将其视为一个“构建块”,理论上可以由其他工具利用,例如搜索和替换、代码修改...

    我已经成功使用tree-sitter-grep作为“一次性大规模自动化重构”的一部分

  • 提出我们自己的自定义查询语法(这里真的很阴影)

    我实际上认为eg ast-grep采取的方法,即提供一个“看起来像代码”的查询语法,非常直观,并且在很多情况下可能是“最容易触及”的方法

    我只个人不太喜欢这个作为工具的方法。我不喜欢它隐藏了“tree-sitter的全部内容”。它感觉就像tree-sitter在一般意义上非常适合在底层技术之上构建各种不同类型的工具,所以我更倾向于“构建块”,让您能够利用现有的知识/专长,并且根据其本质,引导您走向获得更多此类知识/专长的道路。然后在此基础上(或受其启发)构建自己的东西

贡献/问题

代码库是一个相当典型的基于 cargo 的Rust项目

例如,cargo test 运行测试套件

请随意打开问题拉取请求

依赖关系

~212MB
~6M SLoC