Skip to main content

ESLint

Linter

静态代码分析

早在 1978 年,Stephen C. Johnson 在 Debug 自己的 C 语言项目时,突然想到为什么不做一个工具来提示自己写的代码哪里有问题呢?这个工具也被称为 Linter。

Linter 本意指的是衣服上多出来的小球、绒毛和纤维等,如果你刚把晾晒好的衣服收下来就会发现这些小玩意。以前如果想把这些多出来的“残渣”去掉,最简单的方法就是找一个单面胶粘一下再撕开,后来有的人发明了这个神器,一滚就能清除掉。

这就是 Linter 的由来,不过区别是神器重点在清除,而 Linter 重点在上报错误

Linter 想要提示错误,那首先就得阅读代码,这也是为什么 Linter 也被称为静态代码分析的工具。阅读完之后,再加上人为自定义好的一些规则,那么 Linter 就拥有了提示错误的能力了。

JSLint

在 2002 年,Douglas Crockford 就为 JavaScript 写了第一个 Linter 工具:JSLint

JSLint 的优点就是开箱即用,不需要配置太多的东西,但是规则太严格,完全不可扩展和自定义配置,连配置文件都没有。

JSHint

在 JSLint 的基础上,在 2010 年的时候 Anton Kovalyov 跟其它人就 fork 了一份 JSLint 然后改造成了 JSHint

这个工具与 JSLint 的思路正好相反,它的默认规则非常松散,自由度非常高。但是也同样带来了问题:需要非常了解这些规则才能配出一个好用的规则表。因为规则太不严格,过于自由,所以单纯靠默认的规则跟没有配置 Linter 一样。

JSCS

前面的 JSLint 和 JSHint 主要功能都是检查代码质量问题的,JSCS (JavaScript Coding Style) 则是一个代码风格检查器。

它有超过 90 条规则,你也能自己创建规则,不过这些规则主要是和代码风格、代码格式化有关,它不会报任何和 JS 代码质量相关的错误。

尽管 JSCS 在其活跃时期非常受欢迎,但它已于 2016 年被宣布停止维护,并建议用户迁移到 ESLint。ESLint 是一个更强大、更灵活的工具,它不仅可以检查代码风格,还可以发现潜在的错误和代码质量问题。另一个流行的代码格式化工具是 Prettier,它专注于自动格式化代码,而不提供任何代码质量检查。

虽然 JSCS 不再被维护,但它的一些功能和理念已经被 ESLint 和 Prettier 等现代工具所采纳。如果你正在寻找一个代码风格检查器和格式化器,建议使用 ESLint 和 Prettier 来替代 JSCS。这两个工具可以很好地协同工作,ESLint 负责检查代码质量,而 Prettier 负责自动格式化。

ESLint

2013 年,一个叫 JSChecker 的小项目被改名成如今非常熟悉的 ESLint。

ES6 上线之后,由于 JSHint 不支持 ES6 新语法直接宣布投降。而 ESLint 正好异军突起,用 Esprima (一个高性能的 ECMAScript Parser)支持所有 ES6 新语法,并对新语法做好了校验。

除了基础的 ES6 代码质量校验,ESLint 还支持代码风格的规则。开发者不仅可以自定义项目要用哪些规则,也能直接使用社区上制定的规则(比如 eslint-config-airbnb)。

目前,ESLint 已经成为现在 JavaScript 的一个标准的 Linter 了。

TSLint

2012 年微软公布了第一版的 TypeScript,随之而来的有个叫 TSLint 的 Linter。在那段时间里,TSLint 是 TypeScript 的标准 Linter 工具,ESLint 则为 JavaScript 标准 Linter。它们各有自身特色:ESLint 有 TSLint 所没有的一些语法特性支持,而 TSLint 可以对代码进行静态分析和类型检查。

但是,一个项目中有两个 Linter 并行检查属实有点让人不爽。TSLint 也经常和 ESLint 的人探讨应该用哪个作为主力 Linter。TS 的社区也有很多声音希望优先满足 JSer 的需求,毕竟 TS 是 JS 的超集,还是以 ESLint 为主。最终,在 2019 年 TSLint 宣告不再维护,ESLint从此夺取 Linter 天下。

