ESLintとPrettier、いい加減ちゃんと使えるようになろ。

2023/01/02に公開

まえがき

約2年間JavaScript/TypeScriptを使う開発現場で働いてきたが、
どのプロジェクトでもESLintは当たり前のように導入されており
CommitするときにLintチェックが走り、エラーがあればCommitできない仕組みになっていた。

このESLintをただ何となく使う側であったが、0から構築して導入する力も欲しいと思い、
この記事を書くことにする。

※参考になった記事
👉うわっ...私の.eslintrc、無駄が多すぎ...?
👉ESLint 最初の一歩
👉ESLint の設定ファイル (.eslintrc) の各プロパティの意味を理解する
👉ESLint - Prettier連携のやり方と仕組み
👉ESLint + Prettierを導入したTypeScript開発環境

先に総括

✅レビューの手間を減らす手段としてESLint/Prettierはめっちゃ良い手段。
  ESLint/Prettierを使いこなすことは確実に+になるのでちゃんと理解する👍

ESLint

✅ESLintとはJavaScriptのリンター(静的解析ツール)
✅デフォルトだとESLintチェック対象のRuleはゼロ。
✅AirbnbやGoogleが複数のRuleをまとめているRule一覧(共有設定)をextendsで読み込み、これをbaseとする。
✅extendsで取り込んだ共有設定に定義されている一部のRuleを上書きしたり、追加したりでその現場固有のコーディングスタイルを整えていくことになる。
pluginは「Rule/共有設定/環境変数/processer」を含んでいるものである。
🔴extendsは「共有設定」だけ読み込むが、pluginは「Rule/共有設定/環境変数/processer」を読み込むことになる。
👉pluginは「共有設定」を持っているので、extendsの対象に「plugin内の共有設定」を設定する事ができる。
✅npmパッケージはスコープでグルーピングできる → 名前が被っても大丈夫👍 参考

{
    plugins: ['@typescript-eslint'], // means @typescript-eslint/eslint-plugin
}

Prettier

✅PrettierとはJavaScriptの自動フォーマッター。
✅Prettier単体で利用可能。
✅だが、ESLintと一緒に使うパターンがほとんど。
✅ESLintと一緒に使う場合はフォーマット系のRulesはESLintのチェック対象外にする(Prettierで自動整形する)ため、eslint-config-prettierをextendsに追加する必要がある。

ESLintとは

JavaScriptのリンター(ソースコードを分析し問題点を指摘してくれる静的解析ツール)である。
JavaScriptのリンターであるESLintは、プラグイン経由でTypeScriptにも適用可能。

✅スクリプト言語であるJavaScriptはコンパイルの過程がないため、
  実行前にエラーを検知するための仕組み(リンター)が発達している。

✅以下の理由よりリンターを理解/活用することは絶対に+になる👍
 1. 人間によるレビューの手間を減らす。
 2. HumanErrorによる見過ごしの防止。

個人的には2のメリットが大きい。👪人間ではなく🤖機械に任せられるものは任せた方がいい。

まず使ってみよう。

任意の場所でプロジェクトフォルダ作成
mkdir eslint-practice
cd eslint-practice
package.json作成
npm init
eslintインストール
npm install eslint --save-dev
package.jsonにlintコマンド追加
{
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
+   "lint": "eslint src/**/*"
  },
+ "devDependencies": {
+   "eslint": "^8.30.0"
+ }
}

eslintコマンドの使い方

以下、公式より抜粋。

eslint [options] [file|dir|glob]*

✅eslintのチェック対象は「ファイル名」「ディレクトリ名」「globパターン」のいずれか。

パターン 対象ファイル
eslint src/Hello.ts ファイル名指定
eslint --ext .ts,.tsx . ディレクトリ名指定。--ext必須かな?
カレントディレクトリ配下のts,tsx拡張子ファイル。
eslint src/**/* globパターン。srcフォルダ配下の全ファイル
eslint src/**/*.tsx globパターン。srcフォルダ配下のtsx拡張子ファイル

