在上一篇文章中,咱们介绍了AST
的Create
。在这篇文章中,咱们接着来介绍AST
的Retrieve
。vue
针对语法树节点的查询(Retrieve
)操做一般伴随着Update
和Remove
(这两种方法见下一篇文章)。这里介绍两种方式:直接访问和traverse
。node
本文中全部对AST
的操做均基于如下这一段代码git
const babylon = require('babylon')
const t = require('@babel/types')
const generate = require('@babel/generator').default
const traverse = require('@babel/traverse').default
const code = `
export default {
data() {
return {
message: 'hello vue',
count: 0
}
},
methods: {
add() {
++this.count
},
minus() {
--this.count
}
}
}
`
const ast = babylon.parse(code, {
sourceType: 'module',
plugins: ['flow']
})
复制代码
对应的AST explorer的表示以下图所示,你们能够自行拷贝过去查看:github
如上图中,有不少节点Node
,如须要获取ExportDefaultDeclaration
下的data
函数,直接访问的方式以下:express
const dataProperty = ast.program.body[0].declaration.properties[0]
console.log(dataProperty)
复制代码
程序输出:bash
Node {
type: 'ObjectMethod',
start: 20,
end: 94,
loc:
SourceLocation {
start: Position { line: 3, column: 2 },
end: Position { line: 8, column: 3 } },
method: true,
shorthand: false,
computed: false,
key:
Node {
type: 'Identifier',
start: 20,
end: 24,
loc: SourceLocation { start: [Object], end: [Object], identifierName: 'data' },
name: 'data' },
kind: 'method',
id: null,
generator: false,
expression: false,
async: false,
params: [],
body:
Node {
type: 'BlockStatement',
start: 27,
end: 94,
loc: SourceLocation { start: [Object], end: [Object] },
body: [ [Object] ],
directives: [] } }
复制代码
这种直接访问的方式能够用于固定程序结构下的节点访问,固然也可使用遍历树的方式来访问每一个Node
。babel
这里插播一个Update
操做,把data
函数修改成mydata
:async
const dataProperty = ast.program.body[0].declaration.properties[0]
dataProperty.key.name = 'mydata'
const output = generate(ast, {}, code)
console.log(output.code)
复制代码
程序输出:ide
export default {
mydata() {
return {
message: 'hello vue',
count: 0
};
},
methods: {
add() {
++this.count;
},
minus() {
--this.count;
}
}
};
复制代码
使用直接访问Node
的方式,在简单场景下比较好用。但是对于一些复杂场景,存在如下几个问题:函数
Node
,好比ThisExpression
、ArrowFunctionExpression
等,这时候咱们可能须要屡次遍历AST
才能完成操做Node
后,要访问他的parent
、sibling
时,不方便,可是这个也很经常使用 @babel/traverse
库能够很好的解决这一问题。traverse
的基本用法以下:
// 该代码打印全部 node.type。 可使用`path.type`,可使用`path.node.type`
let space = 0
traverse(ast, {
enter(path) {
console.log(new Array(space).fill(' ').join(''), '>', path.node.type)
space += 2
},
exit(path) {
space -= 2
// console.log(new Array(space).fill(' ').join(''), '<', path.type)
}
})
复制代码
程序输出:
> Program
> ExportDefaultDeclaration
> ObjectExpression
> ObjectMethod
> Identifier
> BlockStatement
> ReturnStatement
> ObjectExpression
> ObjectProperty
> Identifier
> StringLiteral
> ObjectProperty
> Identifier
> NumericLiteral
> ObjectProperty
> Identifier
> ObjectExpression
> ObjectMethod
> Identifier
> BlockStatement
> ExpressionStatement
> UpdateExpression
> MemberExpression
> ThisExpression
> Identifier
> ObjectMethod
> Identifier
> BlockStatement
> ExpressionStatement
> UpdateExpression
> MemberExpression
> ThisExpression
> Identifier
复制代码
traverse
引入了一个NodePath
的概念,经过NodePath
的API
能够方便的访问父子、兄弟节点。
注意: 上述代码打印出的node.type
和AST explorer中的type
并不彻底一致。实际在使用traverse
时,须要以上述打印出的node.type
为准。如data
函数,在AST explorer中的type
为Property
,但其实际的type
为ObjectMethod
。这个你们必定要注意哦,由于在咱们后面的实际代码中也有用到。
仍以上述的访问data
函数为例,traverse
的写法以下:
traverse(ast, {
ObjectMethod(path) {
// 1
if (
t.isIdentifier(path.node.key, {
name: 'data'
})
) {
console.log(path.node)
}
// 2
if (path.node.key.name === 'data') {
console.log(path.node)
}
}
})
复制代码
上面两种判断Node
的方法均可以,哪一个更好一些,我也没有研究。
经过travase
获取到的是NodePath
,NodePath.node
等价于直接访问获取的Node
节点,能够进行须要的操做。
如下是一些从babel-handbook
中看到的NodePath
的API
,写的一些测试代码,你们能够参考看下,都是比较经常使用的:
parent
traverse(ast, {
ObjectMethod(path) {
if (path.node.key.name === 'data') {
const parent = path.parent
console.log(parent.type) // output: ObjectExpression
}
}
})
复制代码
findParent
traverse(ast, {
ObjectMethod(path) {
if (path.node.key.name === 'data') {
const parent = path.findParent(p => p.isExportDefaultDeclaration())
console.log(parent.type)
}
}
})
复制代码
find 从Node
节点找起
traverse(ast, {
ObjectMethod(path) {
if (path.node.key.name === 'data') {
const parent = path.find(p => p.isObjectMethod())
console.log(parent.type) // output: ObjectMethod
}
}
})
复制代码
container 没太搞清楚,访问的NodePath
若是是在array
中的时候比较有用
traverse(ast, {
ObjectMethod(path) {
if (path.node.key.name === 'data') {
const container = path.container
console.log(container) // output: [...]
}
}
})
复制代码
getSibling 根据index
获取兄弟节点
traverse(ast, {
ObjectMethod(path) {
if (path.node.key.name === 'data') {
const sibling0 = path.getSibling(0)
console.log(sibling0 === path) // true
const sibling1 = path.getSibling(path.key + 1)
console.log(sibling1.node.key.name) // methods
}
}
})
复制代码
skip 最后介绍一下skip
,执行以后,就不会在对叶节点进行遍历
traverse(ast, {
enter(path) {
console.log(path.type)
path.skip()
}
})
复制代码
程序输出根节点Program
后结束。