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-reacteslint-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,实际上社区在这方面也做了很多工作。

比如

自定义的 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的分享。

更多参考文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342