👋

はじめてのESLint

2021/12/26に公開

ESLintの設定、毎回設定の方法を忘れるので、数ヶ月後の自分が困らないように、整理しました。

ESLintとは

  • Linter。JavaScript/TypeScriptの構文(コードの書き方)が、スタイルガイドラインなどの指定したルールに違反してないかをチェックして、違反してたら指摘してくれたり、修正してくれたりする仕組みです。
  • ルールのチェックをどうやってるかというと、JavaScriptは、ASTという木構造で表現することができるので、ASTの構造に変換し、あらかじめ決めたルールにあってるかチェックする仕組みになってます。 こちら でASTに変換を試したりできます。
  • 公式ドキュメントの説明は こちら

導入

initコマンドで設定ファイルの雛形の生成と必要なパッケージの導入

$ yarn eslint --init
✔ How would you like to use ESLint? · style
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · react
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser
✔ How would you like to define a style for your project? · guide
✔ Which style guide do you want to follow? · airbnb
✔ What format do you want your config file to be in? · JavaScript

scriptsの修正

  "scripts": {
    "lint": "eslint --ext ts,tsx .",
    "lint:fix": "eslint --ext ts,tsx --fix .",
  },

設定ファイル .eslintrc.js が生成されるので、必要なルールを設定します。

設定ファイル(.eslintrc.js) を読む/書くポイント

ESLintは、ソースコードの構文が、指摘したルールに沿ってるかチェックしてくれる仕組みですが、そのルールを実際に設定するのが .eslintrc.js です。

.eslintrc.js のサンプル例
module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    'airbnb',
    'plugin:@next/next/recommended',
    'prettier',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 2021,
    sourceType: 'module',
  },
  plugins: [
    'react',
    '@typescript-eslint',
  ],
  settings: {
    'import/resolver': {
      node: {
        extensions: ['.js', '.jsx', '.ts', '.tsx'],
      },
    },
  },
  rules: {
    'no-unused-vars': 'off',
    '@typescript-eslint/no-unused-vars': ['error'],
    'no-param-reassign': [2, {
      props: true,
      ignorePropertyModificationsFor: ['state', 'draftState'],
    }],
    'import/extensions': [2, 'ignorePackages', {
      js: 'never',
      jsx: 'never',
      ts: 'never',
      tsx: 'never',
    }],
    'import/prefer-default-export': 0,
    'react/jsx-filename-extension': [2, {
      extensions: ['.jsx', '.tsx'],
    }],
    'react/jsx-props-no-spreading': [2, {
      html: 'enforce',
      custom: 'ignore',
      explicitSpread: 'enforce',
    }],
    'react/jsx-uses-react': 0,
    'react/react-in-jsx-scope': 0,
    'react/prop-types': 0,
    'react/function-component-definition': [2, { namedComponents: 'arrow-function' }],
  },
};

ポイント1. pluginsとextendsとrulesの意味を理解する

インストールしたプラグインを plugins に追記せずに、extends に共有設定だけを追記しても、ルールが追加される場合があります。(extendsに指定した共有設定側の pluginsで読み込んでる場合があるため)

この時、ルールを追加するには、plugins に書かないといけないのでは?と混乱したのですが、はじめてeslintの設定する際、これらのルールを設定する主要なプロパティ(pluginsextendsrules)の意味を理解していないから、このような混乱が起きるかなと感じたので、pluginsextendsrulesの各プロパティの意味をまとめました。

(1) plugins

  • pluginsは、 ルール を追加する項目です。

  • npmを通して外部に公開されてるルールのpluginをインストールできます。pluginが持ってるルールを利用する際に、pluginsのプロパティに追記します。(後述しますが、extendsにて指定してる共有設定で、pluginsを指定してる場合もあるので、pluginsに指定しなくてもいい場合があります)

  • ドキュメント には、以下のように書かれてます。

which third-party plugins define additional rules, environments, configs, etc. for ESLint to use.」

(2) extends

  • extendsは、 ルールの設定 を拡張してくれる共有設定を指定する項目です。 (ルールのオン/オフの設定してくれる外部の設定ファイルを読み込んでくれます。)
  • ルール設定が重複している場合、後から書いた方が優先になります。

(3) rules

  • rulesは、 個別のルールの設定 を登録する項目です。
  • extendsで指定した共有設定以外で個別に設定したい内容を記載します。

ポイント2. ルールのレベル

ルールのレベルは、なし、警告、エラーの3つあります。慣れたら気にならなくなりましたが、最初、0と1と2の数字の羅列に混乱しました。

意味
なし 0 off
警告 1 warn
エラー 2 error

ポイント3. 導入した外部ルールのパッケージの名前を .eslintrc.js に記載する際に名前を省略して指定できる

慣れたらそんなに気にならないし、ドキュメントにもちゃんと書いてますが、名前が省略されてると、導入したパッケージと、設定の内容がどう紐づいているのかわからず、混乱しました。

  • 外部のルール、例えば、eslint-plugin-xxx というパッケージを導入して、eslintrc.js の pluginsに追記する際に、eslint-plugin-の部分を省略できます。
  • eslint-plugin-reactというパッケージを、extendsに追記する場合は、"plugin:react/recommended" となります。
  • eslint-config- も同様に省略できます。
  • ドキュメント参考

The plugins property value can omit the eslint-plugin- prefix of the package name.

