ESLint 是如何使用和实现的?

前言

今天这篇文章,主要聊聊什么是ESLint,为什么要用它?它的实现原理是什么?工作中如何使用的ESLint,以及如何自定义ESLint规则。

本文整理自以下文章:

  • 掘金:eslint工作原理探讨
  • 手摸手教你写eslint插件
  • 慕课网:《大前端》第七周「团队协作」

什么是ESLint & 为什么使用它

为什么要使用ESLint?

JavaScript是一个动态的弱类型语言,在代码编写的过程中,经常会出错,而因为其没有编译程序,为了寻找代码错误的地方,需要在执行的过程中不断的调试。

ESLint的作用就是让你在开发过程中发现自己的代码问题以及不规范的地方,提前发现问题所在,并且可以规范团队的代码风格保持一致。

什么是ESLint?

ESlint是一个开源的JS代码检查工具,它的目标是提供一个插件化的JavaScript代码检测工具。

ESLint 的核心就是其中包含的各种规则,这些规则大多为众多开发者经验的结晶:

  • 有的可以帮我们避免错误;
  • 有的可以帮我们写出最佳实践的代码;
  • 有的可以帮我们规范变量的使用方式;
  • 有的可以帮我们规范代码格式;
  • 用的可以帮我们更合适的使用新的语法;

总的来说,ESLint是一套每一个人都应该了解的并且遵循的JS代码规范。它可以让我们的代码风格一致、更加健壮、减少错误并用上社区的最佳实践。

原理

在许多方面,它和 JSLint、JSHint 相似,除了少数的例外:

  • ESLint 使用 Espree 解析 JavaScript。
  • ESLint 使用 AST 去分析代码中的模式
  • ESLint 是完全插件化的。每一个规则都是一个插件并且你可以在运行时添加更多的规则。

AST是Abstract Syntax Tree(抽象语法树)的缩写,如下图。

AST

也就是说,eslint使用Espress把js语法转换成AST。然后通过 AST selectors找到静态代码中的内容,再根据rule的规则去判断这一段js是否符合eslint的规范。

安装和初始化ESlint

新建一个空的文件夹,执行以下的命令:

1、 npm init -y

2、 npm install eslint -D

3、 npx eslint --init

image-20200112155041929

完成以上的步骤,我们将会得到以下的文件夹:

image-20200112155112994

rule是如何工作的?

ESLint 的核心就是规则(rule),每条规则都是独立的,且都可以被设置为禁止off,警告warn,或者报错error

我们选择"no-debugger": "error" 来看看 rule 是如何工作的。源码如下:

module.exports = {
    meta: {
        type: "problem",

        docs: {
            description: "disallow the use of `debugger`",
            category: "Possible Errors",
            recommended: true,
            url: "https://eslint.org/docs/rules/no-debugger"
        },

        fixable: null,
        schema: [],

        messages: {
            unexpected: "Unexpected 'debugger' statement."
        }
    },

    create(context) {

        return {
            DebuggerStatement(node) {
                context.report({
                    node,
                    messageId: "unexpected"
                });
            }
        };
    }
};

一条 rule 就是一个 node 模块,其主要由 metacreate 两部分组成,其中

  • meta 代表了这条规则的元数据,如其类别,文档,可接收的参数的 schema 等等。
  • create:如果说 meta 表达了我们想做什么,那么 create 则表达了这条 rule 具体会怎么分析代码;

Create 返回的是一个对象,其中最常见的键的名字可以是上面我们提到的选择器,在该选择器中,我们可以获取对应选中的内容,随后我们可以针对选中的内容作一定的判断,看是否满足我们的规则,如果不满足,可用 context.report()抛出问题,ESLint 会利用我们的配置对抛出的内容做不同的展示。

上面的代码实际上表明在匹配到 debugger 语句时,会抛出 “Unexpected ‘debugger’ statement.” 。

AST

关于更多的Rules规则,可以查看「eslint工作原理探讨」

plugin的概念

plugin 有两重概念:

  1. 一是 ESLint 配置项中的字段,如 plugins: ['react'];
  2. 二是社区封装的 ESLint plugin,在 npm 上搜索eslint-plugin-就能发现很多,比较出名的有 eslint-plugin-react ,eslint-plugin-import

