1.深入研究ast-grep的米搜米搜模式: 一文解析Tree-Sitter核心概念
深入研究ast-grep的模式: 一文解析Tree-Sitter核心概念
如果你对代码重构工具有兴趣,你可能听说过 ast-grep,源码它是米搜米搜一个基于 Tree-sitter 的工具,可以进行结构搜索和替换。源码 ast-grep 允许你编写代码模式,米搜米搜根据代码的源码婚庆平台源码结构,而不仅仅是米搜米搜文本来查找和修改代码。但它是源码如何在引擎盖下工作的呢?在这篇文章中,我将为你深入介绍ast-grep的米搜米搜模式。它还能帮助你理解Tree-sitter的源码核心概念。
模式是米搜米搜一种写和读描述语法树的表达式的方便方法。 它类似于代码,源码但有一些特殊的米搜米搜语法和语义,允许你根据语法树的源码结构、类型或内容来匹配部分。米搜米搜
ast-grep的模式易学难精。它要求你了解目标语言的Tree-sitter语法和含义,以及ast-grep的规则和惯例。
在本文中,我们将帮助你掌握所有基于Tree-sitter的工具所共有的核心概念。我们还将向你展示如何编写ast-grep模式,extjs源码结构让它的全部威力都为你所用。
什么是Tree-sitter? ast-grep使用 Tree-sitter作为它的底层解析框架,这是因为它的流行度、高性能和健壮性。
Tree-sitter是一个生成解析器的工具,并提供一个增量解析库。
解析器是一个将源代码文件作为输入并产生一个描述代码组织的树状结构的程序。(该树状结构不是抽象语法树,我们将在后面看到)。
为各种编程语言编写优秀的解析器是一项艰巨的任务,尤其是对于像ast-grep这样的单一项目。幸运的是,Tree-sitter是一个被社区广为传唱的好工具。许多主流语言,如C、Java、JavaScript、Python、Rust等,都被Tree-sitter支持。读取php源码使用Tree-sitter作为ast-grep的底层解析库,可以让这个工具支持任何具有良好维护的语法的语言。
Tree-sitter的另一个好处是它的增量性质。增量解析器是指当源代码文件被编辑时能够有效地更新语法树的解析器,而不需要重新解析整个文件。在 ast-grep的交互式编辑中,它可以非常快速地运行在每一个代码变化上。
最后,Tree-sitter还可以优雅地处理语法错误,它可以在同一个文件中解析多种语言。这使得模式代码的解析更加稳健,更容易编写。在未来,我们还可以像Vue那样支持多语言的源代码。
当你使用 ast-grep 来搜索源代码中的模式时,你需要了解文本匹配和结构匹配之间的区别。
源代码输入是文本,是遵循某些语法规则的字符序列。你可以使用常见的搜索工具,如 silver-searcher或 ripgrep来搜索源代码中的分词源码文本模式。
然而,ast-grep并不直接与文本匹配模式。相反,它将文本解析成一个代表代码语法的树状结构。这使得ast-grep能够根据代码的语义来匹配模式,而不仅仅是其表面现象。这就是所谓的 结构搜索,它搜索的是具有特定结构的代码,而不仅仅是特定的文本。
因此,你写的模式也必须是有效的语法,可以与代码树进行比较。
ast-grep中的文本搜索 虽然pattern在结构上匹配代码,但你可以使用ref=" ast-grep.github.io/guid...">原子规则regex通过指定正则表达式来匹配节点的文本。这样,就可以在ast-grep中结合文本和结构匹配。
我们可以用两种类型的树结构来表示代码的语法和语义: AST和CST。
AST代表 抽象语法树,它是代码的简化表示,省略了一些细节,掘金指标源码如标点符号和空白处。CST代表 具体语法树(Concrete Syntax Tree),它是对代码的一种更保真的表示,包括所有的细节。
Tree sitter是一个库,可以将代码解析为许多编程语言的CST。因此,ast-grep与它的名字相反,是根据CST模式而不是AST来搜索和重写代码的。
让我们通过一个例子来看看为什么CST更有意义。考虑一下1 + 1这个JavaScript片段。它的AST表示法 看起来像这样:
精明的读者应该注意到重要的运算符+并没有在AST中被编码。与此同时,代码的CST则忠实地表达了所有的关键信息。
你可能会想,使用CST是否会让不重要的空白影响你的搜索结果。幸运的是,ast-grep使用了一种智能匹配算法,可以在适当的时候跳过CST中的琐碎节点,帮你节省了很多麻烦。
如果不关心标点符号和空白,我们就可以将CST转换为AST。Tree-sitter有两种类型的节点:命名节点和匿名节点(未命名节点)。
更重要的命名节点在语法规则中被定义为常规名称,如binary_expression或identifier。不太重要的匿名节点是用字面字符串定义的,如", "或 "+"。
命名节点对于理解代码的结构和意义更为重要,而匿名节点则不那么重要,有时会被 ast-grep 的匹配算法跳过。
下面的例子改编自 Tree-sitter的官方指南,显示了语法定义的不同。
实际上,命名的节点有一个叫做kind的属性,表示它们的名字。你可以使用ast-grep的ref=" ast-grep.github.io/guid...">原子规则kind来找到具体的AST节点。下面的例子的 Playground链接。
更进一步,ast-grep的元变量默认只匹配命名的节点。return $A 只匹配下面的第一条语句。 Playground链接。
我们可以使用两个米元符号(非笔误)$$VAR在模式匹配的结果中包括匿名节点。return $$A将匹配上面的两个语句。 Playground链接。
有时,仅仅使用kind并不足以找到我们想要的节点。一个节点可能有几个具有相同kind的子节点,但在代码中的作用不同。例如,在JavaScript中,一个对象可能有多个键和值,但它们都可以是字符串。
为了区分它们,我们可以使用field来指定一个节点和它的父节点之间的关系。在ast-grep中,field可以用在两个 关系规则中:has和inside。
has和inside接受一个特殊的配置项,叫做field。field的值是父-子关系的字段名。例如,JavaScript对象中的键值对有两个孩子:一个字段是key,另一个字段value。我们可以用 这个规则来匹配string的key节点。
field可以帮助我们缩小搜索范围,使模式更加精确。
我们也可以用has重写上面的规则,搜索带有字符串key的键值对。 Playground链接。
kind和field的关键区别: kind是节点本身的属性。只有命名节点才有。 field是父子关系的属性。匿名节点也可以有。
一个节点同时拥有kind和field可能会让新用户感到困惑。kind属于节点本身,在ast-grep的Playground上用蓝色文本表示。子节点只有相对于它的父节点才有field,反之亦然。字段在Playground中用深**文本表示。由于field是节点关系的一个属性,匿名节点也可以有field。例如,binary_expression的1 + 1中的+的field就是operator。
ast-grep比Tre-sitter走得更远。它有关于节点的 "重要性 "的概念。
即使是“重要性”这个概念也是不够的。 大多数Tree-Sitter语言没有在命名节点AST中编码所有的关键语义。即使我们定义了范围更广的显著节点,也不足以表示代码的意义。我们必须保留一些琐碎节点来进行精确匹配。
Tree-sitter一般不会用命名节点来编码所有的语义。例如,class A { get method() { } } 和 class A { method() { } } 在Tree-sitter的AST中是等同的。关键标记get没有命名,也没有field。它是一个琐碎节点!
如果你不关心这个方法是getter方法、静态方法还是实例方法,你可以使用class $A { method() { } }来 一次性匹配所有三种方法。如果你需要区分getter方法和普通方法,你可以 拼出完整的方法修饰符。
感谢你阅读到这里! 这篇文章中有许多概念。让我们用一段话来总结一下。
ast-grep使用Tree-sitter将文本源代码解析成一个详细的树状结构,称为CST。我们可以从CST中得到AST,只保留命名节点,这些节点有kind。为了搜索语法树中的节点,可以同时使用节点kind和节点field,后者是一个子节点相对于其父节点的特殊标记。一个有kind或者有field的节点就是一个重要的节点。