CLOVER🍀

That was when it all began.

ESLintのコードスタイルルールとPrettierを合わせて使う時には、eslint-config-prettierを使う

これは、なにをしたくて書いたもの?

JavaScriptでのリンターといえばESLint、フォーマッターといえばPrettierだと思います。

ESLintにはコードスタイルに関するルールも含まれており、これがPrettierと競合することがあります。これをなんとかする場合は
eslint-config-prettierというものを使えばよいということを知ったので、試してみることにします。

ESLint

ESLintは、JavaScriptのリンターです。

Find and fix problems in your JavaScript code - ESLint - Pluggable JavaScript Linter

様々なルールがあり、コードの問題を見つけてくれます。

Rules Reference - ESLint - Pluggable JavaScript Linter

さらに、コードスタイルに関するルールも含まれています。

Formatters Reference - ESLint - Pluggable JavaScript Linter

TypeScriptで使うには、TypeScript ESLintというプラグインを使います。

typescript-eslint

Prettier

Prettierは、コードフォーマッターです。

Prettier · Opinionated Code Formatter

ドキュメントを見ると、JavaScript以外にもいろいろ対応しているようです。

  • JavaScript (including experimental features)
  • JSX
  • Angular
  • Vue
  • Flow
  • TypeScript
  • CSS, Less, and SCSS
  • HTML
  • Ember/Handlebars
  • JSON
  • GraphQL
  • Markdown, including GFM and MDX v1
  • YAML

What is Prettier? · Prettier

ESLintとPrettier

リンターにはコードの問題を探すだけではなく、コードスタイルに関するルールが含まれます。

こうなると、Prettierとリンターのコードスタイルに関するルールが競合する可能性があります。

これをどうしたらいいかは、Prettier側のドキュメントに回答があります。

Integrating with Linters · Prettier

リンターがESLintの場合は、eslint-config-prettierを使い、競合するESLint側のルールを無効にします。

GitHub - prettier/eslint-config-prettier: Turns off all rules that are unnecessary or might conflict with Prettier.

今回は、こちらを試してみたいと思います。

環境

今回の環境は、こちら。

$ node --version
v18.17.1


$ npm --version
9.6.7

TypeScript × Node.jsプロジェクトをセットアップする

今回は、TypeScriptとNode.jsでプロジェクトを作成したいと思います。

プロジェクトの初期化と、TypeScriptおよびNode.jsの型定義のインストール。

$ npm init -y
$ npm i -D typescript
$ npm i -D @types/node@v18

TypeScriptの設定ファイルの作成。

$ npx tsc --init

これを編集して、今回はこんな感じにしました。

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "moduleResolution": "node",
    "lib": ["esnext"],
    "baseUrl": "./src",
    "outDir": "dist",
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitOverride": true,
    "noImplicitReturns": true,
    "noPropertyAccessFromIndexSignature": true,
    "esModuleInterop": true
  },
  "include": [
    "src"
  ]
}

この時点で、package.jsonの依存関係はこんな感じです。

  "devDependencies": {
    "@types/node": "^18.17.6",
    "typescript": "^5.1.6"
  }

次に、ESLintのセットアップをします。

Getting Started with ESLint - ESLint - Pluggable JavaScript Linter

$ npm init @eslint/config

コードの問題だけではなく、スタイルに関するルールも含めるように指定。

✔ How would you like to use ESLint? · style
✔ What type of modules does your project use? · commonjs
✔ Which framework does your project use? · none
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · node
✔ How would you like to define a style for your project? · guide
✔ Which style guide do you want to follow? · standard-with-typescript
✔ What format do you want your config file to be in? · JavaScript
Checking peerDependencies of eslint-config-standard-with-typescript@latest
The config that you've selected requires the following dependencies:

eslint-config-standard-with-typescript@latest @typescript-eslint/eslint-plugin@^6.1.0 eslint@^8.0.1 eslint-plugin-import@^2.25.2 eslint-plugin-n@^15.0.0 || ^16.0.0  eslint-plugin-promise@^6.0.0 typescript@*
✔ Would you like to install them now? · No / Yes
✔ Which package manager do you want to use? · npm
Installing eslint-config-standard-with-typescript@latest, @typescript-eslint/eslint-plugin@^6.1.0, eslint@^8.0.1, eslint-plugin-import@^2.25.2, eslint-plugin-n@^15.0.0 || ^16.0.0 , eslint-plugin-promise@^6.0.0, typescript@*

フォーマットのルールは、今回は標準的なものを使いました。

生成された設定ファイルはこちら。

.eslintrc.js

module.exports = {
    "env": {
        "commonjs": true,
        "es2021": true,
        "node": true
    },
    "extends": "standard-with-typescript",
    "overrides": [
        {
            "env": {
                "node": true
            },
            "files": [
                ".eslintrc.{js,cjs}"
            ],
            "parserOptions": {
                "sourceType": "script"
            }
        }
    ],
    "parserOptions": {
        "ecmaVersion": "latest"
    },
    "rules": {
    }
}

