关于javascript:AST抽象语法树

44次阅读

共计 8694 个字符,预计需要花费 22 分钟才能阅读完成。

AST 形象语法树

why

支流我的项目插件的用处: javascript 转译、代码压缩、css 预处理、eslint、prettier 等都建设在 AST 的根底上。

what

according to the grammar of a programming language, each AST node corresponds to an item of a source code.(依据编程语言的语法,每个 AST 节点对应一个源代码项。)

demo

链接地址:astexplorer.net
AST 解析工具

js 语法

function square(n) {return n * n;}

ast 语法树

// Parser acorn-8.0.1
{
  "type": "Program",
  "start": 0,
  "end": 38,
  "body": [
    {
      "type": "FunctionDeclaration",
      "start": 0,
      "end": 38,
      "id": {
        "type": "Identifier",
        "start": 9,
        "end": 15,
        "name": "square"
      },
      "expression": false,
      "generator": false,
      "async": false,
      "params": [
        {
          "type": "Identifier",
          "start": 16,
          "end": 17,
          "name": "n"
        }
      ],
      "body": {
        "type": "BlockStatement",
        "start": 19,
        "end": 38,
        "body": [
          {
            "type": "ReturnStatement",
            "start": 23,
            "end": 36,
            "argument": {
              "type": "BinaryExpression",
              "start": 30,
              "end": 35,
              "left": {
                "type": "Identifier",
                "start": 30,
                "end": 31,
                "name": "n"
              },
              "operator": "*",
              "right": {
                "type": "Identifier",
                "start": 34,
                "end": 35,
                "name": "n"
              }
            }
          }
        ]
      }
    }
  ],
  "sourceType": "module"
}

从纯文本中失去 AST(通过编译器)

  • 词法剖析

    scanner。它读取咱们的代码,而后把他们依照预约的规定合并成一个个的标识(tokens). 同时,它会移除空白符,正文等。最初,整个代码将被宰割进一个 tokens 列表(或者说一维数组)。当词法剖析源代码的时候,它会一个一个字母的读取代码。当它遇到空格,操作符,或者特殊符号的时候,它会认为一个会话曾经实现了。

  • 语法解析,也叫解析器

    它将词法剖析进去的数组转化成树形的表达形式。同时验证语法,语法错误,抛出语法错误。
    当生成树的时候,解析器会删除一些没必要的标识 tokens(比方不残缺的括号),因而 AST 不是 100% 与源码匹配,但咱们曾经可能晓得如何解决了。题外话,解析器 100% 笼罩所有代码构造生成树叫做 CST(具体语法树)

更多编译器常识

the-super-tiny-compiler- 仓库地址

将 Lisp 转化为 C 语言

LangSandbox- 仓库地址

发明本人的语言,并将它编译成 C 语言或者机器语言,最初运行它。

第三方库生成 AST

重点介绍 Babylon

Babylon
Babylon is a JavaScript parser used in Babel.Support for JSX, Flow, Typescript.

babel

babel 是一个 javascript 编译器。宏观来说,它分为 3 个阶段运行代码:解析(parsing),转译(transforming),生成(generation)。咱们能够给 babel 一些 javascript 代码,它批改代码而后生成新的代码返回。过程即创立 AST,遍历树,批改 tokens,最初从 AST 中生成最新的代码。

babel 解析生成

1、应用 babylon 解析代码生成语法树

import * as babylon from "babylon";
const code = `
  const abc = 5;
`;
const ast = babylon.parse(code);

生成树后果:

