深入理解AST语法编译解析原理

什么是抽象语法树(Abstract Syntax Tree)

webpack和lint等许多的工具和库的核心都是AST,利用AST语法
树的概念可以对代码进行检查分析等操作。当然如果你对AST有充分
的理解后也可以编写自己的类似工具。
复制代码

这些工具的原理都是通过Javascript Parser把代码转化为一颗抽象语法树,它定义了代码的结构,通过操纵这颗树,我们可以精确定位到生命语句、赋值语句、运算语句等等,实现对代码的分析、优化、变更等操作

在计算机科学中,abstract syntax tree 是源代码(编程语言的源代码)的抽象语法结构的一种树的表现形式。

javascript的语法是为了给开发者更好的编程而设计的,但是不适合程序的理解。所以需要转化成AST语法树来更适合程序分析,浏览器编译器一般会把源码转化成AST来进行分析和其他操作。

Abstract Syntax Tree 的用途

  • 可以进行代码语法、风格的检查;代码的格式化、高亮、错误提示、自动补全,如JSLint/JSHint对代码错误或风格的检查。

  • 代码混淆压缩

UglifyJS2等

  • 优化变更代码,改变代码结构
  1. 代码打包工具webpack、rollup等等
  2. CommonJS、AMD、CMD、UMD等代码规范之间的转化
  3. CoffeeScript、TypeScript、JSX等转化成javascript

如何使用ast

使用esprima、escodegen、estraverse实现 ast操作

在没有babel之前我们是这样做的

  1. 解析我们的javascirpt语法 》 ast

esprima: esprima.org/demo/parse.…

  1. 遍历树(先序深度优先遍历) 》 更改树结构

estraverse:

  1. 生成新的内容

escodegen

npm install esprima estraverse escodegen

js语句

function ast() {}
复制代码

esprima编译后

{
  "type": "Program",
  "body": [
    {
      "type": "FunctionDeclaration",
      "id": {
        "type": "Identifier",
        "name": "ast"
      },
      "params": [],
      "body": {
        "type": "BlockStatement",
        "body": []
      },
      "generator": false,
      "expression": false,
      "async": false
    }
  ],
  "sourceType": "script"
}
复制代码

esprima estraverse escodegen 实现基本步骤

let esprima = require("esprima");
let estraverse = require('estraverse');
let escodegen = require('escodegen');
let code = `function ast() {}`
let tree = esprima.parseScript(code);
estraverse.traverse(tree, {
    enter(node) {
        console.log('enter', node.type);
        if(node.type === 'Identifier') {
            node.name = 'tom';
        }
    }
})
let r = escodegen.generate(tree);
console.log(r);
//-----------------------------------
enter Program
enter FunctionDeclaration
enter Identifier
enter BlockStatement

function tom() {
}
复制代码

直接用babel实现操作语法树

实现babel-plugin-arrow-fucntion (箭头函数转换成es5)

箭头函数转成普通函数

  • 箭头函数ast type > ArrowFunctionExpression
  1. 转ast: babel-core
  2. 改ast: babel-types

箭头函数的ast


let sum = (a, b) => { return a + b };
复制代码
{
  "type": "Program",
  "body": [
    {
      "type": "VariableDeclaration",
      "declarations": [
        {
          "type": "VariableDeclarator",
          "id": {
            "type": "Identifier",
            "name": "sum"
          },
          "init": {
            "type": "ArrowFunctionExpression",
            "id": null,
            "params": [
              {
                "type": "Identifier",
                "name": "a"
              },
              {
                "type": "Identifier",
                "name": "b"
              }
            ],
            "body": {
              "type": "BlockStatement",
              "body": [
                {
                  "type": "ReturnStatement",
                  "argument": {
                    "type": "BinaryExpression",
                    "operator": "+",
                    "left": {
                      "type": "Identifier",
                      "name": "a"
                    },
                    "right": {
                      "type": "Identifier",
                      "name": "b"
                    }
                  }
                }
              ]
            },
            "generator": false,
            "expression": false,
            "async": false
          }
        }
      ],
      "kind": "let"
    }
  ],
  "sourceType": "script"
}
        ```
es5有名函数(返回值有代码块)的ast
```js
let babel = require('babel-core');
let t = require('babel-types');

