2022.07.12[Tue]

gitのコミット時にコードの変更部分に対して、自動でフォーマットを行う

  • Git
  • Javascript

目次

gitのコミット時にコードの変更部分に対して、自動でフォーマットを行う仕組みを導入しました。
そもそも、なぜコードフォーマッターが必要なのかは、
・ソースコードのスタイルの一貫性を保つため
・コードの構文チェックも行い、コードを実行する前に明らかなバグなど検出できるようにしたい
・Pull Request(以降PR)時にコードスタイルの指摘修正をなくすため
・ツールを導入することにより、手動で修正する手間をなくすため
以上、4点になります。
コードフォーマッターのLintのライブラリとして、PrettierとESLintの両方を導入しました。
本記事では、Prettier + ESLintを併用する理由、導入した仕組みの説明と導入した感想についてご紹介します。

環境

Node.js 16.13.0
npm 8.10.0
React 17.0.2
typescript 4.6.4

Prettier + ESLintを併用する理由

コードフォーマットについて
・Prettierはソースコードを整形してくれるツール
・ESLintはソースコードの整形する機能だけでなく、ソースコードの構文チェックを行えるツール

PrettierとESLintを併用する理由
・PrettierはESLintよりコードの整形に優れている
・ESLintはコードの構文チェックを担保できる
以上の2点からコードの整形とコードの構文チェックの両方担保したく、PrettierとESLintを併用することにしました。

PrettierとESLintの役割について
・Prettier:  コードの整形を担当し、コーディングスタイルを一定に保つ役割
・ESLint: コードの構文チェックを担当し、コードを実行する前に明らかなバグなど検出する役割

Prettierの導入

// npmの場合
npm install --save-dev --save-exact prettier

// yarnの場合
yarn add --dev --exact prettier

prettier-eslintをインストール
prettier-eslintはprettierとeslintを連携するためのパッケージです。

// npmの場合
npm install --save-dev prettier-eslint

// yarnの場合
yarn add prettier-eslint

Prettierのルールを記述

Prettierのルールを記述します。
ESLintと併用する以上、Prettierのルールは最低限に設定します。
そうしなければ、最低限に設定しないとPrettierとESLintの修正で競合が発生してしまうからです。
.prettierrcをプロジェクトルート配下に作成します。
[参照] https://prettier.io/docs/en/options.html

// .prettierrc
{
   "singleQuote": true
}

ESLintの導入

eslintをインストールします

// npmの場合
npm install --save-dev eslint

// yarnの場合
yarn add eslint

eslintのプラグインをインストールします。

// npmの場合
npm install --save-dev eslint-config-airbnb eslint-config-prettier eslint-plugin-react eslint-plugin-prettier eslint-plugin-jsx-a11y eslint-plugin-import eslint-import-resolver-alias eslint-import-resolver-babel-module eslint-import-resolver-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser babel-eslint 

// yarnの場合
yarn add --dev eslint-config-airbnb eslint-config-prettier eslint-plugin-react eslint-plugin-prettier eslint-plugin-jsx-a11y eslint-plugin-import eslint-import-resolver-alias eslint-import-resolver-babel-module eslint-import-resolver-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser babel-eslint

インストールしたパッケージのversionが下記一覧になります。

"eslint": "^8.17.0",

// config系
"eslint-config-airbnb": "^18.2.1",
"eslint-config-prettier": "^8.3.0",

// プラグイン系
"eslint-plugin-react": "^7.26.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-jsx-a11y": "^6.2.3",

// モジュールのインポート解決類のプラグイン
"eslint-plugin-import": "^2.25.1",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-import-resolver-babel-module": "^5.1.2",
"eslint-import-resolver-typescript": "^2.5.0",

// @typescript-eslint関連のプラグイン
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",

// babel-eslint
"babel-eslint": "^10.0.3",

ESLintのルールを記述