{
  "type": "File",
  "start": 0,
  "end": 18,
  "loc": {
    "start": {
      "line": 1,
      "column": 0
    },
    "end": {
      "line": 3,
      "column": 0
    }
  },
  "program": {
    "type": "Program",
    "start": 0,
    "end": 18,
    "loc": {
      "start": {
        "line": 1,
        "column": 0
      },
      "end": {
        "line": 3,
        "column": 0
      }
    },
    "sourceType": "script",
    "body": [
      {
        "type": "VariableDeclaration",
        "start": 3,
        "end": 17,
        "loc": {
          "start": {
            "line": 2,
            "column": 2
          },
          "end": {
            "line": 2,
            "column": 16
          }
        },
        "declarations": [
          {
            "type": "VariableDeclarator",
            "start": 9,
            "end": 16,
            "loc": {
              "start": {
                "line": 2,
                "column": 8
              },
              "end": {
                "line": 2,
                "column": 15
              }
            },
            "id": {
              "type": "Identifier",
              "start": 9,
              "end": 12,
              "loc": {
                "start": {
                  "line": 2,
                  "column": 8
                },
                "end": {
                  "line": 2,
                  "column": 11
                },
                "identifierName": "abc"
              },
              "name": "abc"
            },
            "init": {
              "type": "NumericLiteral",
              "start": 15,
              "end": 16,
              "loc": {
                "start": {
                  "line": 2,
                  "column": 14
                },
                "end": {
                  "line": 2,
                  "column": 15
                }
              },
              "extra": {
                "rawValue": 5,
                "raw": "5"
              },
              "value": 5
            }
          }
        ],
        "kind": "const"
      }
    ],
    "directives": []},
  "comments": [],
  "tokens": [
    {
      "type": {
        "label": "const",
        "keyword": "const",
        "beforeExpr": false,
        "startsExpr": false,
        "rightAssociative": false,
        "isLoop": false,
        "isAssign": false,
        "prefix": false,
        "postfix": false,
        "binop": null,
        "updateContext": null
      },
      "value": "const",
      "start": 3,
      "end": 8,
      "loc": {
        "start": {
          "line": 2,
          "column": 2
        },
        "end": {
          "line": 2,
          "column": 7
        }
      }
    },
    {
      "type": {
        "label": "name",
        "beforeExpr": false,
        "startsExpr": true,
        "rightAssociative": false,
        "isLoop": false,
        "isAssign": false,
        "prefix": false,
        "postfix": false,
        "binop": null
      },
      "value": "abc",
      "start": 9,
      "end": 12,
      "loc": {
        "start": {
          "line": 2,
          "column": 8
        },
        "end": {
          "line": 2,
          "column": 11
        }
      }
    },
    {
      "type": {
        "label": "=",
        "beforeExpr": true,
        "startsExpr": false,
        "rightAssociative": false,
        "isLoop": false,
        "isAssign": true,
        "prefix": false,
        "postfix": false,
        "binop": null,
        "updateContext": null
      },
      "value": "=",
      "start": 13,
      "end": 14,
      "loc": {
        "start": {
          "line": 2,
          "column": 12
        },
        "end": {
          "line": 2,
          "column": 13
        }
      }
    },
    {
      "type": {
        "label": "num",
        "beforeExpr": false,
        "startsExpr": true,
        "rightAssociative": false,
        "isLoop": false,
        "isAssign": false,
        "prefix": false,
        "postfix": false,
        "binop": null,
        "updateContext": null
      },
      "value": 5,
      "start": 15,
      "end": 16,
      "loc": {
        "start": {
          "line": 2,
          "column": 14
        },
        "end": {
          "line": 2,
          "column": 15
        }
      }
    },
    {
      "type": {
        "label": ";",
        "beforeExpr": true,
        "startsExpr": false,
        "rightAssociative": false,
        "isLoop": false,
        "isAssign": false,
        "prefix": false,
        "postfix": false,
        "binop": null,
        "updateContext": null
      },
      "start": 16,
      "end": 17,
      "loc": {
        "start": {
          "line": 2,
          "column": 15
        },
        "end": {
          "line": 2,
          "column": 16
        }
      }
    },
    {
      "type": {
        "label": "eof",
        "beforeExpr": false,
        "startsExpr": false,
        "rightAssociative": false,
        "isLoop": false,
        "isAssign": false,
        "prefix": false,
        "postfix": false,
        "binop": null,
        "updateContext": null
      },
      "start": 18,
      "end": 18,
      "loc": {
        "start": {
          "line": 3,
          "column": 0
        },
        "end": {
          "line": 3,
          "column": 0
        }
      }
    }
  ]
}

2、应用 babel 的转换器 transforming 语法树语法