let code = `let sum = (a, b) => { return a + b }`;

//.babelrc
let ArrowPlugin = {
    vistor: {
    //path: 树的类型
        ArrowFunctionExpresstion(path) {
            //通过路径匹配到的节点
            let node = path.node;
            let params = path.params;
            let body = node.body;
            //生成一个函数表达式
            let funcs = t.functionExpression(null, params, body, false, false);
            path.replaceWith(funcs);
        }  
    }
 }

let r = babel.transform(code, {
    plugins: [
        ArrowPlugin
    ]
})

console.log(r.code);
/*
codee 之前和之后的对比
之前: let sum = (a, b) => {return a + b};
之后: let sum = function(a, b) { return a + b};
*/

复制代码

接下来兼容生成返回值没有代码块的ast

let babel = require('bable-core');
let t = require('babel-types');

let code = `let sum = (a + b) => a + b`

let ArrowPlugin = {
    vistor: {
        //找到树的类型
        ArrowFunctionExpression(path) {
            let node = path.node;
            let params = path.params;
            let body = node.body;
            //生成一个函数表达式
            //判断是否body是否有代码块
            if(!t.isBlockStatement(body)){
                //如果不是代码块,也就是上面的情况
                let returnStatement = t.returnStatement(body);
                body = t.blockStatement([returnStatement]);
            }
            
            let func = t.functionExpression(null, params, body, false, false)
            path.replaceWith(funcs);
        }
    }
}
let r = babel.transform(code, {
    plugins: [ArrowPlugin]
})

/*
let sum = function() {
    return a + b;
}
*/
复制代码

实现plugin es6 > es5

  • es5
function Person(name) {
    this.name = name;
}
Person.prototype.getName = function() {
    return this.name;
}
复制代码
  • es5 asts off
{
  "type": "Program",
  "body": [
    {
      "type": "FunctionDeclaration",
      "id": {
        "type": "Identifier",
        "name": "Person"
      },
      "params": [
        {
          "type": "Identifier",
          "name": "name"
        }
      ],
      "body": {
        "type": "BlockStatement",
        "body": [
          {
            "type": "ExpressionStatement",
            "expression": {
              "type": "AssignmentExpression",
              "operator": "=",
              "left": {
                "type": "MemberExpression",
                "computed": false,
                "object": {
                  "type": "ThisExpression"
                },
                "property": {
                  "type": "Identifier",
                  "name": "name"
                }
              },
              "right": {
                "type": "Identifier",
                "name": "name"
              }
            }
          }
        ]
      },
      "generator": false,
      "expression": false,
      "async": false
    },
    {
      "type": "ExpressionStatement",
      "expression": {
        "type": "AssignmentExpression",
        "operator": "=",
        "left": {
          "type": "MemberExpression",
          "computed": false,
          "object": {
            "type": "MemberExpression",
            "computed": false,
            "object": {
              "type": "Identifier",
              "name": "Person"
            },
            "property": {
              "type": "Identifier",
              "name": "prototype"
            }
          },
          "property": {
            "type": "Identifier",
            "name": "getName"
          }
        },
        "right": {
          "type": "FunctionExpression",
          "id": null,
          "params": [],
          "body": {
            "type": "BlockStatement",
            "body": [
              {
                "type": "ReturnStatement",
                "argument": {
                  "type": "MemberExpression",
                  "computed": false,
                  "object": {
                    "type": "ThisExpression"
                  },
                  "property": {
                    "type": "Identifier",
                    "name": "name"
                  }
                }
              }
            ]
          },
          "generator": false,
          "expression": false,
          "async": false
        }
      }
    }
  ],
  "sourceType": "script"
}
复制代码
  • es6
