7.acorn实现插入节点
万万没想到,acorn
在新增Block
或者是Line
注释节点的类型中,表现的如此拉胯。acorn.parse
默认只解析javascript语法,默认不解析注释节点,如果需要获取源文件中的注释节点,还需要用回调函数onComment
来获取全部的注释节点。
import acorn from 'acorn'
import walk from 'acorn-walk'
import escodegen from 'escodegen'
import fs from 'fs-extra'
const workflow = async () => {
const source = await fs.readFile('./demo.js')
const ast = acorn.parse(source, { sourceType: 'module', ecmaVersion: 2021 })
const code = escodegen.generate(ast, {
format: {
semicolons: false,
},
comment: true
})
}
这一段代码,其实什么都没有做,只是把源代码用解析成了ast,然后又用escodegen
转换为源代码,最后你会发现,我源代码中的注释呢????
所以,第一步就是要先把源文件中的注释复原,下面的代码就是acorn
对Block
节点的解析数据结构,start
和end
都是我从loc
中结构出来的。
Node: {
type: 'Block',
value: '*\n * @NOTE before\n ',
start: Position { line: 1, column: 0 },
end: Position { line: 3, column: 3 },
loc: SourceLocation { start: [Position], end: [Position] }
},
下面是VariableDeclaration
节点的数据结构:
Node {
type: 'VariableDeclaration',
start: 272,
end: 308,
loc: SourceLocation {
start: Position { line: 24, column: 0 },
end: Position { line: 24, column: 36 }
},
declarations: [
Node {
type: 'VariableDeclarator',
start: 276,
end: 308,
loc: [SourceLocation],
id: [Node],
init: [Node]
}
],
kind: 'var'
}
我本来是打算用start
和end
做查询的条件,找出comments
数组中的出现的位置最近那一个,就必然是节点的注释。但是后来试了一下,发现用loc.start.line
行号是最佳的选择。
对于还原注释类型的节点代码处理,有一个小操作。我们用行号去找,需要对比的就是注释节点的结束行号,在正常的VariableDeclaration 的起始行号之前,如果满足需求了,就对解析出来的注释节点comments
进行shift
操作。
walk.simple(ast, {
VariableDeclaration (node) {
const { id } = node.declarations[0]
const commentNode = {
type: "Block",
value: ['\n', `* this is comment ${id.name}`, `* ${new Date().toLocaleDateString()} \n`].join('\n'),
loc: node.loc
}
const commentFilters = comments.filter(c => (c.loc.end.line) <= node.loc.start.line)
let tail = null
if (commentFilters.length) {
tail = comments.shift()
}
node.leadingComments = [].concat(tail).filter(Boolean)
node.leadingComments = [].concat(node.leadingComments, commentNode).filter(Boolean)
node.trailingComments = [].concat(node.trailingComments, commentNode).filter(Boolean)
}
})
在上面的代码中,可以用const commentFilters = comments.filter(c => (c.loc.end.line) <= node.loc.start.line)
找出comments
中最靠近当前遍历节点的节点集合,一旦集合里面有值了,就说明当前节点是最靠近注释节点的那个节点,就可以使用shift
操作取出第一位节点。
为什么可以这么做?
注释节点的生成,其实是按照节点在源代码中出现的位置具体生成的,它本来就是有顺序的一个集合。所以我们找最靠近注释节点的正常节点,完全可以使用这个方法,而不用担心它会出现乱序。
在解决还原源代码中的注释节点之后,我们的需要实现的需求,实质是加新的注释节点。这里与babel
一样,可以用leadingComments
与trailingComments
来实现对目标节点添加前置注释和后置注释。
如何将AST重新转换为源代码?
在acorn内部没有提供方法,但是它的官方提供了另外一个escodegen
库来做这个事情,其实质我猜想应该与babel差不了太多。与babel完全相同的地方大致就是它们最终生成的代码都是带句尾分号的,所以也可以采取同样的操作,用prettier来改变代码风格,满足自己的需求。
源代码地址: https://github.com/stack-wuh/mini-cli/blob/main/core/create-node-babel/acorn.js
参考文档: