mokajima.com

ESLint と Prettier のアップデート

はじめに

個人プロジェクトの ESLint と Prettier のアップデートを行いました(コミット履歴を確認ところ実に一年ぶり!)。yarn upgrade-interactive --latest で一括アップデートしたものの、エラーが 0 件から一気に 38 件になってしまいました。公式ドキュメントで調べつつ、エラーの解消と設定の見直しを行いました。

npm パッケージのバージョンの差異

ESLint と Prettier の npm パッケージの差異は以下の通りです。

     "@storybook/preset-create-react-app": "^2.1.1",
     "@storybook/react": "^5.3.17",
     "@types/babel-plugin-macros": "^2.8.1",
-    "@types/eslint-plugin-prettier": "^2.2.0",
-    "@types/prettier": "^1.19.0",
+    "@types/prettier": "^2.1.6",
     "@types/react-test-renderer": "^16.9.2",
     "@types/storybook__addon-info": "^5.2.1",
     "@types/stylelint": "^9.10.1",
-    "@typescript-eslint/eslint-plugin": "^2.10.0",
-    "@typescript-eslint/parser": "^2.10.0",
+    "@typescript-eslint/eslint-plugin": "^4.13.0",
+    "@typescript-eslint/parser": "^4.13.0",
     "axios-mock-adapter": "^1.17.0",
     "babel-plugin-macros": "^2.8.0",
-    "eslint-config-airbnb": "^18.0.1",
-    "eslint-config-prettier": "^6.7.0",
-    "eslint-plugin-import": "^2.19.1",
-    "eslint-plugin-jest": "^23.1.1",
-    "eslint-plugin-jsx-a11y": "^6.2.3",
-    "eslint-plugin-prefer-arrow": "^1.1.7",
-    "eslint-plugin-prettier": "^3.1.1",
-    "eslint-plugin-react": "^7.17.0",
-    "eslint-plugin-react-hooks": "^2.3.0",
+    "eslint-config-airbnb": "^18.2.1",
+    "eslint-config-prettier": "^7.1.0",
+    "eslint-plugin-import": "^2.22.1",
+    "eslint-plugin-jest": "^24.1.3",
+    "eslint-plugin-jsx-a11y": "^6.4.1",
+    "eslint-plugin-prefer-arrow": "^1.2.2",
+    "eslint-plugin-react": "^7.22.0",
+    "eslint-plugin-react-hooks": "^4.2.0",
     "jest-fetch-mock": "^3.0.1",
-    "prettier": "^1.19.1",
+    "prettier": "^2.2.1",
     "prettier-stylelint": "^0.4.2",
     "react-docgen-typescript-loader": "^3.7.1",
     "react-docgen-typescript-webpack-plugin": "^1.1.0",

エラーの解消

npm パッケージのアップデートに伴い発生したエラーは主に3つのルール由来のものでした。それぞれ @typescript-eslint/explicit-module-boundary-types @typescript-eslint/camelcase no-use-before-define です。

@typescript-eslint/explicit-module-boundary-types

アップデートに伴いルールのデフォルト設定が変更になり、エクスポートする関数の返り値の型の明記が必須となりました。これまでは型推論に任せていたため、返り値の型を明記するよう変更しました。

@typescript-eslint/camelcase

アップデートに伴いルールが削除されました。 ESLint のルールに camelcase のルールがあるため、そちらを使うように変更しました。

no-use-before-define

アップデートに伴い react のインポート文 import React from 'react' がエラー扱いとなってしまいました。

'React' was used before it was defined no-use-before-define

@typescript-eslint の公式ドキュメントによると、ESLint の no-use-before-define ルールをオフにし、@typescript-eslint/no-use-before-define の拡張ルールを適用する必要があるようです。

module.exports = {
  // ...
  rules: {
    // ...
    "no-use-before-define": 'off',
    "@typescript-eslint/no-use-before-define": ['error']
  }
}

In some cases, ESLint provides a rule itself, but it doesn't support TypeScript syntax; either it crashes, or it ignores the syntax, or it falsely reports against it. In these cases, we create what we call an extension rule; a rule within our plugin that has the same functionality, but also supports TypeScript.

https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/README.md#extension-rules

設定の見直し

エラー解消後、設定の見直しを行いました。

.eslintignore を --ignore-path .gitignore で代用