ESLint

  • 规则 (Rules):规则是 ESLint 的核心,它们是独立的脚本,用于检查代码中的特定问题。ESLint 有许多内置规则,这些规则可以覆盖各种编码风格和潜在错误。规则是可配置的,每个规则可以被启用或禁用,并可以设置为警告或错误级别。
  • 配置 (Configuration):ESLint 允许通过配置文件自定义规则的启用和设置。配置文件可以是 .eslintrc.* 格式的文件或 package.json 文件里的 eslintConfig 字段。配置可以继承其他配置,这使得可以轻松地共享和组合规则集。共享配置通常是一个 npm 包,可以被多个项目使用。
  • 插件 (Plugins):插件是可扩展 ESLint 功能的方式,它们包含一组自定义规则和/或处理器(见下文)。这使得 ESLint 可以适应不同的编码风格和技术栈。插件可以通过 npm 安装并在配置文件中引用。
  • 处理器 (Processors):处理器是一个可选的插件特性,它可以对非 JavaScript 文件进行预处理,以便 ESLint 可以检查这些文件中嵌入的 JavaScript 代码。例如,HTML 文件中的 <script> 标签或 Markdown 文件中的代码块。
  • 命令行接口 (CLI):ESLint 提供了一个命令行接口,用于在终端中执行 linting 操作。CLI 允许用户指定一个或多个文件、目录或 glob 模式以进行检查。CLI 还支持许多选项,这些选项可以覆盖配置文件中的设置,如禁用特定规则、规定输出格式等。

基本使用

yarn add eslint -D

在项目根目录下面创建一个 eslint 的配置文件 .eslintrc

{
"env": {
"browser": true,
"es2021": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"rules": {
"indent": ["error", 2],
"quotes": ["error", "single"],
"semi": ["error", "always"]
}
}

以上配置基于 ESLint v9 版本之前,v9 版本配置方式有所改变。

  • env:定义预设的全局变量。

    • browser:适用于浏览器环境,预定义了诸如 windowdocument之类的浏览器才会有的全局变量。
    • es2021: 表示使用的是 ES 2021 的标准,肯定会预定义一些新版本的全局变量,Promise、Symbol` 这些全局变量是支持的。
  • extendseslint:recommended,这其实是 ESLint 团队推荐的一组核心规则,可以将其视为最佳实践。

  • parserOptions:和解析器相关的配置。

    • ecmaVersion:使用的 ECMAScript 的版本,12 也就是 2021。
    • sourceType:模块类型,设置 module,表示使用的 ESM 模块规则,支持 importexport 语法。
  • rules:定义代码风格,功能类似于 Prettier

    • indent:缩进,我们这里设置的是两个空格,如果不符合要求,会报 error 类型的错误。
    • quotes:引号的设置,这里我们设置的是单引号,如果不符合要求,会报 error 类型的错误。
    • semi:每一条语句添加分号,如果不符合要求,会报 error 类型的错误。

修改 package.json,添加如下的 scripts 脚本命令:

"scripts": {
"lint": "eslint --fix ."
},

检查规则

规则的重要性

在 ESLint 中,可以配置规则的重要性,总共分为三个级别:

  • off 或者 0:关闭这条规则。
  • warn 或者 1:这条规则的级别为警告级别。
  • error 或者 2:这条规则的级别为错误级别。

例如:

{
"rules": {
"no-undef": "error",
"semi": ["warn", "always"]
}
}

数组的第一项是规则重要性,第二项则是该条规则配置可选项:

  • always:这是默认值,代表语句结束需要插入分号。
  • never: 在没有 ASI 风险情况下,不需要插入分号。

ASI 英语全称叫做 automatic semicolon insertion,这个翻译成中文就是自动分号插入。所谓 ASI 风险,是指由于有这个机制,可能会导致意外的行为或者错误。

function example() {
return
{
message: 'Hello, world!'
}
}

如上代码中,本意是要返回一个对象,但是由于 ASI 机制,这里就会产生意外的行为,导致这个函数返回一个 undefined 而非预期的对象。

如果值为 always,那么可以配置一个额外的对象:

  • omitLastInOneLineBlock:配置为 true,表示禁止在单行代码块中的最后一个语句使用分号。
  • omitLastInOneLineClassBody:配置为 true,表示禁止在单行类里面的最后一个语句使用分号。

如果值为 never,那么可以配置一个额外的名为 beforeStatementContinuationChars 的对象:

  • beforeStatementContinuationChars: any(默认):如果下一行以[, (, /, +, 或 -]开始,则忽略语句末尾的分号(或缺少分号):
let a = 1
+1 // 正确:分号被忽略

let b = 2
;+2 // 正确:分号也可以
  • beforeStatementContinuationChars: always:如果下一行以[, (, /, +, 或 -]开始,则要求在语句末尾使用分号:
let a = 1
+1 // 错误:要求在语句末尾使用分号

let b = 2
;+2 // 正确:添加了分号
  • beforeStatementContinuationChars: never:即使下一行以[, (, /, +, 或 -]开始,只要没有引起 ASI 风险,也禁止在语句末尾使用分号:
let a = 1
+1 // 正确:没有 ASI 风险,不需要分号

let b = 2
;+2 // 错误:不允许在没有 ASI 风险的情况下使用分号
规则注释

ESLint支持行内注释规则。可在具体的代码文件中,以注释的方式来配置规则:

/* eslint eqeqeq: "off", curly: "error" */
/* eslint eqeqeq: 0, curly: 2 */
/* eslint quotes: ["error", "double"], curly: 2 */

规则注释的优先级会高于配置文件里面的规则。

一般在如下的场景中可能会涉及到使用注释规则:

  1. 针对特定的文件或者代码片段需要指定特殊规则,比如针对某一个代码片段去禁用 ESLint 检查:
/* eslint-disable */
console.log('Hello')
/* eslint-enable */

或者只禁用某一个规则:

/* eslint-disable semi */
console.log("Hello");
/* eslint-enable semi */
  1. 指定某个文件的特殊配置,有时需要针对某个文件指定和其他文件不同的 ESLint 配置,这种情况下也可以使用注释的形式,这样就不需要去修改主要的配置文件
/* eslint-env node, mocha */

在上面的注释汇总,声明 ESLint 的检查环境为 nodemocha,这就意味着在检查该文件的时候,ESLint 会预设一些 nodemocha 中的全局变量,比如 processdescribeit

  1. 临时禁用某条规则
// eslint-disable-next-line no-unused-vars
const tempVariable = 'Temporarily not used';

在上面的注释中,使用了 eslint-disable-next-line,代表只禁用下一行的代码检查,后面跟上了具体的规则,表示禁用下一行代码的某一条规则的检查,不影响之后的代码。

另外在配置文件中,有如下的配置选项:

  • noInlineConfig:禁止行内注释形式的规则。
  • reportUnusedDisableDirectives:用于是否报告有未使用的 eslint-disable 指令。

例如:

/* eslint-disable-next-line no-console */
console.log('Hello');

上面的代码是可以正常工作的,这条行内注释规则是有用的,但是如果把下面的 console 注释调用:

/* eslint-disable-next-line no-console */
// console.log('Hello');

那么上面的这一条行内注释规则就变成了一条无用的注释规则。

规则参照表

可在规则参照表看到 ESLint 里面的所有规则。

在规则参照表中,每一条规则后面有三个符号,对应的含义如下:

🔧💡
在配置文件中的 "extends": "eslint:recommended" 属性会启用此规则。此规则报告的一些问题可以通过 --fix 参数自动修复。此规则报告的一些问题可以通过编辑器建议手动修复。

配置文件

配置文件格式

在 ESLint 中,支持如下格式的配置文件:

  • JavaScript:使用 .eslintrc.js 并且导出一个包含你配置的对象。
  • JavaScript(ESM):在 v9 之前 ESLint 不支持 ESM 风格模块化,假设我们的源码使用的 ESM 模块化风格,并且我们在 pacakge.json 中明确配置了 type: module,此时需要将 ESLint 的配置文件命名为 .eslintrc.cjs(也就是说要使用 CommonJS 风格来命令 ESLint 的配置文件)。
  • YAML:使用 .eslintrc.yaml 或者 .eslintrc.yml
  • JSON:使用 .eslintrc.json 来配置 ESLint。
  • package.json:在 pacakge.json 中,可以创建一个名为 eslintConfig 的属性,然后对 ESLint 进行配置。

如果在项目的同一目录下存在多种格式的配置文件,那么这些配置文件之间是有一个优先级顺序的。顺序如下:

  1. .eslintrc.js
  2. .eslintrc.cjs
  3. .eslintrc.yaml
  4. .eslintrc.yml
  5. .eslintrc.json
  6. package.json

ESLint v7 之前,ESLint 支持使用 .eslintrc 文件来作为 ESLint 的配置文件,但是从 v7 开始,官方就已经明确废弃掉这种用法,从 v7 之后,就建议使用上述的格式来作为 ESLint 的配置文件。但是为了兼容性,之前的 .eslintrc 格式的配置文件依然能够使用,但是还是建议最好使用官方推荐的格式来进行配置。

使用配置文件

想让配置文件生效,有两种方式:

  • 在项目中创建上述的配置文件,ESLint 在做检查的时候会自动寻找配置文件并应用里面的配置。
  • 在 CLI 命令中通过 --config-c 选项来手动指定配置文件的位置。
eslint -c myconfig.json myfiletotest.js
配置文件的层叠
  • 在 ESLint 中支持配置文件的层叠,这是一种管理项目中多个配置文件的方式,允许在项目中根据不同的部分应用不同的规则。
  • 当存在多份 ESLint 的配置时,ESLint 会在当前目录下查找配置文件,然后会一层一层往上寻找,将找到的所有的配置文件进行一个规则合并。
  • 如果子目录下配置文件的规则和父目录下的配置文件规则发生重合,那么子目录下的配置文件规则会覆盖父目录下配置文件的同名规则(就近原则)。
  • 如果要应用当前目录的配置文件,不需要再往上找了,那么可以在当前的配置文件中添加一个 root:true,表示就应用当前目录下找到的配置文件,停止继续往上搜索。
扩展配置文件

扩展,也可叫继承,可以继承其他的配置规则。

{
"extends": "eslint:recommended",
}

在继承规则的基础上,也可以进行额外的配置:

{
"extends": "eslint:recommended",
"rules" : {
"no-console": "warn"
}
}

在进行原有配置规则的扩展的时候,有一个细节上面的问题:

{
"extends": "eslint:recommended", // "eqeqeq": ["error", "allow-null"]
"rules" : {
"eqeqeq": "warn"
}
}

比如修改了 eqeqeq 规则的重要性,从 error 修改成 warn,但原本的配置选项会保留,也就是说,eqeqeq 这条规则,最终会变为:

"eqeqeq": ["warn", "allow-null"]

但是如果更改的是配置选项,那么则是完全覆盖:

{
"extends": "eslint:recommended", // "quotes": ["error", "single", "avoid-escape"]
"rules" : {
"quotes": ["error", "double"]
}
}

extends 对应的值还可以是一个数组:

{
"extends": [
"./node_modules/coding-standard/eslintDefaults.js",
"./node_modules/coding-standard/.eslintrc-es6",
"./node_modules/coding-standard/.eslintrc-jsx"
],
"rules": {
"quotes": "warn"
}
}
局部重写

有些需要对配置进行更加精确的控制,例如都是在同一个目录下,不同的文件使用不同的配置,这种情况下就可以使用局部重写overrides

{
"rules": {
"quotes": ["error", "double"]
},
"overrides": [
{
"files": ["**/*.js"],
"rules": {
"quotes": ["error", "single"]
}
},
{
"files": ["lib/*.js"],
"rules": {
"quotes": ["error", "double"]
}
}
]
}

当然,overrides也支持嵌套:

{
"rules": {
"quotes": ["error", "double"]
},
"overrides": [
{
"files": ["lib/*.js"],
"rules": {
"quotes": ["error", "single"]
},
"overrides": [
{
"files": ["util.js"],
"rules": {
"quotes": ["error", "double"]
},
}
]
}
]
}

配置文件(v9.x)

从 v9.0.0 开始,官方推荐的配置文件格式为 eslint.config.js,并且支持 ESM 模块化风格,可以通过 export default 来导出配置内容:

export default [
{
files: ["*.js"],
rules: {
"no-var": "error"
}
},
{
files: ["*.ts"],
rules: {
"@typescript-eslint/no-var": "error"
}
}
]
配置选项
  • files:一个含有 glob 模式的数组,指示应用配置对象的文件。如果未指定,配置对象应用于所有由任何其他配置对象匹配的文件。
  • ignores:一个含有 glob 模式的数组,指示配置对象不应用于的文件。如果未指定,配置对象应用于所有由 files 匹配的文件。
  • languageOptions:一个包含与 JavaScript 的 lint 设置有关的设置对象。
    • ecmaVersion: 支持的 ECMAScript 版本。可能是任何年份(例如:2022)或版本(例如:5)。设置为 latest 表示最近支持的版本,(默认为latest)。
    • sourceType:JavaScript 源码的类型。可能的值为 script 表示传统脚本文件,module 表示 ECMAScript 模块(ESM),以及 commonjs 表示 CommonJS 文件。默认情况下 module 对应 .js.mjs 文件,commonjs 对应 .cjs 文件。
    • globals:一个对象,指定在 linting 过程中应添加到全局作用域的额外对象。
    • parser:解析器,包含 parse() 方法或 parseForESLint() 方法的对象,默认值为 espree
    • parserOptions:一个对象,指定直接传递给 parser 上的 parse()parseForESLint() 方法的额外选项,可用的选项依赖于解析器。
  • linterOptions:包含与 linting 相关配置的对象。
    • noInlineConfig:布尔值,指示是否允许内联配置。
    • reportUnusedDisableDirectives:布尔值,控制是否报告未使用的 eslint-disable 指令。
  • processor:包含 preprocess()postprocess() 方法的对象,或者指示插件内部处理器名称的字符串。
  • plugins:包含插件名称到插件对象的名称-值映射的对象。当指定了 files 时,这些插件仅对匹配的文件可用。
  • rules:包含具体配置规则的对象。当指定了 filesignores 时,这些规则配置仅对匹配的文件可用。
  • settings:一个包含键值对信息的对象,这些信息应对所有规则都可用。
globals

该配置项位于 languageOptions 配置项下面,用于配置一些全局的设定:

export default [
{
files: ["**/*.js"],
languageOptions: {
globals: {
var1: "writable",
var2: "readonly"
}
}
}
];

在上面的配置中,指定了 var1 这个变量是可写的,但是 var2 这个变量是只读的:

var1 = 100;
// error
var2 = 200;
parser

parser,即解析器,其作用是负责将源码解析为抽象语法树。

parser 可以自定义, 但需要是一个对象,里面包含 parse 或者 parseForESLint 方法。

import babelParser from "@babel/eslint-parser";

export default [
{
files: ["**/*.js", "**/*.mjs"],
languageOptions: {
parser: babelParser
}
}
];
processor

processor,即处理器,主要用于处理 ESLint 默认不能够处理的文件类型。

例如有一个 markdown 类型的文件,里面有一些 JS 代码,默认这些 JS 代码是不能够被 ESLint 处理的,通过添加额外的处理器,让 ESLint 能够对这些格式的文件进行 lint 检查:

import markdown from "eslint-plugin-markdown";

export default [
{
files: ["**/*.md"],
plugins: {
markdown
},
processor: "markdown/markdown",
settings: {
sharedData: "Hello"
}
}
];

CLI

ESLint CLI 命令行工具的基本格式为:

eslint [options] [file|dir|glob]*

[file|dir|glob]* 主要是用来指定 ESLint 应该检查哪些文件:

  • file:用于指定一个具体的文件名
eslint app.js
  • dir:指定一个目录
eslint src/ 
  • globglob 是一种模式,类似于正则表达式,用来匹配文件的路径。glob 模式下面可以使用一些特殊的字符 * ? [] 来匹配文件名。
eslint src/**/*.js
基本配置
  • --no-eslintrc:让 ESLint 忽略所有的配置文件,当你使用这个选项的时候,ESLint 只会使用内置的规则集来对匹配上的文件进行检查。
  • -c--config:指定配置文件的路径。
  • --env:该配置项允许指定一些环境,当指定了具体的环境之后,就会预设一些该环境下才会有的全局变量。
  • --ext:指定 ESLint 要检查的文件的扩展名,默认情况下,ESLint 只检查 .js 文件。
  • --global:该配置项允许定义全局变量。
  • --parser:该选项允许指定一个自定义的 JavaScript 解析器。
  • --plugin:该配置项是用来指定要使用插件。
  • --rule:该配置项就是指定检查的规则。
自动修复
  • --fix:表示自动修复,但不是所有的问题 ESLint 都可以修复。
  • --fix-type:允许指定修复问题的类型,对应的值有problemsuggestionlayoutdirective
    • problem:修复代码中的潜在错误,这种类型的问题通常是代码错误,如果不修复,可能会导致程序运行错误。
    • suggestion:对代码应用改进性的修复,这种类型的问题通常不会导致程序错误,但修复它们可以改进代码,使代码更易读、更易维护,或更符合最佳实践。这些问题可能涉及到代码的优化、重构或者一些编程习惯的改进。例如,未使用的变量、复杂的表达式可以简化、不必要的代码重复等,都属于 suggestion 类型的问题。
    • layout:应用不改变程序结构(抽象语法树,AST)的修复,主要涉及到代码的格式和样式。这些问题不会影响代码的功能或语义,但是修复它们可以使代码更具可读性和一致性。例如,不正确的缩进、缺失的分号、超过设定长度的行等,都属于 layout 类型的问题。
    • directive:对内联指令(如eslint-disable)应用修复。
忽略文件
  • --ignore-path:指定忽略文件的路径。
  • --no-ignore:忽略所有的忽略指令,相当于让 .eslintignore 失效。
  • --ignore-pattern:忽略文件匹配模式。
输出
  • -o--output-file:将 ESLint 的检查报告输出到一个文件里面。
  • -f--format:正常情况下,ESLint 的检查报告在控制台进行输出,那么这个指令可以配置输出的格式,可选值为:stylishcompacttap
缓存
  • --cache:该配置项表示在进行ESLint检查的时候,生成一个缓存文件 .eslintcache,缓存文件默认在当前目录下面,有了缓存文件之后,下一次 ESLint 在做检查的时候速度会更快。
  • --cache-location:指定缓存文件的位置。
  • --cache-strategy:指定生成缓存时的缓存策略。
    • metadata:这是默认值,使用文件的元数据(修改时间和文件大小)来判断文件是否发生了变化。
    • content:基于文件的内容来判断文件是否发生变化。

APIs

一般在如下的场景中,会涉及到使用 API 来编程:

  • 要将工具集成到代码编辑器或者 IDE 里面。
  • 自定义 linter 工具。
  • 一些在线的学习平台。

常用的 API 有:

  • new ESLint(options):创建一个 ESLint 实例。
  • lintFiles(patterns):格式化文件并返回结果。
  • outputFixes(results):该方法将通过 ESLint 的自动修复功能修改的代码写入相应的文件中。
const { ESLint } = require("eslint");

(async function main() {
// 1. Create an instance with the `fix` option.
const eslint = new ESLint({ fix: true });

// 2. Lint files. This doesn't modify target files.
const results = await eslint.lintFiles(["lib/**/*.js"]);

// 3. Modify the files with the fixed code.
await ESLint.outputFixes(results);

// 4. Format the results.
const formatter = await eslint.loadFormatter("stylish");
const resultText = formatter.format(results);

// 5. Output it.
console.log(resultText);
})().catch((error) => {
process.exitCode = 1;
console.error(error);
});

插件

ESLint 支持插件,使用插件是扩展 ESLint 功能的一种方式,可以通过插件的方式来自定义新的规则或者处理器,也可以自己写一个有独特功能的插件发布到 npm 上面。

使用插件能够带来的好处包括:

  • 自定义规则:用于验证代码是否满足某种预期,如果不满足该预期,应该如何处理。
  • 自定义配置:用于定义一组规则和设置,这些规则和设置可以被重复使用,不需要在每个项目中重新定义。
  • 自定义环境:用于定义一组全局变量,这些变量在特定环境下(例如浏览器、Node.js、Jest 等)是预定义的。
  • 自定义处理器:用于从其他类型的文件中提取 JavaScript 代码,或在进行语法检查之前预处理代码。
名称规范

ESLint 中的插件,每一个插件是一个 npm 模块,命名的格式为 eslint-plugin-<plugin-name>,如:

  • eslint-plugin-react
  • eslint-plugin-jest

插件还可以使用 scope 包的形式,如

  • jquery/eslint-plugin-jquery
  • jquery/eslint-plugin
使用插件
yarn add eslint-plugin-react -D
{
"plugins": [
"react"
],
"rules": {
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error",
}
}
自定义插件

ESLint插件主要是用来扩展ESLint本身没有的功能,这里包括扩展规则、扩展配置、扩展解析器。

大部分的ESLint插件都是以扩展规则为主,所以这些插件里面会包含大量的自定义规则。

ESLint 插件的基本格式为:

module.exports = {
// 元数据信息
meta: {

},
// 规则具体的实现
create: function(){
return {}
}
}
meta

meta 提供这条规则相应的元数据信息。

  • type: 描述规则的类型。可以是以下的一个:

    • problem:表示这个规则识别的是可能导致错误的代码问题。
    • suggestion:表示这个规则识别的是可能的改进,以使代码更易于阅读和/或更具可维护性。
    • layout:表示这个规则识别的是布局问题,即风格指南中的问题,而不会影响代码的功能。
  • docs:提供关于规则的文档信息,可以包含以下字段:

    • description:规则的简短描述,通常用于生成文档。
    • recommended:一个布尔值,表示这个规则是否在配置为 recommended 的情况下被启用。
    • url:指向规则文档的 URL。
  • fixable:说明是否可以自动修复由此规则识别的问题,以及如何修复。如果规则可以自动修复问题,此字段应为 codewhitespace,否则应为 null 或省略。

  • deprecated:一个布尔值,表示这个规则是否已被弃用,默认为 false

create

一个函数,该函数会返回一个对象,对象里面又是一个一个的方法,例如有如下的方法:

  • Program: 这个方法会在遍历抽象语法树开始时被调用。
  • FunctionDeclaration:这个方法会在遍历到一个函数声明时被调用。
  • VariableDeclaration:这个方法会在遍历到一个变量声明时被调用。
  • ExpressionStatement:这个方法会在遍历到一个表达式语句时被调用。
  • CallExpression:这个方法会在遍历到一个函数调用时被调用。
  • ReturnStatement:这个方法会在遍历到一个 return 语句时被调用。

这些方法里面接收一个参数,该参数是当前所遍历到的 AST 节点对象,通过这个节点对象就可以拿到当前节点一些具体的信息以及该节点对应的子节点:

create: function(){
return {
CallExpression(node){
// ...
}
}
}
context

create 方法还会自动传入一个 context 参数:

create: function(context){
return {
CallExpression(node){
// ...
}
}
}

该参数提供了一些方法:

  • context.report(descriptor):这个方法用于报告一个问题。descriptor 是一个对象,包含了问题的信息,如问题的位置、消息等。
  • context.getSourceCode():这个方法返回一个 SourceCode 对象,你可以使用它来访问源代码的文本和 AST。
  • context.getAncestors():这个方法返回一个包含当前节点的所有祖先节点的数组,数组中的第一个元素是最近的祖先。
  • context.getScope():这个方法返回一个代表当前作用域的 Scope 对象。
示例
module.exports = {
meta: {
type: "problem",
docs: {
description: "disallow the use of alert",
category: "Best Practices",
},
fixable: null,
},
create(context) {
return {
CallExpression(node) {
if (node.callee.name === "alert") {
context.report({
node,
message: "alert is not allowed",
});
}
},
};
},
}

集成Prettier

Pretter 和 ESLint,两者都有管理代码风格的功能,因此两者往往就会在代码风格的管理上面存在一些冲突。

例如:ESLint 配置了单引号规则,而Prettier 配置了要使用双引号。假设代码中有双引号,ESLint 会提示错误,然后将引号手动改为单引号,但是一格式化,因为会应用 Prettier 的格式化规则,又会被格式化为双引号,也就是说只要一格式化就会报错。

为了解决这个问题,有两个思路:

  • 手动的将其中一个工具的配置文件进行修改,改成和另外一个工具的配置是相同的。这种方式肯定是没有问题的,但是缺点在于这种方式是手动的,如果涉及到大量的规则,那么手动操作比较繁琐。
  • 使用插件来帮助解决这个问题。
    • eslint-config-prettier 会关闭所有与 Prettier 冲突的 ESLint 规则。
    • eslint-plugin-prettier 将 Prettier 作为 ESLint 规则来运行,这样在运行 ESLint 时也会运行 Prettier。
yarn add eslint-config-prettier eslint-plugin-prettier -D
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: ['eslint:recommended', 'plugin:prettier/recommended'],
parserOptions: {
ecmaVersion: 12,
sourceType: 'module',
},
rules: {
'prettier/prettier': [
'warn',
{
semi: false,
},
],
},
}