👉--extオプションは、ディレクトリ指定時のみ有効。--extで指定した値間はスペースなし。
 × eslint --ext .ts,.tsx src/**/*
 × eslint --ext .ts, .tsx src/**/*
 〇 eslint --ext .ts,.tsx src/

.設定ファイル(.eslintrc.json)作成
{
    "extends": ["eslint:recommended"],
    "plugins": [],
    "parserOptions": {},
    "env": {"browser": true},
    "globals": {},
    "rules": {}
}
test.js作成
function hello(name) {
    document.body.textContent = "Hello, " + nama + "!"
}

hello("World");
lintコマンド実行 → 2件のエラー検知
C:\Users\daisu\Desktop\workspace\eslint-practice>npm run lint

> eslint-practice@1.0.0 lint
> eslint .


C:\Users\daisu\Desktop\workspace\eslint-practice\test.js
  1:16  error  'name' is defined but never used  no-unused-vars
  2:45  error  'nama' is not defined             no-undef

✖ 2 problems (2 errors, 0 warnings)

チェック対象のRulesは、デフォルトは0個。

"extends": ["eslint:recommended"],を消すと、先ほどのエラーが消える。

C:\Users\daisu\Desktop\workspace\eslint-practice>npm run lint

> eslint-practice@1.0.0 lint
> eslint .

C:\Users\daisu\Desktop\workspace\eslint-practice>

このことから分かるのは、
チェック対象のRulesはデフォルトでは0個。
eslint:recommendedというESLintコミュニティが用意してくれたRules一覧をextendsで取り込む=それらをチェックしている。

eslint:recommendedの正体

"extends": ["eslint:recommended"],

eslint-config-eslinteslint-recommended.jsを参照している。

eslint公式サイトeslint:recommendedでチェック対象のrule一覧が記載されている。沢山あるので、1つ1つまとめてみようと思ったが、内容としては結構当たり前なものが多いので、改めて確認する必要性はないと感じたのでここでは割愛する。

ruleを追加する。

先ほどのeslint:recommendedのようにextendsで追加したRules以外のRuleを追加したい場合は、↓のようにrulesに追加する。

.eslintrc.json
{
    "extends": "eslint:recommended",
    "env": {"browser": true},
    "rules": {
        // []の第1引数に【error/warn/off】のいずれかを設定する。
	// []の第2引数に、rule固有のOptionを定義する。semiだと"never"だというOptionがある。
        "semi": ["error", "never"]
    }
}

各項目の説明

extends

extendsプロパティで読み込む対象は、共有設定(Sharable configuration)と呼ばれる。
✅内部のルール設定が重複する場合は、後から指定したものが優先されるので注意

eslint-config-airbnb-baseを定義する場合
{
    // 接頭辞として「eslint-config-」が自動でつく。
    "extends": "airbnb-base"
    "env": {"browser": true},
    "rules": {
        "semi": ["error", "never"]
    }
}

共有設定は色々ある。JavaScript のコーディングスタイルとしてはAirbnb のスタイルが有名
eslint-config-airbnb【 including ECMAScript 6+ and React.】
eslint-config-airbnb-base【 including ECMAScript 6+ ※NO React】
eslint-config-standard
eslint-config-google
eslint-config-react-app

どのeslint-config-*を利用するかというのはそのプロジェクト次第である。↓のような比較記事もあったので参考に置いておく。
Airbnb vs Standard – ESLint のスタイルは何を選ぶべきか?

extendsで「pluginの共有設定」を取り込む。

↓のようにplugin:から始まるものは「pluginが保持する共有設定」を参照している。

