Skip to content

怎么对AST插入新节点

在上一篇中,我们介绍了什么是语句,什么是表达式。还记得之前做过的获取模块中的变量的习题吗?现在我们是不是可以回顾一下,我们在用acron-walk的时候,用的表达式是不是就是ExportNamedDelacration导出表达式。

当然,这个小需求可能不是那么太贴合我们的业务,我们之所以操作AST,并不是为了仅仅查一下导出的变量,增删改查才是我们的最终目的。下面我们一起学习一下AST的简单操作。

先声明一下,下面的几种基础类型数据:

javascript
var num1 = 10

var str = 'str1'

var date1 = '2023-04-01'

var arr1 = ['1', 2, 3, null, undefined]

var func1 = function () {
  console.log('=====> func1')
}

var func2 = async function () {
  console.log('====> func2')
}

var regExp = new RegExp('\d+', 'gi')

1. 在声明语句后新增注释

1.1 前置
  1. 认识"注释"的节点类型("CommentLine", "CommentBlock")
  2. 找到需要增加注释的节点
  3. 调用提供的addComment添加注释

2.实现

2.1 优化前

image-20230405171939875

2.2优化后

image-20230405174152568

如上图,我本来是打算用Identity标识符找去到var节点,然后对这个节点前、后新增一段注释。可以看一下这段代码:

javascript
    // 这里不对,这个是在var后面加了注释
    Identifier (path) {
      if (path.node.name === 'str') {
        path.node.name = 'str'
        path.parentPath.parentPath.addComment('leading', '这里是一条注释', true)
      }
    }

在这个基础上,就可以继续优化一下,去找VariableDeclarator这个类型的节点,

javascript
    /**
     * @NOTE 可以用声明表达式去找对应的节点
     * 
     * !1. 现在是有一个前置条件, 只给num1这个变量加前后注释
     */
    VariableDeclarator (path) {
      if (path.node.id.name === 'num1') {
        // console.log('=====> variableDeclaration.path', path)
        /**
         * @NOTE false代表是多行注释,true 代表单行注释
         */
        path.parentPath.addComment('leading', ' before \n *asd \n *sss \n', false)
        path.addComment('trailing', 'after', true)
      }
    }

现在,离我们预定的目标还差一点,我们的目标是给全部的VariableDeclaration添加一段前置注释,所以这个节点选择器还可以改一下,把VariableDeclarator改成VariableDeclaration,现在的改动就是全部的变量前加上了前置注释。

javascript
    VariableDeclaration (path) {
      const [head] = path.node.declarations


      path.addComment('trailing', `${head.id.name} 的后置注释`, false)
      path.addComment('leading', `${head.id.name} 的前置注释`, false)
    }

@babel/types

这里是补充的内容,是后期在学习的过程中接触到了新的工具。

这是一个极简的工具库,在注释方面提供了两个函数addCommentaddComments。需要值得注意的是在ast中,我们的操作都离不开types内部提供的方法,我的理解是,在ast中我们创建的都是token而不是直接量。

在javascript中,我们可以直接使用var a = 10来声明一个变量a,同时赋值为10。从最外层看它是一个声明表达式VariableDeclaration,其次是VariableDeclarator变量,再次拆分成为"var"、"a"和10,它们分别由``identifier标识符和NumericLiteral`数值类型的token。

image-20230409180502524

所以我们就可以理解,为什么addComments方法中的子节点,是由addComment组成的一个集合。在AST中出现的每一个节点,都是一个token。

下面是一个使用addComment的例子:

javascript
 import t from '@babel/types' 

 const variableDeclaration = t.variableDeclaration('var',
    [t.variableDeclarator(t.identifier('num1'), t.numericLiteral(1)),
    t.variableDeclarator(t.identifier('num2'), t.numericLiteral(2))]
  )

  t.addComment(variableDeclaration, 'leading', 'before', false)

下面是一个使用addComments的例子:

javascript
  import t from '@babel/types' 

  const variableDeclaration1 = t.variableDeclaration('var', [
    t.variableDeclarator(t.identifier('cn1'), t.numericLiteral(11)),
    t.variableDeclarator(t.identifier('cn2'), t.numericLiteral(12))
  ])
  
   t.addComments(variableDeclaration1, 'CommentBlock', [
    t.addComment(variableDeclaration1, 'leading', '第一行注释'),
    t.addComment(variableDeclaration1, 'leading', '第二行注释'),
  ])

为了统一使用,在后面的文档中,只要出现了types指的就是@babel/types

总结

  1. 用标识符(Identifier)是可以找到全部的VariableDeclration类型的全部节点的,但是它所在的位置太深了,不得不得使用parentPath去向上检索。
  2. 可以直接使用最大的选择器VariableDeclaration,将找出来的节点都加上全部前置、后置注释
  3. @babel/core提供了transformFromAstSync方法,用于将AST转换为源文件
  4. 转化的源文件,每一行结尾都被加上了分号,如果你不需要这个分号,可以使用prettier去掉分号
  5. 如果转换的代码样式不符合规范,可以继续使用prettier去转换

源码地址:

  1. 使用常规方法添加注释节点: https://github.com/stack-wuh/mini-cli/blob/main/core/create-node-babel/babel.js
  2. 使用@babel/types添加注释节点: https://github.com/stack-wuh/mini-cli/blob/main/core/create-number-node-babel/babel.js

参考链接:

  1. @babel/core: https://babel.docschina.org/docs/en/6.26.3/babel-core/
  2. @babel/traverse: https://babel.docschina.org/docs/en/6.26.3/babel-traverse/
  3. @babel/types: https://babel.docschina.org/docs/en/6.26.3/babel-types/#api

MIT License.