plugin 其实可以看作是第三方规则的集合,ESLint 本身规则只会去支持标准的 ECMAScript语法,但是如果我们想在 React 中也使用 ESLint 则需要自己去定义一些规则,这就有了 eslint-plugin-react 。

我们在日常的工作中,也可以自定义符合自己团队风格的plugin提供给其他的队友使用。

工作中是如何使用ESLint的?

通常我们再日程的工作中,不会使用npx eslint执行代码检查,而是在IDE中自动提醒Eslint的错误。

在Vscode中,需要安装ESLint插件。

image-20200112161825873

如果使用该插件,需要在项目中或者全局使用npm install eslint安装eslint,否则,ESLint插件会报如下错误。

image-20200112162052045

当然,如果你用的webstorm,就不用这么麻烦安装插件啦。

VsCode中可以使用自动保存autoSave, ctrl + P,使用保存时自动格式化ESLint

image-20200112163411079

这里有一份课程中提供的settings配置,可以供小伙伴们添加到settings.json中使用:

...
  "eslint.validate": [
    "javascriptreact",
    "typescriptreact",
    {
      "language": "html",
      "autoFix": true
    },
    {
      "language": "vue",
      "autoFix": true
    },
    {
      "language": "javascript",
      "autoFix": true
    },
    {
      "language": "typescript",
      "autoFix": true
    }
  ],
...
  "editor.codeActionsOnSave": {
    "source.fixAll.tslint": true,
    "source.fixAll.eslint": true
  },
  // prettier
  "prettier.trailingComma": "es5",
  // vetur
  "vetur.format.defaultFormatter.js": "none",
  "vetur.validation.template": false,
  // default use eslint, NO NEED re-config for twice
  // "vetur.format.defaultFormatterOptions": {
  //   "prettier": {
  //     "semi": false,
  //     "singleQuote": true,
  //     "eslintIntegration": true,
  //     "trailingComma": "all"
  //   }
  // },
  // Files exclude from tree

自定义的ESLint规则

只需要满足 ESLint 的规定,ESLint 支持自定义 parser,实际上社区在这方面也做了很多工作。

比如

  • babel-eslint,A wrapper for Babel’s parser used for ESLint
  • typescript-eslint-parser,让我们可以 lint TS 代码

自定义的 parser 使用方法如下:

{
    "parser": "./path/to/awesome-custom-parser.js"
}

通过自定义 parser ,ESLint 的使用场景又被大大拓展。

下面,我们结合一个小例子,看看自定义的规则是如何实现的:

插件目标:禁止项目中setTimeout的第二个参数是数字。

实现步骤如下

1、安装NPM包

ESLint官方为了方便开发者开发插件,提供了使用Yeoman模板(generator-eslint)。

对于Yeoman我们只需知道它是一个脚手架工具,用于生成包含指定框架结构的工程化目录结构。

npm install -g yo generator-eslint

创建一个文件夹:

mkdir eslint-plugin-demo
cd eslint-plugin-demo

命令行初始化ESLint插件的项目结构:

yo eslint:plugin

下面进入命令行交互流程,流程结束后生成ESLint插件项目框架和文件。

? What is your name? OBKoro1
? What is the plugin ID? korolint   // 这个插件的ID是什么
? Type a short description of this plugin: XX公司的定制ESLint rule // 输入这个插件的描述
? Does this plugin contain custom ESLint rules? Yes // 这个插件包含自定义ESLint规则吗?
? Does this plugin contain one or more processors? No // 这个插件包含一个或多个处理器吗
// 处理器用于处理js以外的文件 比如.vue文件
   create package.json
   create lib/index.js
   create README.md

现在可以看到在文件夹内生成了一些文件夹和文件,但我们还需要创建规则具体细节的文件。

2、创建规则

上一个命令行生成的是ESLint插件的项目模板,这个命令行是生成ESLint插件具体规则的文件。

yo eslint:rule // 生成 eslint rule的模板文件

创建规则命令行交互:

? What is your name? OBKoro1
? Where will this rule be published? (Use arrow keys) // 这个规则将在哪里发布?
❯ ESLint Core  // 官方核心规则 (目前有200多个规则)
  ESLint Plugin  // 选择ESLint插件