package.jsonの依存関係はこうなりました。

  "devDependencies": {
    "@types/node": "^18.17.6",
    "@typescript-eslint/eslint-plugin": "^6.4.0",
    "@typescript-eslint/parser": "^6.4.0",
    "eslint": "^8.47.0",
    "eslint-config-standard-with-typescript": "^38.0.0",
    "eslint-plugin-import": "^2.28.1",
    "eslint-plugin-n": "^16.0.1",
    "eslint-plugin-promise": "^6.1.1",
    "typescript": "^5.1.6"
  }

ちなみに、このままESLintを実行すると、以下のようなエラーに悩まされます。

Error: Error while loading rule '@typescript-eslint/dot-notation': You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.

これを回避するには、.eslintrc.jsのparserOptionsにprojectを追加してtsconfig.jsonへのパスを指定します。

    "parserOptions": {
        "ecmaVersion": "latest",
        "project": "tsconfig.json"
    },

最後に、Prettierをインストールします。

$ npm i -D prettier

空の設定ファイルを作成。

$ echo {} > .prettierrc.json

package.jsonの依存関係はこちら。

  "devDependencies": {
    "@types/node": "^18.17.6",
    "@typescript-eslint/eslint-plugin": "^6.4.0",
    "@typescript-eslint/parser": "^6.4.0",
    "eslint": "^8.47.0",
    "eslint-config-standard-with-typescript": "^38.0.0",
    "eslint-plugin-import": "^2.28.1",
    "eslint-plugin-n": "^16.0.1",
    "eslint-plugin-promise": "^6.1.1",
    "prettier": "^3.0.2",
    "typescript": "^5.1.6"
  }

最後に、package.jsonのscriptsにESLintとPrettierを実行できるように構成。

  "scripts": {
    "lint": "eslint src --ext .js,.ts",
    "format": "prettier --write src",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

これで準備完了です。

ESLintとPrettierで矛盾するルールを作る

まずは、こんなファイルを作ってみます。

src/index.ts

console.log("Hello World!!");

この時点で競合します。

フォーマット。

$ npm run format

> eslint-with-prettier@1.0.0 format
> prettier --write src

src/index.ts 198ms

リント。

$ npm run lint

> eslint-with-prettier@1.0.0 lint
> eslint src --ext .js,.ts


path/to/src/index.ts
  1:13  error  Strings must use singlequote  @typescript-eslint/quotes
  1:29  error  Extra semicolon               @typescript-eslint/semi

✖ 2 problems (2 errors, 0 warnings)
  2 errors and 0 warnings potentially fixable with the `--fix` option.

ESLintで設定ファイルを作る時に選んだ、標準ルール(standard-with-typescript)と競合しているようです。

eslint-config-prettierを導入する

ではどうしたらいいのかですが、eslint-config-prettierを導入します。

GitHub - prettier/eslint-config-prettier: Turns off all rules that are unnecessary or might conflict with Prettier.

インストール。

$ npm i -D eslint-config-prettier

この時のpackage.jsonの依存関係。

  "devDependencies": {
    "@types/node": "^18.17.6",
    "@typescript-eslint/eslint-plugin": "^6.4.0",
    "@typescript-eslint/parser": "^6.4.0",
    "eslint": "^8.47.0",
    "eslint-config-prettier": "^9.0.0",
    "eslint-config-standard-with-typescript": "^38.0.0",
    "eslint-plugin-import": "^2.28.1",
    "eslint-plugin-n": "^16.0.1",
    "eslint-plugin-promise": "^6.1.1",
    "prettier": "^3.0.2",
    "typescript": "^5.1.6"
  }

インストール方法を見ると、ESLintの設定ファイルのextends内の最後に追加するようです。

eslint-config-prettier / Installation

というわけで、このように変更します。

    "extends": [
        "standard-with-typescript",
        "prettier"
    ],

これで、ESLintがNGとならなくなりました。

$ npm run lint

> eslint-with-prettier@1.0.0 lint
> eslint src --ext .js,.ts

prettierを最後に置くのは、設定を継承するextends内で最も優先されるようにするためのようです。

ちなみに今回は使いませんでしたが、以下のようにrules内に直接コードスタイルのルールを書くこともできます。

    "rules": {
        "indent": [
            "error",
            4
        ],
        "linebreak-style": [
            "error",
            "unix"
        ],
        "quotes": [
            "error",
            "double"
        ],
        "semi": [
            "error",
            "always"
        ]
    }

ここに書いた場合は、eslint-config-prettierを使っても競合するルールを無効化することはできないみたいです。

でも、そういう場合はそもそもESLintで競合するルールを定義するなという話ですね…。

まとめ

ESLintのコードスタイルルールとPrettierを合わせて使う時には、eslint-config-prettierを使うと競合するルールをオフにできるということ
でした。

まあ、そもそもESLintの設定ファイルを作成する時にはコードスタイルルールを含めないようにできますし、デフォルトでは
構文とコードの問題に関するチェックになっているので、そもそも競合するルールを含めないように構成するのがよいんでしょうね。

? How would you like to use ESLint? …
  To check syntax only
▸ To check syntax and find problems
  To check syntax, find problems, and enforce code style

ひとまず、こういう回避方法があると覚えておくとよさそうです。