📌

【ESLint】個人テンプレートのESLintを改善するためにeslint-config-airbnbを調査してみる

2023/03/31に公開

モチベーション

  • 個人テンプレートのESLintをアップデートさせる
  • airbnbのルールセットをベースに案件に合わせたルールを設定したい
  • airbnbでどのようなルールセットが適応されているのか探すのが面倒なので設定ファイルのリンクを載せて辞書っぽい記事にしたい
改善前のESLint
{
  "env": {
    "browser": true,
    "es2021": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "airbnb",
    "airbnb-typescript",
    "prettier"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "ecmaVersion": "latest",
    "sourceType": "module",
    "project": "./tsconfig.json"
  },
  "plugins": ["react", "react-hooks", "@typescript-eslint"],
  "rules": {
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "error",
    "@typescript-eslint/no-non-null-assertion": "off",
    "@typescript-eslint/no-explicit-any": "off",
    "@typescript-eslint/no-throw-literal": "off",
    "@typescript-eslint/explicit-module-boundary-types": "off",
    "react/require-default-props": "off",
    "spaced-comment": "off",
    "no-console": "off",
    "no-alert": "off",
    "arrow-body-style": "off",
    "import/prefer-default-export": "off",
    "@typescript-eslint/no-shadow": "off",
    "@typescript-eslint/naming-convention": "off",
    "@typescript-eslint/no-unused-expressions": "off",
    "@typescript-eslint/no-unused-vars": "off",
    "react/prop-types": "off",
    "react/jsx-props-no-spreading": "off",
    "react/react-in-jsx-scope": "off",
    "react/function-component-definition": [
      2,
      { "namedComponents": "arrow-function" }
    ]
  }
}

※2024年8月3日に更新しました

eslint-config-airbnb

eslint-config-airbnbをちゃんと理解したいので改めてREADMEを読んでみましょう

私たちのデフォルトのエクスポートには、ECMAScript 6+とReactを含む、私たちのESLintルールのほとんどが含まれています。eslint、eslint-plugin-import、eslint-plugin-react、eslint-plugin-react-hooks、eslint-plugin-jsx-a11yを必要とします。

https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb/README.md

以下のコマンドでeslint-config-airbnb + eslinteslint-plugin-importeslint-plugin-reacteslint-plugin-react-hookseslint-plugin-jsx-a11yがdevDependenciesにインストールされます

ターミナル
$ npx install-peerdeps --dev eslint-config-airbnb

使用する際は.eslintrc.json以下のように追加します

.eslintrc.json
"extends": ["airbnb"]

実際にairbnbでexportされているのはairbnb-baseとreactとreact-a11yになります

https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb/index.js#L1-L8

もしReact周りの設定が不要な場合は.eslintrc.jsonを以下のように指定することでreact周りのルールセットを排除することができます

.eslintrc.json
"extends": ["airbnb-base"]

https://github.com/airbnb/javascript/blob/5c01a1094986c4dd50a6ee4d9f7617abdfabb58a/packages/eslint-config-airbnb-base/index.js#L1-L17

eslint-plugin-react-hooksを有効にするにはairbnb/hooksをextendsに追加する必要があります。

このエントリポイントでは、Reactフックのlintingルールを有効にします(v16.8+が必要です)。使用するには、"extends "を追加します:.eslintrc に ["airbnb", "airbnb/hooks"] を追加します。

https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb/README.md#eslint-config-airbnbhooks

.eslintrc.json
"extends": ["airbnb", "airbnb/hooks"]

eslint-config-airbnbeslint-plugin-react, eslint-plugin-react-hooks, eslint-plugin-jsx-a11yのどのようなルールを設定をしているかは下記から確認することができます

https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb/rules

これでairbnbのルールセットの追加は完了です。思ったよりも簡単ですね。

airbnb/hooksとreact-hooks/recommendedの設定を比較してみる

eslint-plugin-react-hooksのairbnbのルールセット(airbnb/hooks)とrecommendのルールセット(react-hooks/recommended)を比較してどのような違いがあるか見ていきましょう

airbnb/hooksの場合

react-hooks/rules-of-hooksreact-hooks/exhaustive-depsがerrorで設定されています

https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb/rules/react-hooks.js#L12-L20

react-hooks/recommendedの場合

