备案 控制台
学习
实践
活动
专区
工具
TVP
写文章
专栏首页 神光的编程秘籍 TSLint 和 ESLint 是怎么融合在一起的
3 0

海报分享

TSLint 和 ESLint 是怎么融合在一起的

Eslint 可以静态检查 javascript 代码一些逻辑上的错误,还有一些代码格式的错误。原理是把代码 parse 成 AST,然后基于 AST 来检查一些问题。

Tslint 可以静态检查 typescript 代码的一些逻辑上的错误,一些代码格式的错误。原理也是基于 AST 的。

既然都是基于 AST,而且做的事情差不多,那为啥不合并到一起呢?

后来,还真合并了,tslint 合并到了 eslint 中,把 tslint 标记为了废弃。

但是两者毕竟是不同的 AST,而且 tslint 里还有一些类型检查相关的逻辑,这是 eslint 不支持的。那它们是怎么融合的呢?

本文我们就来探索一下。

不同的 AST

eslint 有自己的 espree 的 parser 和相应的 AST。

typescript 也有自己的 parser 和相应的 AST。

babel 也有自己的 parser 和相应的 AST。

这些 AST 之间的关系是什么?

最早的 parser 是 esprima,它参考了 Mozilla 浏览器的 SpiderMonkey 引擎的 AST 的标准,然后做了扩充。后来形成了 estree 标准。

后面的很多 parser 都是对这个 estree 标准的实现和扩充。esprima、espree、babel parser(babylon)、acorn 等都是。

当然,也有不是这个标准的,自己实现了一套的 typescript、terser 等的 parser。

他们之间的关系如图所示:

esprima 和 acorn 都是 estree 标准的实现,而 acorn 支持插件机制来扩充语法,所以 espree 和 babel parser 是直接基于 acorn 来实现的。

terser、typescript 等则是另外的一套。

所以,对于 JS 的 AST,我们可以简单的划分为两类:estree 系列、非 estree 系列。

可以借助 astexplorer.net 这个工具来可视化的查看不同 parser 产生的 AST。

espree 就是 eslint 自己实现的 parser,但是它毕竟主要是来做代码的逻辑和格式的静态检查的,在新语法的实现进度上比不上 babel parser。所以 eslint 支持了 parser 的切换,也就是可以在配置不同的 parser 来解析代码。

配置文件里面可以配置不同的 parser,并通过 parserOptions 来配置解析选项。

下面分别讲下 eslint、typescript、babel、vue 等的 parser 怎么在 eslint 中使用:

  • 默认 parser 是 espree。parse 出来的是 estree 系列的 AST,一系列 rule 都是基于这些 AST 实现的。
{
    "parserOptions": {
        "ecmaVersion": 6,
        "sourceType": "module",
        "ecmaFeatures": {
            "jsx": true
  • 可以通过 @babel/eslint-parser 来切换到 babel 的 AST,它也是 estree 系列,但是支持的语法更多,在 babel7 之后,支持 typescript、jsx、flow 等语法的解析。
{
  parser: "@babel/eslint-parser",
  parserOptions: {
    sourceType: "module",
    plugins: []
  • 可以通过 @typescript-eslint/parser 来切换到 typescript 的 parser,它可以 parse 类型的信息。
{
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
      "project": "./tsconfig.json"
  • 可以通过 vue-eslint-parser 来解析 vue 的单文件组件,因为 vue 组件代码同样通过 eslint 来检查规范和逻辑错误,所以实现了对应的 parser。
{
    "parser": "vue-eslint-parser",
    "parserOptions": {
        "sourceType": "module",
        "ecmaVersion": 2018,
        "ecmaFeatures": {
            "globalReturn": false,
            "impliedStrict": false,
            "jsx": false

而且单文件组件中的 js 部分还可以分别指定不同的 parser。

{
    "parser": "vue-eslint-parser",
    "parserOptions": {
        "parser": {
             // 指定默认 js 的 parser
            "js": "espree",
             // 指定 `<script lang="ts">` 时的 parser
            "ts": "@typescript-eslint/parser",
             // 指定模版中的一些脚本的 parser
            "<template>": "espree",

是不是感觉有点晕,typescript、babel、vue 等的 parser 都有相应的用于 eslint 的版本。其实细想一下也很正常,因为 lint 就是基于 AST 的,如果不能 parse,那么怎么 lint,所以需要支持 parser 的扩展,支持切换。

但是 parser 之后的 AST 可能不同,那么 lint 的 rule 的实现也不同。为了复用 rule,大家还是都往 estree 标准上靠比较好。

tslint 和 eslint 的融合也是这样的思路,下面我们来详细看一下。

tslint 融合进 eslint

tslint 是独立的工具,基于 typescript 的 parser 来解析代码,并且实现了基于该 AST 的一系列 rule。

如果要融合进 eslint,那么怎么融合呢?

主要考虑的是 AST 怎么融合,因为 rule 就是基于 AST 的。

比如 const a = 1; 这段代码,

estree 系列的 AST 是这样的:

而 typescript 的 AST 是这样的:

AST 都不同,那么基于 AST 的 rule 肯定也要有不同的实现。

怎么融合呢?

转换!把一种 AST 转成另一种 AST 不就行了。

没错,@typescript-eslint/parser 中确实也是这么做的,它把 ts 的 AST 转换成 estree 的 AST(当然对于类型的部分,estree 中没有,就保留了该 AST,但是加上了 TS 前缀)。这样,就能够用 eslint 的 rule 来检查 typescript 代码中的问题了。

我们来简单看一下 @typescript-eslint/parser 的源码:

我简化了一下,是这样的:

function parseAndGenerateServices(code) { 
    // 用 ts parser 来 parse
    let {ast, program} = createIsolatedProgram(code);
    // 转换成 estree 的 ast
    ast = convertAst(ast);
    return {
      services: {
        program,
        esTreeNodeToTSNodeMap,
        tsNodeToESTreeNodeMap