--ignore-path .gitignore とすることで .gitignore を ESLint のチェックの対象外とするファイルの指定に使うことができます。対象外としたいファイルが .gitignore に含まれる場合、 .eslintignore を別途用意する必要がないため便利です。

$ npx eslint --ignore-path .gitignore .

Prettier を直接実行

Prettier 導入時は Prettier を ESLint のルールとして実行するために eslint-plugin-prettier を使っていました。しかし、2021年1月現在の公式ドキュメントではその方法が非推奨となり、Prettier を直接実行する方法に変更されています。

そこで eslint-plugin-prettier をアンインストールし、prettier --write を直接実行するようにしました。

$ npx prettier --write .

.eslintrc.js を更新

.eslintrc.js を更新しました。.eslintrc.js の設定は『りあクト! TypeScript で始めるつらくない React 開発 第3.1版』.eslintrc.js を参考にさせていただきました。

module.exports = {
  env: {
    browser: true,
    es2020: true
  },
  extends: [
    'airbnb',
    'airbnb/hooks',
    'plugin:import/errors',
    'plugin:import/warnings',
    'plugin:import/typescript',
    'plugin:@typescript-eslint/recommended',
    'plugin:@typescript-eslint/recommended-requiring-type-checking',
    'plugin:jest/recommended',
    'prettier',
    'prettier/@typescript-eslint',
    'prettier/react'
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true
    },
    project: './tsconfig.eslint.json',
    sourceType: 'module',
    tsconfigRootDir: __dirname
  },
  plugins: [
    '@typescript-eslint',
    'import',
    'jest',
    'jsx-a11y',
    'prefer-arrow',
    'react',
    'react-hooks'
  ],
  root: true,
  rules: {
    'no-restricted-syntax': [
      'error',
      {
        selector: 'ForInStatement',
        message:
          'for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.'
      },
      {
        selector: 'LabeledStatement',
        message:
          'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.'
      },
      {
        selector: 'WithStatement',
        message:
          '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.'
      },
    ],
    'no-use-before-define': 'off',
    '@typescript-eslint/no-use-before-define': ['error'],
    'no-void': [
      'error',
      {
        allowAsStatement: true,
      },
    ],
    'padding-line-between-statements': [
      'error',
      {
        blankLine: 'always',
        prev: '*',
        next: 'return'
      }
    ],
    '@typescript-eslint/no-unused-vars': [
      'error',
      {
        'vars': 'all',
        'args': 'after-used',
        'argsIgnorePattern': '_',
        'ignoreRestSiblings': false,
        'varsIgnorePattern': '_'
      }
    ],
    'import/extensions': [
      'error',
      'ignorePackages',
      {
        js: 'never',
        jsx: 'never',
        ts: 'never',
        tsx: 'never'
      }
    ],
    'import/no-extraneous-dependencies': [
      'error',
      {
        devDependencies: [
          '.storybook/**',
          'stories/**',
          '**/*/*.story.*',
          '**/*/*.stories.*',
          '**/__specs__/**',
          '**/*/*.spec.*',
          '**/__tests__/**',
          '**/*/*.test.*',
          'src/setupTests.*'
        ]
      }
    ],
    'import/prefer-default-export': 'off',
    'prefer-arrow/prefer-arrow-functions': [
      'error',
      {
        disallowPrototype: true,
        singleReturnOnly: false,
        classPropertiesAllowed: false
      }
    ],
    'react/jsx-filename-extension': [
      'error',
      {
        extensions: ['jsx', 'tsx']
      }
    ],
    'react/jsx-props-no-spreading': [
      'warn',
      {
        html: 'enforce',
        custom: 'enforce',
        explicitSpread: 'ignore'
      }
    ]
  },
  overrides: [
    {
      'files': ['*.tsx'],
      'rules': {
        'react/prop-types': 'off'
      }
    }
  ],
  settings: {
    'import/resolver': {
      node: {
        paths: ['src']
      }
    },
    'react': {
      version: 'detect'
    }
  }
}

2021/03/14 更新

2021/02/21 に eslintrc-config-prettier の v8.0.0 がリリースされました。すべての設定が1つに統合され、.eslintrc.js をよりシンプルに記述できるようになりました。

    'plugin:@typescript-eslint/recommended',
    'plugin:@typescript-eslint/recommended-requiring-type-checking',
    'plugin:jest/recommended',
-   'prettier',
-   'prettier/@typescript-eslint',
-   'prettier/react'
+   'prettier'
  ],
  globals: {
    Atomics: 'readonly',

参考