<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>hd_nvim (Herrington Darkholme)</title>
    <link>https://ruby-china.org/hd_nvim</link>
    <description/>
    <language>en-us</language>
    <item>
      <title>[分享] ast-grep：一个基于抽象语法树的代码搜索工具</title>
      <description>&lt;p&gt;大家好，今天我想给大家介绍一个很有用的代码搜索工具，叫做 ast-grep。官网在 &lt;a href="https://ast-grep.github.io/" rel="nofollow" target="_blank"&gt;https://ast-grep.github.io/&lt;/a&gt; ，&lt;a href="https://github.com/ast-grep/ast-grep" rel="nofollow" target="_blank"&gt;https://github.com/ast-grep/ast-grep&lt;/a&gt; 在这里&lt;/p&gt;

&lt;p&gt;它可以让你用抽象语法树（AST）的模式来搜索代码，而不是用正则表达式或者字符串匹配。这样可以让你更精确地找到你想要的代码片段，而不会受到变量名、空格、注释等无关因素的干扰。&lt;/p&gt;

&lt;p&gt;ast-grep 支持多种编程语言，包括 Ruby。&lt;/p&gt;

&lt;p&gt;因为是在 Ruby China 发帖，所以这次主要想给大家展示一下 ast-grep 的 Ruby 例子，以及它的 Web Playground 中可以调试 Ruby 代码。&lt;/p&gt;

&lt;p&gt;同时，我也想和大家比较一下 ast-grep 和论坛坛友的&lt;a href="https://synvert.net/" rel="nofollow" target="_blank" title=""&gt;Synvert&lt;/a&gt;的区别和优劣。Synvert 是一个可以自动修改 Ruby 代码的工具，它也是基于 AST 的，但是它的目标和方法和 ast-grep 不同。&lt;/p&gt;
&lt;h2 id="Hello World 例子"&gt;Hello World 例子&lt;/h2&gt;
&lt;p&gt;假设我们有一段 &lt;code&gt;Idol&lt;/code&gt; 的 class，要去替换其初始化的语句。（改编自 Ruby 官网 hello world）&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The Idol class&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Idol&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capitalize&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;debut&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"苺プロ所属 &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;です!"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Create a new Idol!&lt;/span&gt;
&lt;span class="n"&gt;oshi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Idol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"瑠美衣"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Output "苺プロ所属 瑠美衣です!"&lt;/span&gt;
&lt;span class="n"&gt;oshi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debut&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 ast-grep 里只要写一句就可以找到 Idol.new&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Idol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;$OSHI_NO_KO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以在线看 &lt;a href="https://ast-grep.github.io/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoicnVieSIsInF1ZXJ5IjoiSWRvbC5uZXcoJE9TSElfTk9fS08pIiwicmV3cml0ZSI6Iklkb2wubmV3KFwiUnVieVwiKSIsImNvbmZpZyI6InJ1bGU6XG4gIHBhdHRlcm46IElkb2wubmV3KCRPU0hJX05PX0tPKSIsInNvdXJjZSI6IiMgVGhlIElkb2wgY2xhc3NcbmNsYXNzIElkb2xcbiAgZGVmIGluaXRpYWxpemUobmFtZSlcbiAgICBAbmFtZSA9IG5hbWUuY2FwaXRhbGl6ZVxuICBlbmRcbiAgZGVmIGRlYnV0XG4gICAgcHV0cyBcIuiLuuODl+ODreaJgOWxniAje0BuYW1lfeOBp+OBmSFcIlxuICBlbmRcbmVuZFxuXG4jIENyZWF0ZSBhIG5ldyBJZG9sIVxub3NoaSA9IElkb2wubmV3KFwi55Gg576O6KGjXCIpXG5cbiMgT3V0cHV0IFwi6Iu644OX44Ot5omA5bGeIOeRoOe+juiho+OBp+OBmSFcIlxub3NoaS5kZWJ1dFxuXG4ifQ==" rel="nofollow" target="_blank" title=""&gt;Playground&lt;/a&gt; 执行结果。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/hd_nvim/a65ddfdf-0d3e-43ef-92cb-5b8bb3c21795.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="官网和Playground"&gt;官网和 Playground&lt;/h2&gt;
&lt;p&gt;ast-grep 的官网有详细的文档和例子。对于 Ruby 而言，&lt;a href="https://ast-grep.github.io/catalog/ruby/" rel="nofollow" target="_blank" title=""&gt;Ruby Example Catalog&lt;/a&gt; 收集了一些例子。也包括了 migrate Rails API 的实用例子。&lt;/p&gt;

