在上一篇文章中,咱們介紹了AST
的Create
。在這篇文章中,咱們接着來介紹AST
的Retrieve
。
針對語法樹節點的查詢(Retrieve
)操做一般伴隨着Update
和Remove
(這兩種方法見下一篇文章)。這裏介紹兩種方式:直接訪問和traverse
。 vue
本文中全部對AST
的操做均基於如下這一段代碼node
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的表示以下圖所示,你們能夠自行拷貝過去查看:git
如上圖中,有不少節點Node
,如須要獲取ExportDefaultDeclaration
下的data
函數,直接訪問的方式以下:github
const dataProperty = ast.program.body[0].declaration.properties[0] console.log(dataProperty)
程序輸出:express
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
後結束。