相信做前端开发都碰到过Eslint,经常被它搞的痛苦不堪,其实它并没有那么复杂,今天咱们好好聊聊。

首先,我们从基础出发来看看 EsLint 相关配置代表的含义。

配置文件

在 EsLint 中所有的检测规则都是可配置的,自然所有的配置规则都需要有一个配置的存储文件。

在 EsLint 中支持两种方式来进行规则配置:

  1. Configuration Comments 在你的 JavaScript 代码中直接使用 JS 注释的形式将配置嵌入你的原代码。
  2. Configuration Files 使用单独 Eslint 配置文件来整合你的相关配置,支持 JavaScript、JSON 或者 YAML 文件三种格式(.eslintrc.*),当然也可以直接将配置写入项目的 package.json 中。在调用 EsLint 命令时,Eslint 会自动寻找对应的配置文件。

同时在 EsLint 中配置文件也支持向上查找的方式(层叠配置),比如我们的项目定义目录如下所示:

- demo
- packages
- projectA
- index.js
- .eslintrc.js
- projectB
- index.js
.eslintrc.js
package.json

层叠配置使用离要检测的文件最近的 ​​.eslintrc​​文件作为最高优先级,然后才是父目录里的配置文件。

这也就意味这,在 projectA 项目中不仅仅 projectA/.eslintrc.js 中的配置规则会生效,同时它也会继承上一层目录中的 .eslintrc 的配置(.eslintc.js)。

默认情况下,ESLint 会在所有父级目录里寻找配置文件,一直找到根目录。少部分情况下如果我们想要你所有项目都遵循一个特定的约定时,这将会很有用。(比如在 monorepo 项目中,我们通常会存在一份根级别的 EsLint 配置文件约束)。

同时,当寻找到项目根目录的 ​​eslintrc.js​​ 时,我们希望它停止向上查找。那么此时 Eslint 的配置文件也支持设置 ​​root: true​​ 的选项来停止这种层叠配置的查找机制。

比如,如果在 ​​demo/packages/projectA/.eslintrc.js​​ 中设置了 ​​root: true​​,那么此时 ​​projectA/index.js​​ 仅会有 ​​projectA/eslintrc.js​​ 的配置生效。

​demo/.eslintrc.js​​​ 仅会影响 ​​projectB/index.js​​​ 并不会影响 ​​packageA/index.js​​。


ParserOptions

EsLint 支持任何类型的 JavaScript 语言选项(比如 ES6、模块类型等等),默认不进行任何配置时 EsLint 默认检测规则为 ES5 代码,

我们可以通过配置中的 ParserOptions 选项来进行语言选项设置,比如:

// .eslint.js
module.exports = {};
复制代码
// index.js
const a = '1'; // error: Parsing error: The keyword 'const' is reserved
console.log(a);
复制代码

文章之后的例子都会以 ​​.eslint.js​​ 的配置文件方式来演示。

上述的 Eslint 配置文件中,我们没有设置任何 ParserOptions。默认会使用 ES5 规范来检查我们的代码,自然当我们在项目中使用 ​​const​​ 时,EsLint 会提示错误 ​​const​​ 作为保留关键字。

当然,我们可以使用 ParserOptions 来修改 EsLint 对于语法检测的规则:

// .eslint.js
module.exports = {
parserOptions: {
ecmaVersion: 6, // 指定 EsLint 支持 ECMAScript 6 的语法检测
},
};
复制代码
// index.js
const a = '1'; // correct
console.log(a);
复制代码

当然 ParserOptions 中支持的全部配置选项:

  • ​ecmaVersion​​: 如上边示例的,表示应用代码中支持的 ECMAScript 版本。默认为 5 ,支持3、5、6、7、8、9 或 10 来指定你想要使用的 ECMAScript 版本。当然也可以使用 latest 表示最新的 ECMA 版本。
  • ​sourceType​​: 表示应用代码中支持的模块规范,默认为 script。支持 scriptmodule (ESM) 两种配置。
  • ​ecmaFeatures​​: 它是一个对象,表示代码中可以使用的额外语言特性。
  • globalReturn 允许全局下使用 return 。
  • impliedStrict 启用全局严格模式。(ES 5以上有效)
  • jsx 允许代码中使用 jsx 语法。
module.exports = {
parserOptions: {
ecmaFeatures: {
// 允许 js 代码中使用 jsx
jsx: true,
// 允许顶层 return
globalReturn: true,
},
ecmaVersion: 6,
},
};
复制代码

总之,ParserOptions​ 选项表示 EsLint 对于不同的 Parser(解析器)配置的语言检查规则。

Parser

上边我们提到所谓的 ParserOptions 代表 Eslint 中支持我们使用哪些语法。

在 EsLint 配置中有一个和它名称非常相似的配置 ​​Parser​​ ,它表示 Eslint 在解析我们的代码时使用到的解析器。

关于 EsLint 是如何帮助我们进行代码检查的,简单来说本质上它仍是将我们的代码根据规定的解析器转化成为 AST 抽象语法树。

之后根据我们传入配置中的各种规则对于源代码生成的 AST 语法树进行代码检查以及代码修复。

ESLint 默认情况下使用Espree作为其解析器,当然我们也可以传入一些自定义的解析器。

通常,我们在项目中使用 typescript 代码:

// .eslint.js
module.exports = {
parser: 'espree', // 使用默认 espree 解析器
rules: {
'no-unused-vars': ['error'], // 定义规则禁止声明未使用的变量
},
};


// index.ts 定义 b 但未使用,并没有报错
const b: string = '1'
复制代码

上述我们使用了 typescript 语法定义了变量 b 但是并没有使用变量 b ,此时 EsLint 规则检查并没有生效。

这是因为我里上述配置文件的 ​​parser​​ 默认使用的是 ​​espree​​,它并不支持 ​​typescript​​ 语法的检查,要额外支持 ​​ts​​ 语法的检查需要使用额外的 ts 解析器。

module.exports = {
parser: '@typescript-eslint/parser', // 修改解析器为 @typescript-eslint/parser
rules: {
'no-unused-vars': ['error'],
},
};


// index.ts
const b: string = '1' // error: 'b' is assigned a value but never used
复制代码

我们提到过本质上 ESLint 内部是基于 AST 进行检查,对于 AST 想深入了解的同学可以查看我​​这篇文章​​。

所以 tsc 在处理 ts 语法转译后的 ast 规则是 eslint 默认的 espree 是完全不一致的,所以我们需要通过 @typescript-eslint/parser 解析器来解析我们的代码。

当我们使用特定的解析器时,比如使用 @typescript-eslint/parser 最终会将 ts 文件转移后的 ast 结构转化成为 espree 支持的 ast 结构进行静态检查。

当然,我们最开始提到的 parserOptions 也是针对于不同的解析器(Parser)的选项配置,具体各个 ParserOptions 选项内容可以在不同的 parser 文档中查找对应规则。

所以,Parser 选项简单来说就是表示我们以何种解析器来转译我们的代码。本质上,所有的解析器最终都需要讲代码转化为 espree​ 格式从而交给 Eslint 来检测。