airbnbと違ってreact-hooks/exhaustive-depsがwarnで設定されています

https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/src/index.js#L16-L19

これらから読み取れるようにairbnb/hooksの方が厳しめに設定していることがわかります。個人的にはreact-hooks/exhaustive-depsをerrorにするのは厳しすぎるかなと思うのでwarnにしたいのですが以下の選択をする必要がありそうです。

  • airbnb/hooksだけを導入し、rulesで'react-hooks/exhaustive-deps': 'warn'として設定を上書きする
  • react-hooks/recommendedだけを導入しデフォルト設定を受け入れる

個人的にはベースをairbnbで揃えたい気持ちがあったので前者を選択しました

airbnb-typescript

Reactのプロジェクトを進める上でTypeScriptは必須になると思います。airbnbはTypeScriptのルールセットも提供していてtypescript-airbnbというものがあります。こちらもみていきましょう。

https://www.npmjs.com/package/eslint-config-airbnb-typescript

READMEに記載がある通りにインストールを進めます。

ターミナル
npm install eslint-config-airbnb-typescript \
            @typescript-eslint/eslint-plugin@^7.0.0 \
            @typescript-eslint/parser@^7.0.0 \
            --save-dev
.eslintrc.json
"extends": ["airbnb", "airbnb-typescript"]

これでairbnb-typescriptのルールセットの追加は完了です。こちらも簡単でしたね。

typescript-airbnbと@typescript-eslint/recommendedの設定を比較してみる

具体的には@typescript-eslint/no-shadowにスポットを当てて設定内容を比較していこうと思います(こだわりはないのでなんとなくでno-shadowをピックアップしました)

@typescript-eslint/recommended

ドキュメントと実際のコードを見てみましたが@typescript-eslint/no-shadow@typescript-eslint/recommendedではデフォルト設定していないようなので特にワーニングやエラーが起きることはありません

https://typescript-eslint.io/rules/

https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/configs/recommended.ts#L12-L36

もし@typescript-eslint/no-shadowを有効させるには別途rulesに設定を追加する必要があります

.eslintrc.json
// 以下をrulesに追加
"@typescript-eslint/no-shadow": "error",

typescript-airbnb

一方でtypescript-airbnbでは@typescript-eslint/no-shadowが有効化されているのでrulesに追記しなくてもリントエラーが出ます。

typescript-airbnb@typescript-eslint/recommendedのルールセットを全てを比較したわけではありませんがairbnbの方が厳しめ設定していそうです

https://github.com/iamturns/eslint-config-airbnb-typescript/blob/master/lib/shared.js#L160-L163

https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb-base/rules/variables.js#L36-L40

個人テンプレートのESLintをアップデートさせる

ようやく個人テンプレートのESLintをアップデートさせるパートです。量も多くなりそうなので@typescript-eslint/〇〇のルールに焦点を当ててアップデートさせていきたいと思います。

非nullアサーション演算子(Non-Null Assertion Operator)をブロックする

nullチェックすれば良いのを妥協していたので削除

.eslintrc.json
// 以下を削除
"@typescript-eslint/no-non-null-assertion": "off"

https://typescript-eslint.io/rules/no-non-null-assertion/

Error オブジェクトの代わりに数値や文字列、boolean などのリテラルを投げるのをブロックする

そもそもErrorオブジェクト以外を投げようとしていた背景が不明だったので削除

.eslintrc.json
// 以下を削除
"@typescript-eslint/no-throw-literal": "off"

https://typescript-eslint.io/rules/no-throw-literal/

戻り値に明示的に型をつけない関数をブロックする

業務委託先で導入されているので真似て導入していたが思った以上にvoid祭りになっていて旨味を感じなかったので削除。jsxファイルにも適用するには別途overridesする必要がある。

.eslintrc.json
// 以下を削除
"@typescript-eslint/explicit-module-boundary-types": "error"

https://typescript-eslint.io/rules/explicit-module-boundary-types/

外部スコープで宣言された変数と同じ名前の変数を宣言することブロックする

再帰的な処理でもない限りは変数名を被らせない方が良いと思ったので削除

.eslintrc.json
// 以下を削除
"@typescript-eslint/no-shadow": "off",
// エラーが発生する具体例
const {
    register,
    setValue,
    formState: { errors },
    watch,
    handleSubmit
  } = useForm<FormData>({
    resolver: yupResolver(getSchema())
  })