プロジェクトルート配下に.eslintrc.jsを作成し、設定を記述します。
ESLintのルールの記述はベースのルールを定義後、上書きしたいルールを記述します。
ベースのルールはextends配下に記述し、上書きしたいルールをoverrides配下に記述します。
ベースのルール、上書きしたいルールの選定はプロジェクトの環境・状況・要件に合わせてください。
下記は記述例になります。
[参照] https://eslint.org/docs/rules/

// .eslintrc.js

module.exports = {
  /** 実行環境設定 */
  env: {
    browser: true,
    es6: true,
    "jest/globals": true,
    node: true,
  },
  /** 継承したいベースのルールを記述 */
  extends: [
    "airbnb",
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
  ],
  /** 継承したルールの中から上書きしたいルールを記述 */
  overrides: [
         // *記述例
    {
      files: ["*.js"],
      rules: {
        /** JSファイルに限り、コメント文字の後はホワイトスペース以外の文字は禁止 */
        "spaced-comment": ["error", "always", { markers: ["/"] }],
      },
    },
    {
      files: ["*.ts", "*.tsx"],
      rules: {
        /** TSファイルに限り、動的な型担保の不使用 */
        "react/prop-types": "off",
      },
    },
  ],
  /** 使用するパーサーの設定を記述 */
  parser: "@typescript-eslint/parser",
  /** パーサーの設定 */
  parserOptions: { ecmaFeatures: { jsx: true }, sourceType: "module" },
  /** 使用するプラグインの指定 */
  plugins: ["@typescript-eslint", "import", "jest", "react"],
  /** 実行時のカレントディレクトリを起点をするかどうか */
  root: true,
  /** 個々のルール単位で有効/無効にする設定を記述 */
  rules: {
    camelcase: "off",
    "react/jsx-filename-extension": ["error", { extensions: ["tsx"] }],
    "react/require-default-props": "error",
    "react/jsx-uses-react": "error",
    "react/react-in-jsx-scope": "error",
    "import/prefer-default-export": "error",
  },
  settings: {
    /** パス解決 */
    "import/resolver": {
      node: { extensions: [".js", ".json", ".ts", ".tsx"] },
      typescript: { alwaysTryTypes: true },
    },
  },
};

.eslintignore にESLintの実行を無視するディレクトリ(ビルド出力など)を定義します。

**/*.*
!**/*.js
!**/*.jsx
!**/*.ts
!**/*.tsx
.*rc
.git/*
.github/*
.vscode/*
app/.next/*
app/assets/*
!app/assets/html
!app/assets/icons
app/coverage/*
app/libs/*
app/public/*
node_modules/*
.eslintignore
.eslintrc.js
.eslintrc.pre-commit.js



pre-commitの場合だけ特定のルールを緩和したい場合

別途.eslintrc.pre-commit.jsを作成し、緩和したいルールを記述します。
実際のファイルの書き方の例が下記になります。

// .eslintrc.pre-commit.js

const { overrides, rules, ...rest } = require("./.eslintrc");

// 緩和したいルールのみ記述
module.exports = {
  ...rest,
  overrides: [
    ...overrides,
    {
      files: ["*.js"],
      rules: {
                 // *記述例
        /**
         * JSファイルに限り、コメント文字の後はホワイトスペース以外の文字は禁止。
         */
        "spaced-comment": ["warn", "always", { markers: ["/"] }],
      },
    },
  ],
  rules: {
    ...rules,
        // *記述例
    /** 空インターフェイスの使用禁止。*/
    "@typescript-eslint/no-empty-interface": "warn",
    /** this のエイリアス変数の生成禁止。*/
    "@typescript-eslint/no-this-alias": "warn",
    /** クリック イベントに対応する、キーボード イベントの省略禁止 */
    "jsx-a11y/click-events-have-key-events": "warn",
    /** ラベル要素を持たないコントロール要素の実装禁止。*/
    "jsx-a11y/control-has-associated-label": "warn",
  },
};

コミット時にコードの変更部分に対して、Prettier と ESLint が実行されるようにする