import traverse from "babel-traverse";
traverse(ast, {enter(path) {if (path.node.type === "Identifier") {
      path.node.name = path.node.name
        .split("")
        .reverse()
        .join("");
    }
  }
});
{
  "type": "File",
  "start": 0,
  "end": 18,
  "loc": {
    "start": {
      "line": 1,
      "column": 0
    },
    "end": {
      "line": 3,
      "column": 0
    }
  },
  "program": {
    "type": "Program",
    "start": 0,
    "end": 18,
    "loc": {
      "start": {
        "line": 1,
        "column": 0
      },
      "end": {
        "line": 3,
        "column": 0
      }
    },
    "sourceType": "script",
    "body": [
      {
        "type": "VariableDeclaration",
        "start": 3,
        "end": 17,
        "loc": {
          "start": {
            "line": 2,
            "column": 2
          },
          "end": {
            "line": 2,
            "column": 16
          }
        },
        "declarations": [
          {
            "type": "VariableDeclarator",
            "start": 9,
            "end": 16,
            "loc": {
              "start": {
                "line": 2,
                "column": 8
              },
              "end": {
                "line": 2,
                "column": 15
              }
            },
            "id": {
              "type": "Identifier",
              "start": 9,
              "end": 12,
              "loc": {
                "start": {
                  "line": 2,
                  "column": 8
                },
                "end": {
                  "line": 2,
                  "column": 11
                },
                "identifierName": "abc"
              },
              "name": "cba"
            },
            "init": {
              "type": "NumericLiteral",
              "start": 15,
              "end": 16,
              "loc": {
                "start": {
                  "line": 2,
                  "column": 14
                },
                "end": {
                  "line": 2,
                  "column": 15
                }
              },
              "extra": {
                "rawValue": 5,
                "raw": "5"
              },
              "value": 5
            }
          }
        ],
        "kind": "const"
      }
    ],
    "directives": []},
  "comments": [],
  "tokens": [
    {
      "type": {
        "label": "const",
        "keyword": "const",
        "beforeExpr": false,
        "startsExpr": false,
        "rightAssociative": false,
        "isLoop": false,
        "isAssign": false,
        "prefix": false,
        "postfix": false,
        "binop": null,
        "updateContext": null
      },
      "value": "const",
      "start": 3,
      "end": 8,
      "loc": {
        "start": {
          "line": 2,
          "column": 2
        },
        "end": {
          "line": 2,
          "column": 7
        }
      }
    },
    {
      "type": {
        "label": "name",
        "beforeExpr": false,
        "startsExpr": true,
        "rightAssociative": false,
        "isLoop": false,
        "isAssign": false,
        "prefix": false,
        "postfix": false,
        "binop": null
      },
      "value": "abc",
      "start": 9,
      "end": 12,
      "loc": {
        "start": {
          "line": 2,
          "column": 8
        },
        "end": {
          "line": 2,
          "column": 11
        }
      }
    },
    {
      "type": {
        "label": "=",
        "beforeExpr": true,
        "startsExpr": false,
        "rightAssociative": false,
        "isLoop": false,
        "isAssign": true,
        "prefix": false,
        "postfix": false,
        "binop": null,
        "updateContext": null
      },
      "value": "=",
      "start": 13,
      "end": 14,
      "loc": {
        "start": {
          "line": 2,
          "column": 12
        },
        "end": {
          "line": 2,
          "column": 13
        }
      }
    },
    {
      "type": {
        "label": "num",
        "beforeExpr": false,
        "startsExpr": true,
        "rightAssociative": false,
        "isLoop": false,
        "isAssign": false,
        "prefix": false,
        "postfix": false,
        "binop": null,
        "updateContext": null
      },
      "value": 5,
      "start": 15,
      "end": 16,
      "loc": {
        "start": {
          "line": 2,
          "column": 14
        },
        "end": {
          "line": 2,
          "column": 15
        }
      }
    },
    {
      "type": {
        "label": ";",
        "beforeExpr": true,
        "startsExpr": false,
        "rightAssociative": false,
        "isLoop": false,
        "isAssign": false,
        "prefix": false,
        "postfix": false,
        "binop": null,
        "updateContext": null
      },
      "start": 16,
      "end": 17,
      "loc": {
        "start": {
          "line": 2,
          "column": 15
        },
        "end": {
          "line": 2,
          "column": 16
        }
      }
    },
    {
      "type": {
        "label": "eof",
        "beforeExpr": false,
        "startsExpr": false,
        "rightAssociative": false,
        "isLoop": false,
        "isAssign": false,
        "prefix": false,
        "postfix": false,
        "binop": null,
        "updateContext": null
      },
      "start": 18,
      "end": 18,
      "loc": {
        "start": {
          "line": 3,
          "column": 0
        },
        "end": {
          "line": 3,
          "column": 0
        }
      }
    }
  ]
}

