[译] 以和为贵!让 ESlint、Prettier 和 EditorConfig 互不冲突

2021-06-30 11:02:56 浏览数 (1)

原文链接:https://blog.theodo.com/2019/08/empower-your-dev-environment-with-eslint-prettier-and-editorconfig-with-no-conflicts/

ESLint, Prettier and EditorConfig

来由

如果你已经在搭配使用 Prettier 和 ESLint, 可能已经遇到过 代码格式化冲突 的问题了吧。

ESLint - Prettier conflict

我曾在一次把 TypeScript 项目从 TSLint 迁移到 ESLint 的工作中遇到过这些问题。我们打算用 ESLint 和 Prettier 接管语法检查,在添加了一条 ESLint 规则强制规定 2 个空格缩进以解决上图中的问题后,其他问题又像按下葫芦浮起瓢一样纷纷出现了,很明显没法子通过一条条增加规则解决每一个冲突。

网上关于这个话题的确有很多说法,但大部分都是针对某个特定项目给出一个配置,而非深入阐释为什么 ESLint、Prettier 或 EditorConfig 会八字不合。本文的目的就是对这些潜在的问题解惑,以免相关的 bug 再干扰调试。

策略

我们先来明确一下 各司其职 的原则:

  • EditorConfig 将负责统一各种编辑器的配置,所有和编辑器相关的配置 (行尾、缩进样式、缩进距离...) 都交给它
  • Prettier 作为 代码格式化 工具
  • 其余的,也就是 代码质量 方面的语法检查,用 ESLint 来做

ESLint 和 Prettier

假设我们有这么一个 main.js,并同时配置了 ESLint 和 Prettier。

main.js

代码语言:javascript复制
function printUser(firstName, lastName, number, street, code, city, country) {
    console.log(`${firstName} ${lastName} lives at ${number}, ${street}, ${code} in ${city}, ${country}`);
}
printUser('John', 'Doe', 48, '998 Primrose Lane', 53718, 'Madison', 'United States of America');

eslintrc.json

代码语言:javascript复制
{
  "extends": ["eslint:recommended"],
  "env": {
    "es6": true,
    "node": true
  }
}

让 Prettier 做它自己的工作

为了能 配合使用 ESLint 和 Prettier,应该 关闭所有可能和 Prettier 冲突的 ESLint 规则 (也就是 代码格式化 那些)。好消息是,eslint-config-prettier 包已经解决了这个问题。

代码语言:javascript复制
npm install eslint-config-prettier --save-dev

先在 .eslintrc.json 中,将 prettier 加到 extends 数组的最后,并移除任何 代码格式化 相关的规则:

代码语言:javascript复制
{
  "extends": ["eslint:recommended", "prettier"],
  "env": {
    "es6": true,
    "node": true
  }
}

如此一来, Prettier 的配置将覆盖 extends 数组中先前任何 代码格式化 相关的 ESLint 配置,二者就能并行不悖地工作了。

将 Prettier 整合进 ESLint

分别运行两条命令以检查语法和格式化代码可不太方便,我们可以通过安装 eslint-plugin-prettier 包来解决这个问题。

代码语言:javascript复制
npm install eslint-plugin-prettier --save-dev

.eslintrc.jsonplugins 数组中加入 prettier 插件,并建立一条指定为 error 的 Prettier 新规则,这样任何格式化错误就也被认为是 ESLint 错误了。

代码语言:javascript复制
{
  "extends": ["eslint:recommended", "prettier"],
  "env": {
    "es6": true,
    "node": true
  },
  "rules": {
    "prettier/prettier": "error"
  },
  "plugins": [
    "prettier"
  ]
}

main.js 运行 ESLint 试一下:

代码语言:javascript复制
npx eslint main.js

ESLint no fix

可以看到,那些字符过多或缩进错误的行,都被标以了 prettier/prettier 并作为 ESLint 错误被打印出来。

修复之:

代码语言:javascript复制
npx eslint --fix main.js

文件将按 Prettier 的方式被正确格式化。

Prettier 和 ESLint 配合中的常见问题

添加 ESLint 插件

以上的配置应付小项目绰绰有余;但当你使用 Vue、React 或其他框架时,还是 很容易让 ESLint 和 Prettier 的配置打架。我遇到的一个常见问题是当开发者增加一个 ESLint 插件后,如何在不同时改动 Prettier 的情况下,也能让后者正常工作。

