Babylon-AST初探-代碼查詢(Retrieve)

  在上一篇文章中,我們引見了ASTCreate。在這篇文章中,我們接着來引見ASTRetrieve
  針對語法樹節點的查詢(Retrieve)操縱一般伴隨着UpdateRemove(這兩種要領見下一篇文章)。這裏引見兩種體式格局:直接接見和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的示意以下圖所示,人人能夠自行拷貝過去檢察:

《Babylon-AST初探-代碼查詢(Retrieve)》

直接接見

  如上圖中,有許多節點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的體式格局,在簡樸場景下比較好用。然則關於一些龐雜場景,存在以下幾個題目:

  1. 須要處置懲罰某一範例的Node,比方ThisExpressionArrowFunctionExpression等,這時刻我們能夠須要屢次遍歷AST才完成操縱
  2. 抵達特定Node后,要接見他的parentsibling時,不輕易,然則這個也很經常使用

  @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的觀點,經由過程NodePathAPI能夠輕易的接見父子、兄弟節點。

  注重: 上述代碼打印出的node.type和AST explorer中的type並不完全一致。現實在運用traverse時,須要以上述打印出的node.type為準。如data函數,在AST explorer中的typeProperty,但其現實的typeObjectMethod。這個人人肯定要注重哦,由於在我們背面的現實代碼中也有效到。

  仍以上述的接見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獵取到的是NodePathNodePath.node等價於直接接見獵取的Node節點,能夠舉行須要的操縱。

  以下是一些從babel-handbook中看到的NodePathAPI,寫的一些測試代碼,人人能夠參考看下,都是比較經常使用的:

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)
    }
  }
})

findNode節點找起

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后完畢。

    原文作者:肉欣
    原文地址: https://segmentfault.com/a/1190000015127262
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