ESLint 之与 Prettier 配合使用

19,196 阅读7分钟

前言

看本文前建议先动手写一个 ESLint 插件,理解 ESLint 的运行原理。

Lint 和 Prettier

区别

ESLint(包括其他一些 lint 工具)的主要功能包含代码格式和代码质量的校验,而 Prettier 只是代码格式的校验,不会对代码质量进行校验。代码格式问题通常指的是:单行代码长度、tab 长度、空格、逗号表达式等问题。代码质量问题指的是:未使用变量、三等号、全局变量声明等问题。

配合

为什么要两者配合使用?

第一,ESLint 推出 --fix 参数前,ESLint 并没有自动格式化代码的功能,而 Prettier 可以自动格式化代码。

第二,虽然 ESLint 也可以校验代码格式,但 Prettier 更擅长。

ESLint 和 Prettier 相互合作的时候有一些问题,对于他们交集的部分规则,ESLint 和 Prettier 格式化后的代码不一致。导致的问题是:当你用 Prettier 格式化代码后再用 ESLint 校验,会出现一些因为格式化导致的 warning or error。

例如:针对字符串使用单引号还是双引号的问题,ESLint 和 Prettier 分别对应如下规则:

ESLint: quotes - enforce the consistent use of either backticks, double, or single quotes, 默认是 double.

// .eslintrc.js
{
  rules: {
    quotes: ["error", "double"] // 可简写为 quotes: "error", 表示字符串需使用双引号,否则报错
  }
}

Prettier: prettier.singleQuote, 默认是 false.

// .prettierrc
{
  "singleQuote": true // 字符串使用单引号
}

假如这样一行代码 const a = "irene",用 Prettier 格式化后变成了 const a = 'irene',再用 ESLint 去检测,就会报 Strings must use doublequote;当你用 eslint --fix 自动修复这个问题后又无法通过 Prettier 的校验,结果陷入死循环。

解决方案

我们一般的解决思路是禁掉 ESLint 中与 Prettier 冲突的规则,然后使用 Prettier 做格式化, ESLint 做代码校验。

  • 假设配置文件如下,我们在 .eslintrc.js 禁掉 quotes 这条规则,然后在 .prettierrc 中配置使用单引号。

    // .eslintrc.js
    {
      rules: {
        quotes: "off",
        "no-unused-vars": "error", // 开启没有用过的变量检测
      }
    }
    // .prettierrc
    {
      "singleQuote": true
    }
    
  • 创建一个测试文件 test.js。

    // test.js
    const a = "irene"
    
  • 运行 prettier 格式化代码,双引号变成了单引号。

    prettier --write test.js
    
    // test.js
    const a = 'irene' // 双引号变成了单引号
    
  • 运行 eslint 校验代码。

    eslint test.js
    // error  'a' is assigned a value but never used  no-unused-vars
    

上面这种解决思路需要运行两个命令(prettier & eslint)才能完成格式化和校验的工作,我们希望以更简单的方式完成这件事,最好一个命令就能搞定。

于是社区提出了这样一种解决方案:

目的:使用 eslint --fix 就能完成格式化和校验的工作,格式化使用 Prettier,代码校验使用 ESLint。

具体步骤:

  • 首先禁掉 ESLint/插件 中与 Prettier 冲突的规则,创建一个包 eslint-config-prettier,里面定义了被禁掉的 ESLint/插件 规则。

  • 创建一个插件 eslint-plugin-prettier,定义一条规则 prettier/prettier,调用 Prettier,配合 ESLint 实现运行 eslint --fix 按 Prettier 规则自动格式化代码。

  • 在 .eslintrc.js 中如下配置。

    {
      extends: [
        ..., // 其他
        "prettier", // eslint-config-prettier
      ]
      plugins: ["prettier"], // eslint-plugin-prettier
      rules: {
        "prettier/prettier": "error" // 开启规则
      }
    }
    

下面我们介绍下 eslint-config-prettier 和 eslint-plugin-prettier。

eslint-config-prettier

github.com/prettier/es…

我们看下 eslint-config-prettier/index.js,这些都是 ESLint 本身已经定义好的规则,eslint-config-prettier/index.js 没有创造新的规则,它只是关闭了 ESLint 中一些不必要的规则以及可能与 Prettier 冲突的规则 。请注意,这个配置只能关闭规则,所以只有和其他配置一起使用才有意义。

从下面这个文件可以看出:被关闭的规则一些是 0,一些是 'off'。0 表示这些规则在某些选项下是不会和 Prettier 规则冲突的。

module.exports = {
  rules: Object.assign(
    {
      // The following rules can be used in some cases. See the README for more
      // information. (These are marked with `0` instead of `"off"` so that a
      // script can distinguish them.)
      "arrow-body-style": 0,
      curly: 0,
      "lines-around-comment": 0,
      "max-len": 0,
      "no-confusing-arrow": 0,
      "no-mixed-operators": 0,
      "no-tabs": 0,
      "no-unexpected-multiline": 0,
      "prefer-arrow-callback": 0,
      quotes: 0,
      // The rest are rules that you never need to enable when using Prettier.
      "array-bracket-newline": "off",
      "array-bracket-spacing": "off",
      "array-element-newline": "off",
      "arrow-parens": "off",
      "arrow-spacing": "off",
      "block-spacing": "off",
      ...
    },
    includeDeprecated && {
      // Deprecated since version 4.0.0.
      // https://github.com/eslint/eslint/pull/8286
      "indent-legacy": "off",
      // Deprecated since version 3.3.0.
      // https://eslint.org/docs/rules/no-spaced-func
      "no-spaced-func": "off",
    }
  ),
};

