1. 抽象语法树(Abstract Syntax Tree)

在计算机科学中,抽象语法树,或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

  1. 抽象语法树的用途

代码语法的检查、代码风格的检查、代码的格式化、代码的高亮、代码错误提示、代码自动补全等等。

如: JSlint、JSHint 对代码错误或风格的检查,IDE 的错误提示、格式化、高亮、自动补全、语言转化等等

我们如何将 JavaScript 代码转成 AST 呢,业界有一些成熟的方案,我们可以使用它们来辅助我们理解 AST,开发我们想要的功能。 :::info 不同的团队可能会根据业务需求制作特定的 AST 树。

  1. ES 标准下的 AST 树
  2. Babel 自定义的 AST 树
  3. Uglifyjs 自定义的 AST 树
  4. …… :::

如何使用 AST 树

我们下面使用 ES 标准下的 AST 树,我们可以使用 ES 提供的工具库:

  1. esprima:解析 JavaScript 的语法,生成 AST 树
  2. estraverse:遍历 AST 树
  3. escodegen:将 AST 树 转成 JavaScript 语法的代码

    AST explorer:将代码转成 AST 的在线网站,各种标准的 AST 都有。

esprima

  1. const esprima = require('esprima')
  2. let code = `function ast(){}`
  3. const AST = esprima.parseScript(code)
  4. console.log(AST)
  5. // {
  6. // "type": "Program",
  7. // "body": [
  8. // {
  9. // "type": "FunctionDeclaration",
  10. // "id": {
  11. // "type": "Identifier",
  12. // "name": "ast",
  13. // "range": [
  14. // 9,
  15. // 12
  16. // ]
  17. // },
  18. // "params": [],
  19. // "body": {
  20. // "type": "BlockStatement",
  21. // "body": [],
  22. // "range": [
  23. // 14,
  24. // 16
  25. // ]
  26. // },
  27. // "generator": false,
  28. // "expression": false,
  29. // "async": false,
  30. // "range": [
  31. // 0,
  32. // 16
  33. // ]
  34. // }
  35. // ],
  36. // "sourceType": "module",
  37. // "range": [
  38. // 0,
  39. // 16
  40. // ]
  41. // }

estraverse

:::info estraverse 的使用文档:https://github.com/estools/estraverse/wiki/Usage ::: estraverse 提供了两个方法,可以让我们在遍历过程中修改 AST 树

  • estraverse.traverseestraverse.replace

    • 两者的区别:后者会返回修改后的 AST 树,前者不会
      1. estraverse.traverse(AST, options)
      2. estraverse.replace(AST, options)
      上面代码中的 options是一个对象,它的可配置的属性如下 ```javascript const options = { // 当进入 AST 某个节点时,会触发这个函数 entry: function (node, parent) { // node 代表当前遍历节点 // parent 是 node 的父节点 }, // 当离开 AST 某个节点时,会触发这个函数 leave: function (node, parent) {

    }, // 设置 fallback 为 iteration 时,可以用于遍历用户自定义的 AST fallback: ‘iteration’, // 此外,fallback 还可以设置为函数,决定了 AST 节点的哪些属性可遍历 fallback: function (node) { // 比如返回节点中,不是 arguments 的属性 return Object.keys(node).filter(key => key !== ‘arguments’) }, // 设置 keys,可用于定义自定义 AST 的哪些节点哪些属性可遍历 keys: {} } 下面我们举几个例子来理解一下javascript // 自定义 AST var tree = { type: ‘TestExpression’, argument: { type: ‘UserType’, value: 20, userLiteral: {

    1. type: 'Literal',
    2. value: 1

    } }, extended: true }; // 配置 const options = { enter: function (node, parent) { console.log(node) }, // 定义了哪些节点,哪些属性可遍历 keys: { // type 为 TestExpression 的节点可遍历,并且遍历该节点的 argument 属性(如果可遍历的话) TestExpression: [‘argument’], // type 为 UserType 的节点可遍历,并且遍历该节点的 userLiteral 属性(如果可遍历的话) UserType: [‘userLiteral’], } }

estraverse.traverse(tree, options)

  1. ```javascript
  2. // 自定义 AST
  3. var tree = {
  4. type: 'TestExpression',
  5. argument: {
  6. type: 'UserType',
  7. value: 20,
  8. userLiteral: {
  9. type: 'Literal',
  10. value: 1
  11. }
  12. },
  13. extended: true
  14. };
  15. const options = {
  16. enter: function (node, parent) {
  17. console.log(node)
  18. },
  19. fallback: 'iteration' // 所有属性可遍历
  20. }
  21. estraverse.traverse(tree, options)
  1. // 自定义 AST
  2. var tree = {
  3. type: 'TestExpression',
  4. argument: {
  5. type: 'UserType',
  6. value: 20,
  7. userLiteral: {
  8. type: 'Literal',
  9. value: 1
  10. }
  11. },
  12. extended: true
  13. };
  14. const options = {
  15. enter: function (node, parent) {
  16. console.log(node)
  17. },
  18. // 设置所有节点的 useLiteral 属性不可遍历
  19. fallback: function (node) {
  20. return Object.keys(node).filter(key => key !== 'userLiteral')
  21. }
  22. }
  23. estraverse.traverse(tree, options)

escodegen

escodegen 可以将 AST 树 转成 JavaScript 语法的代码

  1. const esprima = require('esprima')
  2. const estraverse = require('estraverse')
  3. const escodegen = require('escodegen')
  4. let source = `function ast(){}`
  5. const AST = esprima.parseScript(source)
  6. const options = {
  7. enter: function (node, parent) {
  8. if (node.type === 'Identifier' && parent.type === 'FunctionDeclaration') {
  9. node.name = node.name.toUpperCase()
  10. }
  11. },
  12. }
  13. estraverse.traverse(AST, options)
  14. const result = escodegen.generate(AST)
  15. console.log(result)

JavaScript Parser

JavaScript Parser 是将 JS 源码转化为抽象语法树的解析器。一般来说每个 JS 引擎都会有自己的抽象语法树格式。 :::info JavaScript Parser 的发展:
SpiderMonkey 是世界上第一款 JS 引擎,使用 C/C++ 实现,后来 mozilla 在 MDN 公布了 SpiderMonkey 引擎的 Parser API 和 AST 标准。 随着前端的发展,出现了用 JS 实现的 JavaScript Parser。
最早实现的是 esprima,它基于 SpiderMonkey 的 API 标准,用 JS 实现。
在 ES2015 开始进入告诉发展期,espirma 的更新慢并且不支持 JSX 等扩展语法,于是出现了 estree,estree 向下兼容 esprima。
acorn 也是基于 SpiderMonkey 标准,比 esprima 快,体积更小,而且支持插件,所以后续的库和 Parser 基本都基于 acorn。如 Webpack、Babel、espree(esprima ——> acorn)。

Babel 也不完全兼容 EStree Spec,它对一些细节做了扩充和修改。

  • Literal 扩展成了 StringLiteral、NumbericLiteral、BooleadnLiteral 等,形成了 Babel Spec 标准

除了 SpiderMonkey 系列外,还有别的 AST Spec 标准,比如 UglifyJS 的 AST、Shift 的 AST,它们自建标准都是由自己的原因的。
如 UglifyJS 的作者说,他想换一个 generotor 的成本太大,而且换结构会使它的 generotor 耗时增加一倍等 ::: 常用的 JavaScript Parser

  • esprima
  • acorn
  • shift
  • traceur