? What is the rule ID? settimeout-no-number  // 规则的ID
? Type a short description of this rule: setTimeout 第二个参数禁止是数字  // 输入该规则的描述
? Type a short example of the code that will fail:  占位  // 输入一个失败例子的代码
   create docs/rules/settimeout-no-number.md
   create lib/rules/settimeout-no-number.js
   create tests/lib/rules/settimeout-no-number.js

加了具体规则文件的项目结构

.
├── README.md
├── docs // 使用文档
│   └── rules // 所有规则的文档
│       └── settimeout-no-number.md // 具体规则文档
├── lib // eslint 规则开发
│   ├── index.js 引入+导出rules文件夹的规则
│   └── rules // 此目录下可以构建多个规则
│       └── settimeout-no-number.js // 规则细节
├── package.json
└── tests // 单元测试
    └── lib
        └── rules
            └── settimeout-no-number.js // 测试该规则的文件

3、安装项目依赖

npm install

rule完整文件

lib/rules/settimeout-no-number.js:

module.exports = {
    meta: {
        docs: {
            description: "setTimeout 第二个参数禁止是数字",
        },
        fixable: null,  // 修复函数
    },
    // rule 核心
    create: function (context) {
        // 公共变量和函数应该在此定义
        return {
            // 返回事件钩子
            'CallExpression': (node) => {
                if (node.callee.name !== 'setTimeout') return // 不是定时器即过滤
                const timeNode = node.arguments && node.arguments[1] // 获取第二个参数
                if (!timeNode) return // 没有第二个参数
                // 检测报错第二个参数是数字 报错
                if (timeNode.type === 'Literal' && typeof timeNode.value === 'number') {
                    context.report({
                        node,
                        message: 'setTimeout第二个参数禁止是数字'
                    })
                }
            }
        };
    }
};

context.report():这个方法是用来通知ESLint这段代码是警告或错误的,用法如上。在这里查看contextcontext.report()的文档。

规则写完了,原理就是依据AST解析的结果,做针对性的检测,过滤出我们要选中的代码,然后对代码的值进行逻辑判断

4、发布插件

eslint插件都是以npm包的形式来引用的,所以需要把插件发布一下:

  1. 注册:如果你还未注册npm账号的话,需要去注册一下。
  2. 登录npm: npm login
  3. 发布npm包: npm publish即可,ESLint已经把package.json弄好了。

5、集成到项目:

安装npm包:npm i eslint-plugin-korolint -D

常规的方法: 引入插件一条条写入规则

// .eslintrc.js
module.exports = {
  plugins: [ 'korolint' ],
  rules: { 
    "korolint/settimeout-no-number": "error"
 }
}

extends继承插件配置:

当规则比较多的时候,用户一条条去写,未免也太麻烦了,所以ESLint可以继承插件的配置:

修改一下lib/rules/index.js文件:

'use strict';
var requireIndex = require('requireindex');
const output = {
  rules: requireIndex(__dirname + '/rules'), // 导出所有规则
  configs: {
    // 导出自定义规则 在项目中直接引用
    koroRule: {
      plugins: ['korolint'], // 引入插件
      rules: {
        // 开启规则
        'korolint/settimeout-no-number': 'error'
      }
    }
  }
};
module.exports = output;

使用方法:

使用extends来继承插件的配置,extends不止这种继承方式,即使你传入一个npm包,一个文件的相对路径地址,eslint也能继承其中的配置。

// .eslintrc.js
module.exports = {
  extends: [ 'plugin:korolint/koroRule' ] // 继承插件导出的配置
}

总结

ESLint 可谓是现代前端开发过程中必备的工具了。ESLint 做为必备工具之一,能减少团队协作的问题,个人代码风格问题,减少Bug的错误,是非常值得我们深入了解学习的。

感谢掘金作者@OBKoro1,@zhangwang的分享。

更多参考文章

  • AST in Modern JavaScript
  • 如何写自定义的ESLint规则 (Customized ESLint Rule)
  • 前端工具-Eslint篇

你可能感兴趣的:(ESLint 是如何使用和实现的?)