class Person {
    constructor(name) {
        this.name = name;
    }
    getName() {
        return this.name;
    }
}
复制代码
  • es6 ast
{
  "type": "Program",
  "body": [
    {
      "type": "ClassDeclaration",
      "id": {
        "type": "Identifier",
        "name": "Person"
      },
      "superClass": null,
      "body": {
        "type": "ClassBody",
        "body": [
          {
            "type": "MethodDefinition",
            "key": {
              "type": "Identifier",
              "name": "constructor"
            },
            "computed": false,
            "value": {
              "type": "FunctionExpression",
              "id": null,
              "params": [
                {
                  "type": "Identifier",
                  "name": "name"
                }
              ],
              "body": {
                "type": "BlockStatement",
                "body": [
                  {
                    "type": "ExpressionStatement",
                    "expression": {
                      "type": "AssignmentExpression",
                      "operator": "=",
                      "left": {
                        "type": "MemberExpression",
                        "computed": false,
                        "object": {
                          "type": "ThisExpression"
                        },
                        "property": {
                          "type": "Identifier",
                          "name": "name"
                        }
                      },
                      "right": {
                        "type": "Identifier",
                        "name": "name"
                      }
                    }
                  }
                ]
              },
              "generator": false,
              "expression": false,
              "async": false
            },
            "kind": "constructor",
            "static": false
          },
          {
            "type": "MethodDefinition",
            "key": {
              "type": "Identifier",
              "name": "getName"
            },
            "computed": false,
            "value": {
              "type": "FunctionExpression",
              "id": null,
              "params": [],
              "body": {
                "type": "BlockStatement",
                "body": [
                  {
                    "type": "ReturnStatement",
                    "argument": {
                      "type": "MemberExpression",
                      "computed": false,
                      "object": {
                        "type": "ThisExpression"
                      },
                      "property": {
                        "type": "Identifier",
                        "name": "name"
                      }
                    }
                  }
                ]
              },
              "generator": false,
              "expression": false,
              "async": false
            },
            "kind": "method",
            "static": false
          }
        ]
      }
    }
  ],
  "sourceType": "script"
}
复制代码

操作ast实现

let babel = require('babel-core');
let t = require('babel-types');

let classPlugin = {
    vistor = {
        ClassDeclaration(path) {
            let node = path.node;
            let className = path.id.name;
            let classList = node.body.body;
            
            //将className转成一个标识符
            className = t.identifier(className);
            //生成一个函数体
            let funcs = t.functionDeclaration(className, [], t.blockStatement([]), false, false);
            
            let es5Func = [];
            classList.forEach((item, index) => {
                //函数的代码体
                let body = classList[index].body;
                if(item.kind === 'constructor') {
                //如果是构造函数那就生成新的函数将默认的空函数替换
                    let params = item.params.length ? item.params.map(item => item.name) : [];
                    params = t.identifier(params);
                    funcs = t.functionDeclaration(className, [params], body, false, false);
                }else {
                //原型上的方法
                    let protoObj = t.memberExpression(className, t.identifier("prototype"));
                    let left = t.merberExpression(protoObj, t.identifier(item.key.name));
                    let right = t.functionExpression(null, [], body, false, false);
                    let assign = t.assignmentExpression('=', left, right)
                    //多个原型上的方法
                    esFunc.push(assign);
                }
            });
            if(esFunc.length === 0) {
                path.replaceWith(funcs);
            }else {
                es5Func.push(funcs);
                path.replaceWithMultiple(esFuncs);
            }
            
        }
    }
}

babel.transform(code, {
    plugins: [
        classPlugin
    ]
})

/*
function Person(name) {
    this.name = name;
}
Person.prototype.getName = function() {
    return this.name;
}

*/
复制代码

总结

好了,到此为止,如果你认真看到这里,想必你应该知道了,编写一个babel和plugin的原理了,其实就是操作ast语法树进行修改然后再渲染成新的ast语法树的过程,而这个过程中需要的思路和逻辑,就是对应的babel和plugin的原理了。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享