3、应用 babel 的生成器 generator 代码

import generate from "@babel/generator";
const newCode = generate(ast).code;

// newCode => const cba = 5;
babel 插件制作(babel-plugins)

在上述步骤中,第一步(解析)和第三步(生成)有 babel 解决。
当开发 babel-plugin 插件的时候,咱们只须要形容转化你的 AST 节点的 ”visitors” 就能够了。

// my-babel-plugin.js
module.exports = function() {
  return {
    visitor: {Identifier(path) {
        const name = path.node.name;
        console.log(name);
        path.node.name = name
          .split("")
          .reverse()
          .join("");
      }
    }
  };
};
// 在 babel.config.js 中注册插件,重启我的项目能力失效
// plugins: ["./src/plugins/mybabelplugin.js"]

学习 Babel 插件制作 -Babel-handbook
中文插件手册

主动代码重构工具,神器 JSCodeshift

例如说你想要替换掉所有的老掉牙的匿名函数, 把他们变成 Lambda 表达式(箭头函数)。

// transform
load().then(function(response)) {return response.data;}
// to
load().then(response => response.data)

上述操作代码编辑器可能没方法这么做,因为这并不是简略的查找替换操作。这时候 jscodeshift 就能够应用了。
如果你想创立主动把你的代码从旧的框架迁徙到新的框架,这就是一种很 nice 的形式。

jscodeshift

jscodeshift 是一个工具包,用于在多个 JavaScript 或 TypeScript 文件上运行 codemods。

react-codemod
This repository contains a collection of codemod scripts for use with JSCodeshift that help update React APIs.
此存储库蕴含一组 codemod 脚本,用于 jscodeshift,用于更新 React api。

Prettier
// transform
foo(reallyLongArg(), omgSoManyParameters(), IShouldRefactorThis()),isThereSeriouselyAnotherOne());
// to
foo {reallyLongArg(),
  omgSoManyParameters(), 
  IShouldRefactorThis(), 
  isThereSeriouselyAnotherOne()};
// Prettier 格式化咱们的代码。它调整长句,整顿空格,括号等。

《A prettier printer》

Finally

js2flowchart 在线转化预览地址
js2flowchart 仓库地址

它将 js 代码转化生成 svg 流程图
这是一个很好的例子,因为它向你展示了你,当你领有 AST 时,能够做任何你想要做的事。把 AST 转回成字符串代码并不是必要的,你能够通过它画一个流程图,或者其它你想要的货色。

js2flowchart 应用场景是什么呢?通过流程图,你能够解释你的代码,或者给你代码写文档;通过可视化的解释学习其他人的代码;通过简略的 js 语法,为每个处理过程简略的形容创立流程图。
你也能够在代码中应用它,或者通过 CLI,你只须要指向你想生成 SVG 的文件就行。而且,还有 VS Code 插件(链接在我的项目 readme 中)

首先,解析代码成 AST,而后,咱们遍历 AST 并且生成另一颗树,我称之为工作流树。它删除很多不重要的额 tokens,然而将要害块放在一起,如函数、循环、条件等。再之后,咱们遍历工作流树并且创立形态树。每个形态树的节点蕴含可视化类型、地位、在树中的连贯等信息。最初一步,咱们遍历所有的形态,生成对应的 SVG,合并所有的 SVG 到一个文件中.
后续会继续更新,学习中。。。

正文完
 0