一文带你读懂 ESLint(基于最新 9.x 版本)

这篇文章详细介绍了 ESLint 相关的一些知识,基于最新的版本 V9,主要分成三大部分:

  • ESLint 的基本介绍
  • ESLint 配置文件解析
  • ESLint 运行原理与 AST
  • 如何编写 ESLint 插件

什么是 ESLint

ESLint 是一个可配置的 JavaScript linter。它可以帮助你发现并修复 JavaScript 代码中的问题。

ESLint 中的核心概念

规则(Rules)

规则是 ESLint 的核心构建块。 规则用来验证你的代码是否满足特定期望,以及如果不满足该期望该怎么办。

如果不满足期望,规则可以选择为他们发现的违规行为提供修复和建议。

规则修复

规则修复可以安全地纠正违规行为,而无需更改应用逻辑。

应用方式:通过 --fix 命令行选项或编辑器扩展自动应用。

规则建议

除了规则修复之外,规则还可以选择提供建议。建议与修复有两个不同的地方:

  • 建议可能会更改应用逻辑,所以不能自动应用。
  • 建议无法通过 ESLint CLI 应用,只能通过编辑器集成使用。

在 ESLint 官方的 Rules 中,每条规则有三个标记:分别是 ✅(官方推荐规则)、🔧(可以通过 --fix 修复的规则)、💡(可以提供规则建议)。

配置文件

ESLint 配置文件是在项目中放置 ESLint 配置的位置。可以包含内置规则、你希望它们如何执行、具有自定义规则的插件、可共享配置、你希望规则应用到哪些文件等等。是 ESLint 的核心配置。

可共享的配置

可共享配置是通过 npm 共享的 ESLint 配置,本质是是一个对象。

插件

ESLint 插件是一个 npm 模块,可以包含一组 ESLint 规则、配置、处理器和环境。插件通常包含自定义规则。插件可用于强制执行样式指南并支持 JavaScript 扩展(如 TypeScript)、库(如 React)和框架(Angular)。

解析器

ESLint 解析器将代码转换为 ESLint 可以计算的抽象语法树。默认情况下,ESLint 使用内置的 Espree 解析器,它与标准 JavaScript 运行时和版本兼容。

自定义解析器让 ESLint 解析非标准 JavaScript 语法。通常,自定义解析器作为可共享配置或插件的一部分包含在内,因此你不必直接使用它们。

例如,@typescript-eslint/parsertypescript-eslint 项目中包含的自定义解析器,可让 ESLint 解析 TypeScript 代码。

自定义处理器(Custom Processors)

ESLint 处理器从其他类型的文件中提取 JavaScript 代码,然后让 ESLint 检测 JavaScript 代码。或者,你可以在使用 ESLint 解析 JavaScript 代码之前使用处理器来操作 JavaScript 代码。

例如,eslint-plugin-markdown 包含一个自定义处理器,可让你在 Markdown 代码块内检查 JavaScript 代码。

集成(Integrations)

ESLint 成为如此有用的工具的原因之一是它周围的集成生态系统。例如,许多代码编辑器都有 ESLint 扩展,可以在你工作时在文件中显示代码的 ESLint 结果,这样你就不需要使用 ESLint CLI 来查看 linting 结果。

配置文件

ESLint 配置文件 的命名为 eslint.config.(c|m)js,放在项目的根目录下,导出一个配置对象的数组。比如:

// eslint.config.js
export default [
  {
    rules: {
      semi: 'error',
      'prefer-const': 'error',
    },
  },
];

如果项目的 package.json 文件中没有指定 "type":"module",那么 eslint.config.js 必须是 CommonJS 格式。比如:

// eslint.config.js
module.exports = [
  {
    rules: {
      semi: 'error',
      'prefer-const': 'error',
    },
  },
];

配置对象

每个配置对象都包含 ESLint 需要在一组文件上执行的所有信息。由以下属性组成:

  • name:配置对象的名称。在显示错误消息时,用来帮助识别正在使用哪个配置对象。
  • files:表明配置文件对象作用于哪些文件,是一个通配符模式数组。如果未指定,那么这个配置对象适用于与任何其他配置对象匹配的所有文件。
  • ignores:表明配置对象不作用于哪些文件,也是一个通配符模式数组。
  • languageOptions:配置 JavaScript 应该如何 linting。
  • linterOptions:包含与代码 linting 过程相关的设置的对象。
  • processor:包含 preprocess()postprocess() 方法的对象或指示插件内处理器名称的字符串(即 "pluginName/processorName")。
  • plugins:包含插件名称到插件对象的名称-值映射的对象。指定 files 时,这些插件仅对匹配的文件可用。
  • rules:包含配置规则的对象。当指定 files 或 ignores 时,这些规则配置只对匹配的文件可用。
  • settings:一个包含名称-值对信息的对象,所有规则都应使用这些信息。

默认情况下,ESLint 匹配 /*.js、/.cjs 和 **/.mjs。

配置规则

你可以通过添加一个 rules 属性来在配置对象中配置任意数量的规则,该属性包含一个带有你的规则配置的对象。此对象中的名称是规则的名称,值是每个规则的配置。

// eslint.config.js
export default [
  {
    rules: {
      semi: 'error',
    },
  },
];

