怎么对AST插入新节点
在上一篇中,我们介绍了什么是语句,什么是表达式。还记得之前做过的获取模块中的变量的习题吗?现在我们是不是可以回顾一下,我们在用acron-walk
的时候,用的表达式是不是就是ExportNamedDelacration
导出表达式。
当然,这个小需求可能不是那么太贴合我们的业务,我们之所以操作AST,并不是为了仅仅查一下导出的变量,增删改查才是我们的最终目的。下面我们一起学习一下AST的简单操作。
先声明一下,下面的几种基础类型数据:
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 前置
认识"注释"的节点类型("CommentLine", "CommentBlock")- 找到需要增加注释的节点
- 调用提供的
addComment
添加注释
2.实现
2.1 优化前
2.2优化后
如上图,我本来是打算用Identity
标识符找去到var
节点,然后对这个节点前、后新增一段注释。可以看一下这段代码:
// 这里不对,这个是在var后面加了注释
Identifier (path) {
if (path.node.name === 'str') {
path.node.name = 'str'
path.parentPath.parentPath.addComment('leading', '这里是一条注释', true)
}
}
在这个基础上,就可以继续优化一下,去找VariableDeclarator
这个类型的节点,
/**
* @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
,现在的改动就是全部的变量前加上了前置注释。
VariableDeclaration (path) {
const [head] = path.node.declarations
path.addComment('trailing', `${head.id.name} 的后置注释`, false)
path.addComment('leading', `${head.id.name} 的前置注释`, false)
}
@babel/types
这里是补充的内容,是后期在学习的过程中接触到了新的工具。
这是一个极简的工具库,在注释方面提供了两个函数addComment
与addComments
。需要值得注意的是在ast中,我们的操作都离不开types内部提供的方法,我的理解是,在ast中我们创建的都是token而不是直接量。
在javascript中,我们可以直接使用var a = 10
来声明一个变量a,同时赋值为10。从最外层看它是一个声明表达式VariableDeclaration
,其次是VariableDeclarator
变量,再次拆分成为"var"、"a"和10,它们分别由``identifier标识符和
NumericLiteral`数值类型的token。
所以我们就可以理解,为什么addComments
方法中的子节点,是由addComment
组成的一个集合。在AST中出现的每一个节点,都是一个token。
下面是一个使用addComment
的例子:
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
的例子:
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
总结
- 用标识符(Identifier)是可以找到全部的
VariableDeclration
类型的全部节点的,但是它所在的位置太深了,不得不得使用parentPath
去向上检索。 - 可以直接使用最大的选择器
VariableDeclaration
,将找出来的节点都加上全部前置、后置注释 @babel/core
提供了transformFromAstSync
方法,用于将AST转换为源文件- 转化的源文件,每一行结尾都被加上了分号,如果你不需要这个分号,可以使用
prettier
去掉分号 - 如果转换的代码样式不符合规范,可以继续使用
prettier
去转换
源码地址:
- 使用常规方法添加注释节点: https://github.com/stack-wuh/mini-cli/blob/main/core/create-node-babel/babel.js
- 使用@babel/types添加注释节点: https://github.com/stack-wuh/mini-cli/blob/main/core/create-number-node-babel/babel.js
参考链接:
- @babel/core: https://babel.docschina.org/docs/en/6.26.3/babel-core/
- @babel/traverse: https://babel.docschina.org/docs/en/6.26.3/babel-traverse/
- @babel/types: https://babel.docschina.org/docs/en/6.26.3/babel-types/#api