上記の設定でコードの整形と構文チェックを可能となりましたが、PrettierとESLintを実行しない限りはコードの整形・構文チェックは行われません。PrettierとESLintを実行せず、PRを提出してしまう人が出てきてしまいます。これだとコードの品質が保てません。
コードの品質を保てるよう、コミット時にPrettierとESLintを実行できるようにします。
Gitには、pre-commitというコミットの前に指定のスクリプトを実行できる仕組みがあります。
今回はpre-commitを利用し、pre-commit時にPrettierとESLintを実行できるようにします。

pre-commitに必要なパッケージをインストール

huskylint-stagedをインストールします。

// npmの場合
npm install --save-dev husky lint-staged

// yarnの場合
yarn add husky lint-staged

husyはGit commit時やpush時に指定したコマンドを実行できるライブラリになります。
lint-stagedはGitのステージングに追加されたファイルにのみPrettierやESLintなどのリントを実行できるライブラリになります。
[参照]
https://github.com/okonet/lint-staged
https://typicode.github.io/husky/#/

huskyのセットアップ

Git フックを有効にします。
実行後、.huskyディレクトリと.husky.shが作成されます。

// npmの場合
npx husky install

// yarnの場合
yarn husky install

下記のコマンドを実行する

npm set-script prepare "husky install"

コマンドを実行すると、package.jsonscripts配下にprepareが追加されます。

{
  "name": "precommit-with-prettier-eslint",
  "scripts": {
    "prepare": "husky install",
  },
  ...
}

pre-commitのフックを追加します。

npx husky add .husky/pre-commit "npx lint-staged"

上記を実行すると.husky/pre-commitが作成されます。
.husky/pre-commitの中身は下記の内容になります。

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged

lint-staged.config.jsを作成します。
js,jsx,ts,tsxファイルに対して、PrettierとESLintのコマンドと対象ファイル・ディレクトリを記述します。

// lint-staged.config.js
module.exports = {
  '*.{js,jsx,ts,tsx}': [
    'prettier --write',
    'eslint --cache --config .eslintrc.pre-commit.js --fix --quiet',
  ]
};
各コマンドについて

prettier --write:対象ディレクトリのファイルをPrettierが全てフォーマット
eslint --cache --config .eslintrc.pre-commit.js --fix --quiet 
--cache: 変更されたものだけを操作するために、処理されたファイルに関する情報を保存するフラグ
--fix: コードに自動修正を行うフラグ
--quiet : 警告に関する報告を無効にするフラグ
--config.eslintrc.jsではなく、別途ファイルを読み込みたい場合のフラグ。今回であれば、.eslintrc.pre-commit.jsが当たります。
[参照] https://eslint.org/docs/user-guide/command-line-interface

ESLintよりPrettierを先に実行する理由は、ソースコードが整形された状態でソースコードの構文チェックを実行したいためです。先にPrettierを実行した上でESLintを実行した方がより確実に構文チェックを行えます。

以上の設定で、コミット時にPrettierとESLintが実行できるようになりました。
実際にコミットを実行してみます。
何らかのESLintのルールに引っかかり、コミットできなくなると、下記のようになります。

エラーになった場合は、エラーを解消しなければ、コミットはできません。
ESLintのエラーとなる箇所を修正し、ESLintのチェックが通ると、コミットができます。
コミット成功時は下記のようになります。

導入した感想

コミット時にPrettierとESLintが実行されることにより、コードの品質が一定水準以上に保つつことができました。また、インデントの修正などコードのスタイル修正に割く作業が無くなったことも大きな収穫でした。手軽に導入できましたので、導入して大変良かったです。少しでも魅力と感じられましたら、導入をお勧めします。

おわりに

以上、コミット時にコードの変更部分に対して、フォーマットをかける仕組みと導入した感想についてご紹介しました。PrettierとESLintの導入を検討している方のご参考になれば幸いです。

Share

ShopifyのAPIをPostmanで手軽にテストするDokku + Github Actions でプルリクエストに連動したサーバーを立てる 後編