资讯专栏INFORMATION COLUMN

Babylon-AST初探-代码查询(Retrieve)

wangdai / 2860人阅读

摘要:针对语法树节点的查询操作通常伴随着和这两种方法见下一篇文章。注意上述代码打印出的和中的并不完全一致。如函数,在中的为,但其实际的为。这个大家一定要注意哦,因为在我们后面的实际代码中也有用到。

  在上一篇文章中,我们介绍了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的表示如下图所示,大家可以自行拷贝过去查看:

直接访问

  如上图中,有很多节点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,比如ThisExpressionArrowFunctionExpression等,这时候我们可能需要多次遍历AST才能完成操作

到达特定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://www.ucloud.cn/yun/107869.html

相关文章

  • Babylon-AST初探-代码更新&删除(Update & Remove)

    摘要:操作通常配合来完成。因为是个数组,因此,我们可以直接使用数组操作自我毁灭方法极为简单,找到要删除的,执行就结束了。如上述代码,我们要删除属性,代码如下到目前为止,的我们都介绍完了,下面一篇文章以转小程序为例,我们来实战一波。   通过前两篇文章的介绍,大家已经了解了Create和Retrieve,我们接着介绍Update和 Remove操作。Update操作通常配合Create来完成。...

    levius 评论0 收藏0
  • Babylon-AST初探-实战

    摘要:生成属性这一步,我们要先提取原函数中的的对象。所以这里我们还是主要使用来访问节点获取第一级的,也就是函数体将合并的写法用生成生成生成插入到原函数下方删除原函数程序输出将中的属性提升一级这里遍历中的属性没有再采用,因为这里结构是固定的。   经过之前的三篇文章介绍,AST的CRUD都已经完成。下面主要通过vue转小程序过程中需要用到的部分关键技术来实战。 下面的例子的核心代码依然是最简单...

    godiscoder 评论0 收藏0
  • 宿舍得

    摘要:序列化输出定义序列化器字段的作用补充为列表,关系属性字段的三种方式获取序列化后的数据,,反序列化输入验证验证的调用接收数据继承获取查询字符串中的某个参数值获取查询字符串中的所有值返回一个字典获取请求体中的参数包括表单创建序列化器对象类字典进 1.序列化输出: 1.定义序列化器字段的作用 1. 2. 补充:read_only,write_only 2.`Json...

    lx1036 评论0 收藏0
  • 数据库时区那些事儿 - MySQL的时区处理

    摘要:本文探究了及其驱动对于时区的处理方式,并尝试给出最佳实践。只要保证时区和用户所在时区保持一致即可。下面是运行结果中国标准时间中欧时间可以看到结果是基本符合文档里的说明的,但是要注意,在时区,和的时间部分相差一小时。 原文地址 当JVM时区和数据库时区不一致的时候,会发生什么?这个问题也许你从来没有注意过,但是当把Java程序容器化的时候,问题就浮现出来了,因为目前几乎所有的Docker...

    Ku_Andrew 评论0 收藏0
  • drf实现常用数据缓存

    摘要:使用扩展类使用了视图集用于缓存返回列表数据的视图,与扩展类配合使用,实际是为方法添加了装饰器用于缓存返回单一数据的视图,与扩展类配合使用,实际是为方法添加了装饰器为视图集同时补充和两种缓存,与和一起配合使用。 在以往的后台数据访问时,我们往往都会进行数据库查询,基本的流程是这样的: showImg(https://segmentfault.com/img/bVbooYc?w=784&h...

    yanest 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<