フロントエンドに静的解析と自動整形を導入したい

## 概要
- コードレビューの際、フォーマット崩れの指摘で摩耗しないためにJavaScript、SCSS、HTMLにフォーマッターを導入した際の備忘録です。
- 色々と右往左往してしまったので、今後の自分のためにも書き出してみました。

## できたこと

- [ESLint](https://eslint.org/)と[Prettier](https://prettier.io/)を併用したJavaScriptの静的解析と自動フォーマット
- [stylelint](https://stylelint.io/)とPrettierを併用したSCSSの静的解析と自動フォーマット
- Prettierを使用したHTMLの自動フォーマット

## できなかったこと

- HTMLの静的解析
  - ESLintのようにローカル環境で実行できるプラグインを探していましたが当時は見当たらなかったため断念しました
  - これいいよ！っていうプラグインがあったらぜひ教えてください

## JavaScriptの静的解析とフォーマット

### ESLint導入

JavaScriptの静的解析のために[ESLint](https://eslint.org/)をインストールします。

```
$ npm install eslint --save-dev
```

`.eslintrc`ファイルにルールを定義します。ゼロから独自のルールを定義するよりは、提供されているルールをカスタマイズする方が扱いやすいと思うので、今回は推奨設定を読み込んで一部だけカスタマイズしました。

- `env` : スクリプトが実行される環境を設定する
- `extends` : 外部のルールを読み込む
- `rules` : 一部ルールのカスタマイズ

```json:.eslintrc
{
    "env": {
        "browser": true,
        "jquery": true,
        "node" : true,
        "es6": true
    },
    "extends": ["eslint:recommended"],
    "rules": {
        "eqeqeq": [
            "error",
            "always"
        ],
        "no-multi-spaces": "error",
        "no-labels": "error",
        "yoda": [
            "error",
            "never",
            {
                "onlyEquality": true
            }
        ]
    }
}
```

### ESLintとPrettierの連携

#### Prettierインストール

まずは[Prettier](https://prettier.io/)をインストールします。

```
$ npm install prettier --save-dev
```

#### eslint-plugin-prettierインストール

ESLintで定義したルールを噛みつつ、Prettierでフォーマットできるように以下のプラグインをインストールします。

- [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier) : ESLintとPrettierを連携させます
- [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) : Prettierと競合するESLintのルールを無効化します

```
$ npm install eslint-plugin-prettier eslint-config-prettier --save-dev
```

#### 定義修正

Prettierと競合するESLintのルールは無効化されるため、フォーマット関係のルールはPrettierで定義します。

```json:.eslintrc
{
    "env": {
        "browser": true,
        "jquery": true,
        "node" : true,
        "es6": true
    },
    "extends": ["eslint:recommended", "prettier"],
    "plugins": ["prettier"],
    "rules": {
        "prettier/prettier": ["error", {
            "tabWidth": 4,
            "singleQuote": true,
            "semi": true,
            "endOfLine": "lf"
        }],
        "eqeqeq": [
            "error",
            "always"
        ],
        "no-multi-spaces": "error",
        "no-labels": "error",
        "yoda": [
            "error",
            "never",
            {
                "onlyEquality": true
            }
        ]
    }
}
```

これで`eslint --fix`実行時にPrettierのフォーマットもしてくれるようになりました。

#### 補足：prettier-eslintについて

上記のプラグイン以外に、[prettier-eslint](https://github.com/prettier/prettier-eslint)というプラグインもあります。こちらはPrettier定義のフォーマットをした後にESlintのfixを走らせるため、ESLintとPrettierで被るルールがある場合ESLintのルールが優先されます。

今回はこちらの記事を参考にeslint-plugin-prettierを採用しました。

- [ESLint(あるいはTSLint)とPrettierを併用する](https://tech-1natsu.hatenablog.com/entry/2018/01/07/154941)

個人的感触ですが、基本的には一元管理できるeslint-plugin-prettierが使いやすそう。どうしてもPrettierとESLintを別々に使いたいケースがあるのであればprettier-eslint、そうでなければeslint-plugin-prettierがいいかなと思いました。

## SCSSの静的解析とフォーマット

### stylelint導入

cssの静的解析のため[stylelint](https://stylelint.io/)をインストールします。

```
$ npm install stylelint --save-dev
```

`.stylelintrc`ファイルにルールを定義します。ESLint同様、推奨ルールを読み込んで一部をカスタマイズしました。並び替え用のプラグインも追加しました。

```
$ npm install stylelint-scss stylelint-config-standard stylelint-config-idiomatic-order --save-dev
```

`.stylelintrc`ファイルにルールを定義します。ESLint同様、推奨ルールを読み込んで一部をカスタマイズしました。並び替え用のプラグインも追加しました。

```json:.stylelintrc
{
    "pulugins": "stylelint-scss",
    "extends": [
        "stylelint-config-standard",
        "stylelint-config-idiomatic-order"
    ],
    "rules": {
        "at-rule-no-unknown": [
            true,
            {
              "ignoreAtRules": ["extend", "include", "mixin"]  
            }
        ],
        "string-quotes": "single",
        "indentation": 4
    }
}
```

#### 補足:SCSS用の設定

stylelint-scssプラグインと、`mixin`や`extend`などのSCSS記法に対する警告を無視する設定が必要です。
はじめはこちらの[issue](https://github.com/stylelint/stylelint/issues/3190)を参考に以下の設定をしたのですが、`scss/at-rule-no-unknown`の読込でエラーになってしまったため、今回はプロジェクトで使用していたSCSS記法を無視するように設定しました。

↓ やりたかったこと

```json:.stylelintrc
{
    "pulugins": "stylelint-scss",
    "rules": {
        "at-rule-no-unknown": null,
        "scss/at-rule-no-unknown": true
    }
}
```

↓ 今回の設定

```json:.stylelintrc
{
    "pulugins": "stylelint-scss",
    "rules": {
        "at-rule-no-unknown": [
            true,
            {
              "ignoreAtRules": ["extend", "include", "mixin"]  
            }
        ]
    }
}
```


### stylelintとPrettierの連携

#### stylelint-prettierインスト―ル

stylelintで定義したルールを噛みつつ、Prettierでフォーマットできるように以下のプラグインをインストールします。

- [stylelint-prettier](https://github.com/prettier/stylelint-prettier) : stylelintとPrettierを連携させます
- [stylelint-config-prettier](https://github.com/prettier/stylelint-config-prettier) : Prettierと競合するstylelintのルールを無効化します

```
$ npm install stylelint-prettier stylelint-config-prettier --save-dev
```

#### 定義修正

- `extends`に`stylelint-prettier/recommended`と追加すると連携が有効化されます。
- カスタマイズルールをstylelint側に定義したままだと無視されてしまうため、Prettier側の設定として記載を修正します。(`"prettier/prettier"`以下)

```json:.stylelintrc
{
    "pulugins": "stylelint-scss",
    "extends": [
        "stylelint-config-standard",
        "stylelint-config-idiomatic-order",
        "stylelint-prettier/recommended"
    ],
    "rules": {
        "at-rule-no-unknown": [
            true,
            {
              "ignoreAtRules": ["extend", "include", "mixin"]  
            }
        ],
        "prettier/prettier": [
            true,
            {
                "singleQuote": true,
                "tabWidth": 4
            }
        ]
    }
}
```

これで`stylelint --fix`実行時にPrettierのフォーマットもしてくれるようになりました。

#### 補足：prettier-stylelintについて

ESLintと同様に、stylelintにも[prettier-stylelint](https://github.com/hugomrdias/prettier-stylelint)というPrettier連携プラグインがあります。prettier-stylelintはprettier-eslintと似ていて、Prettier定義のフォーマットをした後にstylelintのfixを走らせるため、stylelintとPrettierで被るルールがある場合stylelintのルールが優先されます。

プロジェクトにはprettier-stylelintを導入していましたが、今回備忘録をまとめるにあたってstylelint-prettierの存在を知りました。stylelint-prettierはeslint-plugin-prettierと似た使用感なので、今後使うとしたらstylelint-prettierだなーと思って手順はそちらをまとめました。


## HTMLのフォーマット

ほぼPrettierのデフォルト設定でフォーマットしました。変更した設定といえば1行の最大長を150にしたくらいです。

```json:.prettierrc
{
    "printWidth": 150
}
```

:warning: 今までに使用したプラグインの性質上、ここで定義した設定はJSやSCSSにも適用されてしまいます。各言語で異なる設定をしたい場合は、`.eslintrc`や`.stylelintrc`にそれぞれのPrettierルールを定義することで解決します。


## 自動フォーマット

### VScodeで保存時にフォーマットさせる

プロジェクトではVSCodeを使っていたので、VSCodeにプラグインを導入します。保存時に自動修正できるようになるだけでなく、各linterの警告がエディタ上で可視化されるため開発しやすくなります。

- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
- [stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint)
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)

上記のプラグインをインストールして、`.vscode/.settings.json`に以下を追記すると、保存時の自動フォーマットが有効化されます。

```json:.vscode/.settings.json
{
    "editor.formatOnSave": false, // 対象外のファイルを自動整形しないように
    "editor.codeActionsOnSave": {
        // For ESLint
        "source.fixAll.eslint": true,
        // For Stylelint
        "source.fixAll.stylelint": true
    },
    "[html]": {
        "editor.formatOnSave": true
    }
}
```


### コミット(ステージ)時に強制フォーマットさせる

Git上に必ずフォーマットされたコードがプッシュされるので、設定さえしてしまえばフォーマットに関する指摘はゼロになります。開発者によって使っているエディタが異なったり、フォーマッタの設定をしたつもりがうまく効いていなかったり…なんてケースに有効かも。

公式ドキュメント記載のPre-commit Hook導入方法をみると、オプション1のユースケースに当てはまるのでよさそうです。

- https://prettier.io/docs/en/precommit.html

> Use Case: Useful for when you want to use other code quality tools along with Prettier (e.g. ESLint, Stylelint, etc.) or if you need support for partially staged files (git add --patch).

手順に従って[husky](https://github.com/typicode/husky)と[link-staged](https://github.com/okonet/lint-staged)をインストールして、`package.json`に設定を追加しました。

```json:package.json
{
    "lint-staged": {
        "*.html": [
            "prettier --write",
            "git add"
        ],
        "*.js": [
            "eslint --fix",
            "git add"
        ],
        "*.scss": [
            "stylelint --fix",
            "git add"
        ]
    }
}
```

これでステージの際にhtml, js, scssファイルがフォーマットされるようになりました。