const onError: SubmitErrorHandler<FormData> = (errors) => {}
// 引数の変数(errors)がreact-hook-formのerrorsと被るのでエラーが発生する

https://typescript-eslint.io/rules/no-shadow

明示的なany型の利用をブロックする

any絶対許さないマン。と言いながら時には必要な場合もあるのでerrorで設定するのではなく、rulesから削除しデフォルトのwarnを適用させる。

.eslintrc.json
// 以下を削除
"@typescript-eslint/no-explicit-any": "off",

https://typescript-eslint.io/rules/no-explicit-any

最後に

長くなりましたが一旦これでFIXさせようと思います。もし間違い等ありましたらご指摘いただけますと幸いです。最後までご覧いただきありがとうございました。

改善後のESLint
.eslintrc.json
{
  "extends": ["airbnb", "airbnb/hooks", "airbnb-typescript", "prettier"],
  "parserOptions": {
    "project": "./tsconfig.json"
  },
  "rules": {
    // eslint-config-airbnb(eslint)
    "spaced-comment": "off",
    "no-console": "off",
    "no-alert": "off",
    "arrow-body-style": "off",
    // eslint-config-airbnb(eslint-plugin-import)
    "import/prefer-default-export": "off",
    // eslint-config-airbnb(eslint-plugin-react)
    "react/require-default-props": "off",
    "react/prop-types": "off",
    "react/jsx-props-no-spreading": "off",
    "react/react-in-jsx-scope": "off",
    // eslint-config-airbnb(eslint-plugin-react-hook)
    "react-hooks/exhaustive-deps": "warn",
    // eslint-config-airbnb-typescript(eslint-typescript)
    "@typescript-eslint/naming-convention": "off",
    "@typescript-eslint/no-unused-vars": "off"
  }
}

Next.jsのプロジェクトでもairbnbのルールセットは使えるの?

記事を更新したタイミングで個別の質問があったため補足します。

Q, Next.jsのプロジェクトでもairbnbのルールセットは使えるの?
A, はい、大丈夫です

少しNext.js側で用意しているeslint-config-nextの説明も交えながら解説していきます。

Next.jsは環境構築の段階でESLintを有効にする選択をした場合、自動でeslint-config-nextが.eslintrc.jsonに追加されていると思います。

添付したeslint-config-nextの中身を見ていただけるとわかると思いますが、airbnbでインストールしたものと同じパッケージが利用されています。そのため大枠は同じだと思っていて、airbnbのコンセプトを選択するか、nextのコンセプトを選択するかの差だと考えています。無難にNext.jsのプロジェクトを進めるなら最適化されているであろうnextのルールセットをチョイスするのが良いかなと思いました。

もし両方とも取り入れたい場合は、extendsの順番に注意してください。後出しが優先されるので、例えば改善後のESLintを以下のように変更した場合、nextのルールが優先される(オーバーライドする)のでairbnbのルールを骨子にメインはnextのルールにすることができます。

全体感を見た時にどっちをオーバーライドさせるかはルールを全て比較していないので保証はできませんが、Next.jsを使っているならnextがメインになるように設定したほうが良いと思いました。

.eslintrc.json
// nextのルールセットが優先される
"extends": ["airbnb", "airbnb/hooks", "airbnb-typescript", "next/core-web-vitals", "prettier"]

eslint-config-next

https://github.com/vercel/next.js/blob/canary/packages/eslint-config-next/index.js

https://nextjs.org/docs/pages/building-your-application/configuring/eslint#eslint-plugin

eslint-config-next/core-web-vitals

厳格にする場合はnext/core-web-vitalsを指定します。

https://github.com/vercel/next.js/blob/99a92826f6c01d9064a828feba96a617f46803f9/packages/eslint-config-next/core-web-vitals.js#L1-L3

https://github.com/vercel/next.js/blob/99a92826f6c01d9064a828feba96a617f46803f9/packages/eslint-plugin-next/src/index.ts#L54-L61

参考記事

https://zenn.dev/yoshiko/articles/0994f518015c04

https://zenn.dev/kimromi/articles/b7cf98005f3193

https://uit-inside.linecorp.com/episode/105

Discussion