&lt;p&gt;除了查看文档和例子，你还可以在 ast-grep 的&lt;a href="https://ast-grep.github.io/playground.html" rel="nofollow" target="_blank" title=""&gt;Web Playground&lt;/a&gt;中在线调试 Ruby 代码。
Web Playground 是一个网页版的交互式环境，你可以在左边输入 Ruby 代码，然后在右边输入 AST 模式，就可以看到匹配结果。你匹配结果还会在左边的代码中高亮显示对应的位置。这样，你就可以很方便地测试和调整你的 AST 模式，直到达到你想要的效果。&lt;/p&gt;

&lt;p&gt;除了模式以外，ast-grep 也有更高级的&lt;a href="https://ast-grep.github.io/guide/rule-config/atomic-rule.html" rel="nofollow" target="_blank" title=""&gt;YAML&lt;/a&gt;功能，可以更准确匹配代码。也可以当 linter 来使用。&lt;/p&gt;
&lt;h2 id="和Synvert的比较"&gt;和 Synvert 的比较&lt;/h2&gt;
&lt;p&gt;Ruby 论坛里已经有 Synvert 的介绍啦。相比 Synvert，ast-grep 是基于 tree-sitter 实现的，支持的语言也更多一点。此外，默认的接口形式上，ast-grep 的模式匹配也简单一点。&lt;/p&gt;

&lt;p&gt;比如 把 map + flatten 转成 flat_map 的转换，&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; &lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;flat_map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Synvert 的核心逻辑是这样的：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;find_node&lt;/span&gt; &lt;span class="s1"&gt;'.send
            [receiver=.block
              [caller=.send[message=map]]]
            [message=flatten]
            [arguments.size=0]'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;delete&lt;/span&gt; &lt;span class="ss"&gt;:message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:dot&lt;/span&gt;
  &lt;span class="n"&gt;replace&lt;/span&gt; &lt;span class="s1"&gt;'receiver.caller.message'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;with: &lt;/span&gt;&lt;span class="s1"&gt;'flat_map'&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码还不能直接跑，需要加入 Synvert 的一些 boilerplate 代码&lt;/p&gt;

&lt;p&gt;而 ast-grep 的规则可以这么写，在命令行可以做完代码转换&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sg &lt;span class="nt"&gt;--pattern&lt;/span&gt; &lt;span class="s1"&gt;'$LIST.map {|$V| $E }.flatten '&lt;/span&gt; &lt;span class="nt"&gt;--rewrite&lt;/span&gt; &lt;span class="s1"&gt;'$LIST.flat_map { |$V| $E }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href="https://ast-grep.github.io/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoicnVieSIsInF1ZXJ5IjoiJExJU1QubWFwIHt8JFZ8ICRFIH0uZmxhdHRlbiAiLCJyZXdyaXRlIjoiJExJU1QuZmxhdF9tYXAgeyB8JFZ8ICRFIH0iLCJjb25maWciOiIjIFlBTUwgUnVsZSBpcyBtb3JlIHBvd2VyZnVsIVxuIyBodHRwczovL2FzdC1ncmVwLmdpdGh1Yi5pby9ndWlkZS9ydWxlLWNvbmZpZy5odG1sI3J1bGVcbnJ1bGU6XG4gIGFueTpcbiAgICAtIHBhdHRlcm46IGNvbnNvbGUubG9nKCRBKVxuICAgIC0gcGF0dGVybjogY29uc29sZS5kZWJ1ZygkQSlcbmZpeDpcbiAgbG9nZ2VyLmxvZygkQSkiLCJzb3VyY2UiOiJbMSwgMiwgMywgNF0ubWFwIHsgfGV8IFtlLCBlXSB9LmZsYXR0ZW5cbiMgPT4gXG5bMSwgMiwgMywgNF0uZmxhdF9tYXAgeyB8ZXwgW2UsIGVdIH0ifQ==" rel="nofollow" target="_blank" title=""&gt;ast-grep 代码重写在线 Demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;以上 Ruby 例子可以看到，ast-grep 的 pattern 更简洁而且直观。你只需要用$符号来表示变量，就可以构造出各种各样的 AST 模式。&lt;/p&gt;

&lt;p&gt;以上就是 ast-grep 的介绍，希望能帮助大家在工作或项目中用到。谢谢支持！&lt;/p&gt;</description>
      <author>hd_nvim</author>
      <pubDate>Mon, 24 Jul 2023 11:56:45 +0800</pubDate>
      <link>https://ruby-china.org/topics/43230</link>
      <guid>https://ruby-china.org/topics/43230</guid>
    </item>
  </channel>
</rss>
