抽象语法树-AST

2,334 阅读2分钟

抽象语法树(Abstract Syntax Tree)

webpack和Lint等很多的工具和库的核心都是通过Abstract Syntax Tree抽象语法树这个概念来实现对代码的检查、分析等操作的

抽象语法树定义

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

在计算机科学中,抽象语法树(abstract syntax tree或者缩写为AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。

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

JavaScript Parser

  • JavaScript Parser,把js源码转化为抽象语法树的解析器。
  • 浏览器会把js源码通过解析器转为抽象语法树,再进一步转化为字节码或直接生成机器码。
  • 一般来说每个js引擎都会有自己的抽象语法树格式,Chrome的v8引擎,firefox的SpiderMonkey引擎等等,MDN提供了详细SpiderMonkey AST format的详细说明,算是业界的标准。
  • 通过 esprima 把源码转化为AST
  • 通过 estraverse 遍历并更新AST
  • 通过 escodegen 将AST重新生成源码
    // 重命名
    let esprima = require('esprima');
    var estraverse = require('estraverse');
    var escodegen = require("escodegen");
    let code = 'function ast(){}';
    let ast=esprima.parse(code);
    let indent=0;
    function pad() {
        return ' '.repeat(indent);
    }
    estraverse.traverse(ast,{
        enter(node) {
            console.log(pad()+node.type);
            if(node.type == 'FunctionDeclaration'){
                node.id.name = 'ast_rename';
            }
            indent+=2;
         },
        leave(node) {
            indent-=2;
            console.log(pad()+node.type);
    
         }
     });
    let generated = escodegen.generate(ast);
    console.log(generated);

转换箭头函数

@babel/core、babel-types

转换前

const sum = (a,b)=>a+b

转换后

var sum = function sum(a, b) { return a + b; };

代码实现

    let babel = require('@babel/core');
    let t = require('babel-types');
    const code = `const sum = (a,b)=>a+b`;
    // path.node  父节点
    // path.parentPath 父路径
    let transformArrowFunctions = {
        visitor: {
            ArrowFunctionExpression: (path, state) => {
                let node = path.node;
                let id = path.parent.id;
                let params = node.params;
                let body=t.blockStatement([
                    t.returnStatement(node.body)
                ]);
                let functionExpression = t.functionExpression(id,params,body,false,false);
                path.replaceWith(functionExpression);
            }
        }
    }
    const result = babel.transform(code, {
        plugins: [transformArrowFunctions]
    });
    console.log(result.code); 

把类编译为Function

转换前

  class Person {
      constructor(name) {
          this.name=name;
      }
      getName() {
          return this.name;
      }
  }

转换后

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

实现

    let babel = require('@babel/core');
    let t=require('babel-types');
    let source=`
        class Person {
            constructor(name) {
                this.name=name;
            }
            getName() {
                return this.name;
            }
        }
    `;
    let ClassPlugin={
        visitor: {
            ClassDeclaration(path) {
                let node=path.node;
                let id=node.id;
                let constructorFunction = t.functionDeclaration(id,[],t.blockStatement([]),false,false);
                let methods=node.body.body;
                let functions = [];
                methods.forEach(method => {
                    if (method.kind == 'constructor') {
                        constructorFunction = t.functionDeclaration(id,method.params,method.body,false,false);
                        functions.push(constructorFunction);
                    } else {
                        let memberObj=t.memberExpression(t.memberExpression(id,t.identifier('prototype')),method.key);
                        let memberFunction = t.functionExpression(id,method.params,method.body,false,false);
                        let assignment = t.assignmentExpression('=',memberObj,memberFunction);
                        functions.push(assignment);
                    }
                });
                if (functions.length ==1) {
                    path.replaceWith(functions[0]);
                } else {
                    path.replaceWithMultiple(functions);
                }
            }
        }
    }
    
    
    const result = babel.transform(source,{
        plugins:[
            ClassPlugin
        ]
    });
    console.log(result.code);