在上一篇文章中,我們引見了AST
的Create
。在這篇文章中,我們接着來引見AST
的Retrieve
。
針對語法樹節點的查詢(Retrieve
)操縱一般伴隨着Update
和Remove
(這兩種要領見下一篇文章)。這裏引見兩種體式格局:直接接見和traverse
。
本文中一切對AST
的操縱均基於以下這一段代碼
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的示意以下圖所示,人人能夠自行拷貝過去檢察:
直接接見
如上圖中,有許多節點Node
,如須要獵取ExportDefaultDeclaration
下的data
函數,直接接見的體式格局以下:
const dataProperty = ast.program.body[0].declaration.properties[0]
console.log(dataProperty)
順序輸出:
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
。
這裏插播一個Update
操縱,把data
函數修改成mydata
:
const dataProperty = ast.program.body[0].declaration.properties[0]
dataProperty.key.name = 'mydata'
const output = generate(ast, {}, code)
console.log(output.code)
順序輸出:
export default {
mydata() {
return {
message: 'hello vue',
count: 0
};
},
methods: {
add() {
++this.count;
},
minus() {
--this.count;
}
}
};
運用Traverse接見
運用直接接見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
后完畢。