使用

  • 首先安装 eslint-config-prettier。

    npm install --save-dev eslint-config-prettier
    
  • 将 eslint-config-prettier 添加到 .eslintrc.js 文件的 "extends" 数组中。确保把它放在最后,这样它就有机会覆盖其他配置。

    {
      extends: [
        ..., // 其他
        "prettier", // eslint-config-prettier 可简写成 prettier
      ]
    }
    
  • eslint-config-prettier 下还有 @typescript-eslint.js, babel.js, react.js, prettier.js, vue.js 等文件,其内容是禁掉与它搭配使用的插件中创建的与 Prettier 冲突的规则。

    例如:当我们想校验 ts 文件时,需要引入 @typescript-eslint/eslint-plugin,这个插件中存在一些与 Prettier 冲突的规则,prettier/@typescript-eslint.js 就是禁掉这些规则。

    {
      extends: [
        "plugin:@typescript-eslint/recommended", // 引入相关插件
        "prettier", // 禁用 ESLint 中与 Prettier 冲突的规则
        "prettier/@typescript-eslint" // 禁用插件中与 Prettier 冲突的规则
      ]
    }
    

    以下插件都对应了 eslint-config-prettier 中的一个配置文件

eslint-plugin-prettier

github.com/prettier/es…

eslint-plugin-prettier 以 ESLint 规则的方式运行 Prettier,通过 Prettier 找出格式化前后的差异,并以 ESLint 问题的方式报告差异,同时针对不同类型的差异提供不同的 ESLint fixer。

注意:eslint-plugin-prettier 不会自动安装 Prettier,你需要自己安装。

Options

prettier/prettier 规则接收两个选项,均为对象;第一个可用来传递 Prettier 的配置项;第二个可通过 usePrettierrc 属性设置是否使用 .prettierrc 文件中的Prettier 的配置项;默认值如下:

// .eslintrc.js
{
  rules: {
    "prettier/prettier": ["error", {}, {
       usePrettierrc: true,
       fileInfoOptions: {}
     }]
  }
}
  • 第一个选项: 一个 Prettier 配置项对象 Prettier options. 例如:

    "prettier/prettier": ["error", {
       "singleQuote": true,
       "parser": "flow"
     }]
    

    这里的配置项会覆盖 .prettierrc 文件中的配置项。

    注意:虽然可以通过 ESLint 配置文件将选项传递给 Prettier,但不建议这样做,因为诸如 "prettier-atom" 和 "prettier-vscode" 这样的编辑器插件会读取 .prettierrc,但不会读取 ESLint 中的设置,从而导致不一致的体验。

  • 第二个选项,包含以下属性的对象:

    • usePrettierrc: 是否启用 Prettier 配置文件,默认是启用;

      "prettier/prettier": ["error", {}, {
        "usePrettierrc": true
      }]
      
    • fileInfoOptions: 传递给 Prettier API - prettier.getFileInfo,用于决定是否需要对文件进行格式化。例如,可以用来选择不忽略位于 node_modules 目录下的文件。

      "prettier/prettier": ["error", {}, {
        "fileInfoOptions": {
          "withNodeModules": true
        }
      }]
      

Prettier 规则

我们看下 prettier/prettier 规则的源码:

const prettierOptions = Object.assign(
  {},
  initialOptions, // { parser }
  prettierRcOptions, // .prettierrc 中的选项
  eslintPrettierOptions, // .eslintrc.js 中 'prettier/prettier' 规则的第一个 option
  { filepath }
);
prettierSource = prettier.format(source, prettierOptions);

可以看出,Prettier 格式化最终采用的规则是 .prettierrc 和 "prettier/prettier" 第一个 option merge 而成的。

使用

  • 首先安装 prettier 和 eslint-plugin-prettier。

    npm install --save-dev prettier
    npm install --save-dev eslint-plugin-prettier
    
  • .eslintrc.js 中开启 prettier 这条规则。

    {
      extends: [
        ..., // 其他
        "prettier", // eslint-config-prettier
      ]
      plugins: ["prettier"], // eslint-plugin-prettier
      rules: {
        "prettier/prettier": "error" // 开启规则
      }
    }
    
  • eslint-plugin-prettier 提供了这样一个配置 plugin:prettier/recommended,具体内容如下:

    注意:使用 eslint-plugin-prettier 和 --fix 时开启 arrow-body-style & prefer-arrow-callback 可能会导致 issue,因此建议关闭这两条规则。

    {
      "extends": ["prettier"],
      "plugins": ["prettier"],
      "rules": {
        "prettier/prettier": "error",
        "arrow-body-style": "off", 
        "prefer-arrow-callback": "off"
      }
    }
    

    因此我们的 .eslintrc.js 可以更简单

    // .eslintrc.js
    {
      "extends": ["plugin:prettier/recommended"]
    }
    

总结

eslint 和 prettier 配置使用需要进行如下操作:

  • 首先下载 eslint, prettier, eslint-config-prettier, eslint-plugin-prettier

    npm install --save-dev eslint prettier eslint-config-prettier eslint-plugin-prettier
    
  • 项目根目录添加 .eslintrc.js 和 .prettierrc

    // .eslintrc.js
    {
      "extends": ["plugin:prettier/recommended"]
    }
    // .prettierrc
    {
      "singleQuote": true
    }
    
  • 如果有使用到一些 ESLint 插件(如eslint-plugin-react),需要禁掉这些插件中与 Prettier 冲突,所以需要添加 "prettier/react"。

    {
      "extends": [
        "plugin:prettier/recommended",
        "prettier/flowtype",
        "prettier/react"
      ]
    }
    

推荐

ESLint 之解析包名