{
  "extends": [
    "eslint:recommended",
    "plugin:react/recommended",
  ],
  ...

pluginとは?

そもそもpluginとは?公式の説明がこちら。

要は「CustomなRules/Configurations(共有設定)/environments/processors」を定義したもの。✅eslint-config-airbnbのような共有設定単体でパッケージングされているものに対して、eslint-plugin-reactのように共有設定+Rules/environments/processorsでパッケージングされているもの(Plugin)となる。

「non-scoped」「scoped」なnpmパッケージに対する名前解決

今まで見てきたnon-scopedなものにnpmパッケージに対して、@がつくようなものはscopedなnpmパッケージとなる。詳細はこちらの記事が参考になった。

{
  "extends": [
    // non-scoped
    "eslint:recommended",
    "plugin:react/recommended",
    "prettier"
    // scoped
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/eslint-recommended",
  ],
  ...

✅non-scoped → パッケージ名の接頭辞に「eslint-config」がつく。
✅scoped → plugin:@scope_name/filenameに対して@scope-name/eslint-pluginをパッケージ名とする。

名称 対象のコード
plugin:@typescript-eslint/recommended @typescript-eslint/eslint-pluginrecommended.ts
plugin:@typescript-eslint/eslint-recommended @typescript-eslint/eslint-plugineslint-recommended.ts
plugin:react/recommended eslint-plugin-react

env

事前に定義されたグローバル変数を読み込む。ブラウザ上だったらdocument変数は、グローバル変数じゃなかったら「こんな変数定義してないよ!」とLintチェックしてるときに怒られるが、envを定義しておけばOK。
参考:.eslintrcのenv設定ってなんぞや?
参考:ESLint 最初の一歩|環境設定をする

プロジェクト内にReactコードとExpressコードがあったら

env: {
    browser: false, // documentやwindows
    es6: true,      // ES6で追加された構文/オブジェクト
    jest: true,     // Jest関連のグローバル変数
    node: true,     // NodeJSのグローバル変数
},

parser

An ESLint parser converts code into an abstract syntax tree that ESLint can evaluate. By default, ESLint uses the built-in Espree parser

JS/TSのコードを、ESLintが分析できる構文に変換してくれる。
DefaultのParserとしてEspree parserが使われているが、TypeScriptコードをParseする場合は@typescript-eslint/parserをインストールするなどして設定する必要がある。

paserOption

ignorePatterns

overrides

(自分用)Airbnb-baseでチェック対象である&違反してたらErrorになるRuleの中で、対象外/Warning扱いにしたいやつ。

アロー関数関連

arrow-body-style

Require braces({}中括弧/波括弧) around arrow function bodies

アロー関数のボディを「{}」で囲わせるかどうか。

🔴airbnb-baseではチェック対象であり、違反してるとErrorとなる。
 チェック対象外にしたいなら↓のように定義しておく。

.eslintrc.json
{
    "extends": ["airbnb-base"],
    "rules": {
        "arrow-body-styled": "off"
    }
}

arrow-parens

Require parentheses(括弧()) around arrow function arguments

アロー関数の引数に「()括弧」をつけるかどうか。

🔴airbnb-baseではチェック対象であり、違反してるとErrorとなる。
 チェック対象外にしたいなら↓のように定義しておく。

.eslintrc.json
{
    "extends": ["airbnb-base"],
    "rules": {
        "arrow-parens": "off"
    }
}

Styling系

padded-blocks

Require or disallow padding within blocks

ブロック(波括弧{})前後に、空白行(Padding)を入れるか。

OK
if (a) {

    b();

}
NG
if (a) {
    b();
}

🔴airbnb-baseではチェック対象であり、違反してるとErrorとなる。
 チェック対象外にしたいなら↓のように定義しておく

.eslintrc.json
{
   "extends": ["airbnb-base"],
   "rules": {
       "padded-blocks": "off"
   }
}

max-len

Enforce a maximum line length

1行の最大文字数の設定

🔴airbnb-baseではチェック対象であり、最大文字数(Default:80)を超えてたらErrorとなる。

ErrorではなくWarning扱いとしたい場合
{
   "extends": ["airbnb-base"],
   "rules": {
       "max-len": "warn"
   }
}
最大文字数を150にする。
{
    "extends": ["airbnb-base"],
    "rules": {
        "max-len": [
	    "warn", 
	    {
               "code": 150,
               "ignoreComments": true,
               "ignorePatter": "^import .*"
             }
         ]
    }
}

no-plusplus

Disallow the unary operators ++ and --

Because the unary ++ and -- operators are subject to automatic semicolon insertion, differences in whitespace can change semantics of source code.

便利な++,--の利用を禁止する。なぜなら↓のように++/--前後のwhitespaceの違いによって動きが変わってしまう可能性があるから。

expected
var i = 10;
var j = 20;

i ++
j
// i = 11, j = 20
unexpected
var i = 10;
var j = 20;

i
++
j
// i = 10, j = 21

🔴airbnb-baseではチェック対象であり、違反してるとErrorとなる。
 warn扱いにするなら↓のように定義しておく

.eslintrc.json
{
    "extends": ["airbnb-base"],
    "rules": {
        "no-plusplus": ["warn", { allowForLoopAfterthoughts: true }]
    }
}

no-underscore-dangle

Disallow dangling underscores in identifiers

As far as naming conventions for identifiers go, dangling underscores may be the > most polarizing in JavaScript.

Varible系

no-shadow

Disallow variable declarations from shadowing variables declared in the outer scope

Shadowing is the process by which a local variable shares the same name as a variable in its containing scope.

ローカル変数名が外部スコープの変数名と被ってしまう(Shadow)ことを、許すか許さないか。

NG
var a = 3;
function b() {
    var a = 10;
}

airbnb-baseではチェック対象であり、違反してるとErrorとなる。
🔴ただ
グローバル変数に対するShadowingの検知**はoptionでbuiltinGlobalsをtrueにしないと✅されないので、↓のように設定する必要がある。

.eslintrc.json
{
   "extends": ["airbnb-base"],
   "rules": {
       "no-plusplus": ["error", { builtinGlobals: true }]
   }
}

ECMAScript6系

prefer-destructuring

Require destructuring from arrays and/or objects

配列、オブジェクト内から1値を取り出す際にDestructuring assignmentを使わせるかどうか。
ECMAScript6から導入された書き方。
✅配列、オブジェクトから複数の値をそれぞれの変数に格納したい場合は便利なので使うべき👍

Destructuring assignmentを使わない場合
// indexを指定して要素を取得する。
var foo = array[0];

// プロパティ名を指定して取得する。
var foo = object.foo;
var foo = object['foo'];
Destructuring assignmentを使う場合
// []で要素を取り出す。
const arr = [1,2,3,4,5];
const [a,b,c] = arr;
console.log(a, b, c); // 1, 2, 3

// {}で要素を取り出す。。
const person = { name: "DAISUKE", age: 26 };
const { name, age } = person;
console.log(`I'm ${name}. ${age} years old.`) //  "I'm DAISUKE. 26 years old."

🔴airbnb-baseではチェック対象であり、違反してるとErrorとなる。
 warn扱いにするなら↓のように定義しておく

.eslintrc.json
{
    "extends": ["airbnb-base"],
    "rules": {
        "prefer-destructuring": ["warn"]
    }
}

ESLintとPrettierと併用する

ESLintで、コードチェックする。
.eslintrc.jsonに、チェック対象のRulesを定義する。

Prettierで、フォーマットする。
prettier.config.jsに、フォーマット設定を定義する。

ESLintのRulesには、フォーマット系のものが含まれている。しかしフォーマットはPrettierに一任するので、ESLintのRulesからフォーマット系のRulesは取り除く。
 ↓
取り除くためにeslint-config-prettierをextendsに追加する。

Prettierインストール

npm install prettier --save-dev

フォーマット系のRulesをESLintチェック対象外にする。

npm install eslint-config-prettier --save-dev
extendsの"最後"に追加する。
{
    "extends": ["airbnb-base", "prettier"],
}

Prettierの設定ファイル

Prettierによる自動フォーマットのRulesをここに記載する。

.prettierrc.yaml
printWidth: 100
tabWidth: 2
semi: true
singleQuote: true
trailingComma: all
endOfLine: lf

VSCode -> ファイル保存時にPrettierで自動フォーマットさせる。

ESLint(dbaeumer.vscode-eslint)と Prettier(esbenp.prettier-vscode) の拡張機能をインストールする。

.vscode/settings.json追加
{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true 
}

ここまでの設定を完了すれば、VSCodeでのファイル保存時に.prettierrc.yamlの設定に基づいてPrettierが自動フォーマットしてくれる👍

Discussion