The eslint-config- prefix can be omitted from the configuration name. For example, airbnb resolves as eslint-config-airbnb.

ポイント4. その他のプロパティ

env

  • プログラムの実行環境を指定します。
  • ブラウザで動かすコードの場合 window オブジェクトとか出てくると思いますが、browser: true, にしておくと、未定義だと怒られたりしません。
  env: {
    browser: true,
    es2021: true,
  },

parserとparserOptions

  • parserは、ESLintがプログラムの構文解析してくれる時に使われます。デフォルトでは、ES5になってます。例えば、TypeScriptが対象の場合は、parser: '@typescript-eslint/parser', を指定する必要があります。
  • parserOptionsは、parserの細かいオプションを指定するプロパティで、jsxを対象にする場合は、以下のように記載します。
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 2021,
    sourceType: 'module',
  },

settings

  • settings は任意の実行ルールに適用される追加の共有設定です。

extendsの設定を追ってみる

extends で指定した共有設定のルールが、具体的にどのような設定になっているか追ってみます。試しに、airbnbの設定を追ってみましょう。

  extends: [
    'airbnb',
  ],

extends には、'airbnb' と書かれていますが、eslint-config- の部分は省略できるので、実際のパッケージ名は、 eslint-config-airbnb です。

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

index.js は、以下のようになっていて、さらにextendsで設定を読み込んでいます。

module.exports = {
  extends: [
    'eslint-config-airbnb-base',
    './rules/react',
    './rules/react-a11y',
  ].map(require.resolve),
  rules: {}
};

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

./rules/react は、以下のようになっていて、ルールの設定がたくさん書かれてます。

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

試しに、この中から react/react-in-jsx-scope のルールを読み進めてみます。 pluginsでの指定は、'react' と記述されていますが、eslint-plugin の部分が省略できるので、パッケージ名は、eslint-plugin-react になります。

// Prevent missing React when using JSX
// https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md
'react/react-in-jsx-scope': 'error',

https://github.com/yannickcr/eslint-plugin-react

eslint-plugin-react のindex.js は、以下のようになっています。

... 省略
'react-in-jsx-scope': require('./lib/rules/react-in-jsx-scope'),
... 省略

https://github.com/yannickcr/eslint-plugin-react/blob/master/index.js

やっと、ルール自体が書かれてるコードにたどり着きました。./lib/rules/react-in-jsx-scope は、以下のようになっています。

https://github.com/yannickcr/eslint-plugin-react/blob/master/lib/rules/react-in-jsx-scope.js

このルール自体は、JSXを使うために、Reactをちゃんとimportしてるか、チェックするルールですが、他のルールもどんな感じで実装されてるか確認したい場合は、似たような感じでコードを追っていけば辿り着けます。

Prevent missing React when using JSX

https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md

ちなみにですが、トランスパイルの仕組みが新しくなって Reactはimportする必要がなくなりました。eslint-plugin-reactの推奨設定を利用する場合に、上記ルールは不要なので、ルールをoffにしましょう。

https://ja.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html#eslint

ルールの決め方

プロジェクトに導入するルールの決め方(.eslintrc.js の設定内容の決めた際の考え方)は、ベースとなる extendsを選定し、個別に無効にしたいルールや例外パターンのルールを、rulesに追記する感じで書きます。

eslint-config-airbnb

airbnbは、「Airbnb JavaScript Style Guid」 といったスタイルガイドがあり、このガイドに従ったルールが使えて便利です。

https://github.com/airbnb/javascript

eslint-config-next

nextjsが出してるルールなので、nextjsを利用する場合は導入した方が良さそうです。

https://nextjs.org/docs/basic-features/eslint

https://github.com/vercel/next.js/tree/canary/packages/eslint-plugin-next

eslint-config-prettier

prettierと競合するフォーマットのルールを無効化してくれます。

https://github.com/prettier/eslint-config-prettier

prettier 導入するときの考え方

prettierとeslintは、それぞれの設定で、ルールが競合する場合があります。 例えば、prettierの設定では、XXXX なルールでコードをフォーマットしてるが、eslintでは YYYYY のルールで構文解析をしていて、prettierで自動でコードフォーマットしても、eslintで構文がおかしいって怒られ、それをfixしても、prettierを動かすと、prettierのルールで自動でフォーマットされるというのが競合してると無限に繰り返されという問題があります。

対処方法の一つとして、eslint側に、Prettier と競合する可能性のある eslintのルールを無効にする共有設定を追加することで対応できます。

以下、参考にさせてもらった記事です。

https://blog.ojisan.io/prettier-eslint-cli/

prettier 導入

$ yarn add -D prettier eslint-config-prettier

.prettierrc.json 追加

{
    "singleQuote": true
}

script編集

  "scripts": {
    ... 省略	  
    "format": "prettier --write ./src",
    ... 省略	  
  },

.eslintrc.js 編集

extends: [
    ... 省略
    'prettier',
    ... 省略
],

参考にした記事と情報

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

https://zenn.dev/yhay81/articles/def73cf8a02864

https://blog.ojisan.io/eslint-plugin-and-extend/

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

https://twitter.com/oukayuka/status/1461623589317210116

https://github.com/oukayuka/Riakuto-StartingReact-ja3.0/blob/master/06-lint/01-eslint/.eslintrc.js

Discussion