以 TypeScript 为例

出于某些考虑,我们决定在项目中使用 TypeScript。鉴于 TSLint 将被废弃,自然要用 ESLint 取而代之。这里就使用 TypeScript 作为一个例子,来展示 对于有一个适用的 ESLint 插件的框架,该如何处理

这是进行了 TypeScript 改造后的 main.ts 文件:

代码语言:javascript复制
function printUser(firstName: string, lastName: string, number: number, street: string, code: number, city: string, country: string): void {
 console.log(`${firstName} ${lastName} lives at ${number}, ${street}, ${code} in ${city}, ${country}`);
}
printUser('John', 'Doe', 48, '998 Primrose Lane', 53718, 'Madison', 'United States of America');

要想让 ESLint 兼容 TypeScript 或是其他什么特殊语法的框架,需要增加一个 parser 以使 ESLint 可以读取新的代码和相关的一系列规则。

代码语言:javascript复制
npm install typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev

然后修改 .eslintrc.json,分别向 parser 选项和 extends 数组中包含 TypeScript 插件:

代码语言:javascript复制
{
  "parser": "@typescript-eslint/parser",
  "extends": ["plugin:@typescript-eslint/recommended", "eslint:recommended", "prettier"],
  "env": {
    "es6": true,
    "node": true
  },
  "rules": {
    "prettier/prettier": "error"
  },
  "plugins": [
    "prettier"
  ]
}

带上 --fix 选项,再来试试 ESLint:

eslint-fix-option

这时如果我们多次执行这条命令,每次都将得到同样的报错 -- 尽管控制台里面说错误是可以被修复的。此时文件被修改为了这样:

代码语言:javascript复制
function printUser(
  firstName: string,
  lastName: string,
  number: number,
  street: string,
  code: number,
  city: string,
  country: string
): void {
  console.log(
    `${firstName} ${lastName} lives at ${number}, ${street}, ${code} in ${city}, ${country}`
  );
}

printUser(
  'John',
  'Doe',
  48,
  '998 Primrose Lane',
  53718,
  'Madison',
  'United States of America'
);

所以 Prettier 的确格式化了我们的代码,但 ESLint 期望的 4 个空格没有被满足。错误看起来和 @typescript-eslint 规则有关。

如果你像我一样在使用 VSCode 并开启了保存时自动执行 ESLint 修复,可能会看到这种情况:

Conflict between typescript eslint and prettier

通过禁用新增插件的所有 ESLint 格式化规则解决冲突

很多人的一个常见错误就是头疼医头、脚疼医脚。比如对于这个 @typescript-eslint 插件里面的缩进规则,他们会往 rules 数组中添加一条这样的规则:

代码语言:javascript复制
"@typescript-eslint/indent": ["error", 2]

这当然解决了具体冲突,但有两个问题出现了:

  • 无法保证 typescript-eslint 插件中的其他规则今后不和 Prettier 冲突
  • ESLint 和 Prettier 又开始同时负责代码格式化了,这违背了我们的分工策略

按照之前的整合方法,通过在 extends 数组中增加 prettier/@typescript-eslint 来禁用相关插件中所有关乎 代码格式化 的规则。

代码语言:javascript复制
{
  "parser": "@typescript-eslint/parser",
  "extends": ["plugin:@typescript-eslint/recommended", "eslint:recommended", "prettier", "prettier/@typescript-eslint"],
  "env": {
    "es6": true,
    "node": true
  },
  "rules": {
    "prettier/prettier": "error"
  },
  "plugins": [
    "prettier"
  ]
}

谨记 .eslintrc.jsonextends 数组的顺序非常重要。基本上每次向数组添加新配置时,都将覆盖之前的配置。因此 prettierprettier/@typescript-eslint 待在数组末尾至关重要。

这样配置后就没问题了,ESLint 将不会再越俎代庖。