ESLint 有两个预定义的 JavaScript 配置:

  • js.configs.recommended - 启用 ESLint 建议每个人使用的规则,以避免潜在的错误
  • js.configs.all - 启用 ESLint 附带的所有规则

使用可共享的配置包

可共享配置是导出配置对象或数组的 npm 包。该包应作为项目中的依赖安装,然后从 eslint.config.js 文件内部引用。例如,要使用名为 eslint-config-example 的可共享配置,你的配置文件将如下所示:

// eslint.config.js
import exampleConfig from 'eslint-config-example';
 
export default [
  exampleConfig,
 
  // your modifications
  {
    rules: {
      'no-unused-vars': 'warn',
    },
  },
];

某些可共享配置将导出一个数组,在这种情况下,你需要使用扩展运算符将这些项目插入到配置数组中。例如:

// eslint.config.js
import exampleConfigs from 'eslint-config-example';
 
export default [
  ...exampleConfigs,
 
  // your modifications
  {
    rules: {
      'no-unused-vars': 'warn',
    },
  },
];

配置规则

规则是 ESLint 的核心构建块。规则验证你的代码是否满足特定期望,以及如果不满足该期望该怎么办。规则还可以包含特定于该规则的其他配置选项。

规则有三个级别:

  • "off"0 - 关闭规则
  • "warn"1 - 打开规则作为警告(不影响退出代码)
  • "error"2 - 打开规则作为错误(触发时退出代码为 1)

来自插件的规则

要配置在插件中定义的规则,需要在规则 ID 前添加插件命名空间和 /。

例如名为 eslint-plugin-example 的插件中包含了一条名为 rule1 的规则,那么:

// eslint.config.js
import example from 'eslint-plugin-example';
 
export default [
  {
    plugins: {
      example,
    },
    rules: {
      'example/rule1': 'warn',
    },
  },
];

配置插件

ESLint 支持使用第三方插件。插件只是符合 ESLint 识别的特定接口的对象。

要在配置文件内配置插件,需要使用 plugins 键,该键包含一个对象,该对象的属性表示插件命名空间,且值等于插件对象。

// eslint.config.js
import example from 'eslint-plugin-example';
 
export default [
  {
    plugins: {
      example,
    },
    rules: {
      'example/rule1': 'warn',
    },
  },
];

为插件创建命名空间时,惯例是使用不带 eslint-plugin- 前缀的 npm 包名称。在前面的示例中,为 eslint-plugin-example 分配了命名空间 example。

配置局部插件

插件不需要发布到 npm 即可与 ESLint 一起使用。你还可以直接从文件加载插件,如下例所示:

// eslint.config.js
import local from './my-local-plugin.js';
 
export default [
  {
    plugins: {
      local,
    },
    rules: {
      'local/rule1': 'warn',
    },
  },
];

plugins 中的 local 其实是 local: local,只不过因为同名省略了而已,你也可以给其他名字,比如 my-local: local

配置虚拟插件

假设我们只有一个名为 my-rule.js 的文件中包含一条规则,你希望在配置中启用该规则。你可以定义一个虚拟插件来执行此操作,如下例所示:

// eslint.config.js
import myRule from './rules/my-rule.js';
 
export default [
  {
    plugins: {
      local: {
        rules: {
          'my-rule': myRule,
        },
      },
    },
    rules: {
      'local/my-rule': 'warn',
    },
  },
];

指定处理器

插件可以提供处理器。处理器可以从其他类型的文件中提取 JavaScript 代码,然后让 ESLint 对 JavaScript 代码进行 lint。或者,处理器可以在预处理期间转换 JavaScript 代码。

要在配置文件中指定处理器,请使用 processor 键并以 namespace/processor-name 格式分配处理器名称。例如,以下使用 eslint-plugin-markdown 中的处理器处理 *.md 文件。

// eslint.config.js
import markdown from 'eslint-plugin-markdown';
 
export default [
  {
    files: ['**/*.md'],
    plugins: {
      markdown,
    },
    processor: 'markdown/markdown',
  },
];

创建插件

前面说过,ESLint 是高度插件化和可配置的,插件允许你将自己的 ESLint 自定义规则和自定义处理器添加到项目中。

并且插件通常包含一组或多组可共享配置,这种配置包含一些预定义的规则、插件和其他设置,可以方便地在多个项目中复用。这种机制使得团队或社区可以共享一致的代码风格和最佳实践。

插件是一个 JavaScript 对象,它向 ESLint 公开某些属性:

  • meta - 有关插件的信息。
  • configs - 包含命名配置的对象。
  • rules - 包含自定义规则定义的对象。
  • processors - 包含命名处理器的对象。

meta

为了更轻松地调试和更有效地缓存插件,建议在插件根目录下的 meta 对象中提供名称和版本,如下所示:

const plugin = {
  // preferred location of name and version
  meta: {
    name: 'eslint-plugin-example',
    version: '1.2.3',
  },
  rules: {
    // add rules here
  },
};
 
// for ESM
export default plugin;
 
// OR for CommonJS
module.exports = plugin;

meta.name 属性应与插件的 npm 包名称匹配,meta.version 属性应与插件的 npm 包版本匹配。完成此操作的最简单方法是从你的 package.json 读取此信息。