总结一下这种常见问题:

  • 每当你添加一个 ESLint 插件,针对 它引入的 代码格式化 规则 添加一个 prettier 配置 (如果有的话) 。在我们的例子中,使用了 prettier/@typescript-eslint,但其实我们也可以用 prettier/reactprettier/vue。检查 eslint-config-prettier 文档(https://github.com/prettier/eslint-config-prettier) 获取可用的 ESLint 插件。
  • 不要尝试自己覆盖 eslintrc 中的格式化规则
  • 每当你见到这种 Prettier 和 ESLint 对同一种格式化的冲突,就以为着你有一条无用的 ESLint 格式化规则,也意味着你没有遵守以上两条

添加一条自定义规则

项目团队中的 TypeScript 开发者对 2 个空格缩进浑身不舒服,非要改成 4 个。一个常见的错误是把我们的 ESLint-Prettier 整合策略抛之脑后,并在 .eslintrc.json 中直接添加规则,就像这样:

代码语言:javascript复制
{
  "parser": "@typescript-eslint/parser",
  "extends": ["plugin:@typescript-eslint/recommended", "eslint:recommended", "prettier", "prettier/@typescript-eslint"],
  "env": {
    "es6": true,
    "node": true
  },
  "rules": {
    "prettier/prettier": "error",
    "@typescript-eslint/indent": ["error", 4]
  },
  "plugins": [
    "prettier"
  ]
}

熟悉的错误毫无意外地又出现了,和我们之前解决 Prettier 协同 ESLint 时遇到的一摸一样:

Conflict with custom rule

rules 数组中自定义的规则会覆盖 prettier/@typescript-eslint 配置。

按照正确的策略,代码格式化 规则应该在 .prettierrc 中配置。对于例子来说,应该是:

.prettierrc

代码语言:javascript复制
{
  "tabWidth": 4
}

这样 Prettier 将以 4 个空格格式化代码,而 .eslintrc.json 应该不关心任何缩进规则。

总结一下这种常见问题:

  • 当你想添加一条规则时,分清其属于 代码质量 还是 代码格式化 类别。简单地做法是,检查这条规则在 Prettier 中是不是可行的
  • 不要在 .eslintrc.json 中添加格式化规则,这样做将不可避免的和 Prettier 冲突

Prettier 和 EditorConfig

设置编辑器配置

EditorConfig 使不同编辑器可以保持同样的配置。因此,我们得以无需在每次编写新代码时,再依靠 Prettier 来按照团队约定格式化一遍(译注:出现保存时格式化突然改变的情况)。当然这需要在你的 IDE 上安装了必要的 EditorConfig 插件或扩展。

本文以 VSCode 为例,但 EditorConfig 支持很多编辑器。

在项目中增加自定义的编辑器配置:

.editorconfig

代码语言:javascript复制
[*]
end_of_line = lf
charset = utf-8
indent_style = space

如果安装了 the EditorConfig VSCode extension,编辑器将自动获知该如何格式化你的文件。你也能在编辑器右下角看到相应的信息:

vscode

避免 EditorConfig 和 Prettier 的重复配置

但是,这意味着 Prettier 和 EditorConfig 共享了相同的配置选项,而我们不希望同步维护两份重复的配置 (比如关于行尾的配置)。Prettier 的最新版本通过处理 .editorconfig 文件来决定使用的配置。

限于以下几种选项:

代码语言:javascript复制
end_of_line
indent_style
indent_size/tab_width
max_line_length

这些选项会被映射为 Prettier 的相关选项 (如果没有在 .prettierrc 再被定义的话):

代码语言:javascript复制
"endOfLine"
"useTabs"
"tabWidth"
"printWidth"

就像 ESLint 配合 Prettier 时的策略那样,在你改变 EditorConfig 或 Prettier 配置时,根据这些限定选项来决定在哪边改。上面例子中的选项就应该只在 .editorconfig 中存在。

据此再检查我们上面做过的所有配置,还能发现一个配置错误。我们在 Prettier 配置中指定了缩进距离。因为这也是一个浏览器相关的配置,所以应该将其移至 .editorconfig

.editorConfig

代码语言:javascript复制
tab_width 4

现在我们删除了已经空白的 .prettierrc.json,并对未格式化的代码运行 ESLint:

代码语言:javascript复制
function printUser(
    firstName: string,
    lastName: string,
    number: number,
    street: string,
    code: number,
    city: string,
    country: string
): void {
    console.log(
        `${firstName} ${lastName} lives at ${number}, ${street}, ${code} in ${city}, ${country}`
    );
}

printUser(
    "John",
    "Doe",
    48,
    "998 Primrose Lane",
    53718,
    "Madison",
    "United States of America"
);

代码缩进变成了 4 个空格。现在,无论你在何时用编辑器打开一个新文件,都会应用这个配置,Prettier 同样也会遵循。

--End--

查看更多前端好文 请搜索 云前端 或 fewelife 关注公众号 转载请注明出处

0 人点赞