JavaScriptのコード品質をさらに高めるeslint-plugin-unicornのススメ


JavaScriptのコードを書く際、ESLintによって静的解析をするのはデファクトスタンダードであると言ってもいいでしょう。
ESLintとともに[eslint-config-standard](https://www.npmjs.com/package/eslint-config-standard)や[eslint-config-airbnb](https://www.npmjs.com/package/eslint-config-airbnb)などのShareable Configsを使うことが多いかと思いますが、それらにくわえて[**eslint-plugin-unicorn**](https://www.npmjs.com/package/eslint-plugin-unicorn)も併用すると、コード品質のさらなる向上が期待できます。

https://www.npmjs.com/package/eslint-plugin-unicorn

## 導入方法

`eslint-plugin-unicorn`をインストールします。

```sh:npm
npm i -D eslint-plugin-unicorn
```

```sh:Yarn
yarn add -D eslint-plugin-unicorn
```

そして、ESLintの設定の`extends`に`plugin:unicorn/recommended`を追加すると、eslint-plugin-unicornの推奨設定が適用されます。

```json
{
  "extends": [
    "plugin:unicorn/recommended"
  ]
}
```

推奨設定の中には「これはどうなんだ」というものもあるので、プロジェクトに応じてよしなにカスタマイズするといいでしょう。
たとえば、略語の使用を禁止する`prevent-abbreviations`というルールを無効化したい場合、次のようにします。

```json
{
  "extends": [
    "plugin:unicorn/recommended"
  ],
  "rules": {
    "unicorn/prevent-abbreviations": "off"
  }
}
```

私は自分用のShareable Configをつくり、その中でいくつかのルールを上書きして使っています。

https://qiita.com/munieru_jp/items/37e9d979bc1a18952b38

## イチオシのルール

100を超えるeslint-plugin-unicornのルールの中から、特に私がイチオシしたいものをピックアップしてご紹介します。

### better-regex

https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/better-regex.md

正規表現の記法を最適化します。
たとえば、`[0-9]`は`\d`になります。

```js:エラーになるケース
/^[0-9]+$/.test('123')
```

```js:エラーにならないケース
/^\d+$/.test('123')
```

### catch-error-name

https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/catch-error-name.md

キャッチされたエラーの変数名を`error`などに強制します。

```js:エラーになるケース
try {
  await fetch('https://example.com')
} catch (e) {
  console.error(e)
}

try {
  await fetch('https://example.com')
} catch (err) {
  console.error(err)
}
```

```js:エラーにならないケース
try {
  await fetch('https://example.com')
} catch (error) {
  console.error(error)
}

// fooError のような形式も許容される
try {
  await fetch('https://example.com')
} catch (fetchError) {
  console.error(fetchError)
}
```

### consistent-function-scoping

https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/consistent-function-scoping.md

関数の定義される場所が、なるべく上位のスコープになるようにします。

```js:エラーになるケース
const findFooElement = () => {
  // isFooElement は findFooElement の外側に移動できる
  const isFooElement = (element) => {
    return element.textContent.includes('foo')
  }

  return Array.from(document.querySelectorAll('*')).find(element => isFooElement(element))
}
```

```js:エラーにならないケース
const isFooElement = (element) => {
  return element.textContent.includes('foo')
}

const findFooElement = () => {
  return Array.from(document.querySelectorAll('*')).find(element => isFooElement(element))
}
```

### expiring-todo-comments

https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/expiring-todo-comments.md

TODOコメントに日付やバージョンなどの条件を併記することで、その条件が満たされたらエラー扱いにすることができます。

```js:日付
// TODO [2022-12-31]: 2022年中に実装する
```

```js:パッケージのバージョン
// TODO [>=1.0.0]: v1のリリースまでに実装する
```

```js:エンジンのバージョン
// TODO [engine:node@>=18.0.0]: top level awaitに置き換える
```

```js:依存パッケージの追加
// TODO [+date-fns]: date-fnsに置き換える
```

```js:依存パッケージの削除
// TODO [-jquery]: ネイティブのAPIに置き換える
```

```js:依存パッケージのバージョン
// TODO [uuid@>=8.0.0]: named importに置き換える
```

### no-abusive-eslint-disable

https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-abusive-eslint-disable.md

ESLintの無効化コメントを記載する際、ルールの指定を強制します。
ルール単位で無効化することで、本来であれば検出されるはずのエラーが見過ごされてしまうような事態を防げます。

```js:エラーになるケース
// eslint-disable-next-line
console.log('Hello World')
```

```js:エラーにならないケース
// eslint-disable-next-line no-console
console.log('Hello World')
```

### no-array-callback-reference

https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-callback-reference.md

`Array.map()`などの高階関数にコールバック関数を直接渡すことを禁止します。
これにより、以下のように2個以上の引数をとる関数を渡したときの予期せぬ挙動を回避することができます。

```js:エラーになるケース
['10', '10', '10'].map(Number.parseInt) // [10, NaN, 2]
```

```js:エラーにならないケース
['10', '10', '10'].map(number => Number.parseInt(number)) // [10, 10, 10]
```

ただし、TypeScriptにおいて型ガードを適用するためには型ガード関数を直接渡す必要があるので、その際はコメントで無効化するとよいでしょう。

```ts
import { isNotUndefined } from 'type-guards'

const nullableValues: Array<number | undefined> = [1, 2, undefined]

const values1 = nullableValues.filter(value => isNotUndefined(value))
// values1 の型は　(number | undefined)[]

// eslint-disable-next-line unicorn/no-array-callback-reference
const values2 = nullableValues.filter(isNotUndefined)
// values2 の型は　number[]
```

### no-negated-condition

https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-negated-condition.md

条件式が否定形にならないようにします。

```js:エラーになるケース
if (!isFoo) {
  doBar()
} else {
  doFoo()
}
```

```js:エラーにならないケース
if (isFoo) {
  doFoo()
} else {
  doBar()
}
```

### no-null

https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-null.md

`null`の使用を禁止します。
値がないことを表現したいときは、常に`undefined`を使います。

```js:エラーになるケース
const foo = null
```

```js:エラーにならないケース
const foo = undefined
```

特にTypeScriptにおいては、`null`ではなく`undefined`を使うのは理に適っています。

https://typescript-jp.gitbook.io/deep-dive/recap/null-undefined

https://typescriptbook.jp/reference/values-types-variables/undefined-vs-null

### prefer-modern-dom-apis

https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-modern-dom-apis.md

DOM操作について、よりモダンなAPIの使用を優先します。

```js:エラーになるケース
foo.insertAdjacentElement('beforebegin', bar)
foo.insertAdjacentElement('afterbegin', bar)
foo.insertAdjacentElement('beforeend', bar)
foo.insertAdjacentElement('afterend', bar)
```

```js:エラーにならないケース
foo.before(bar)
foo.prepend(bar)
foo.append(bar)
foo.after(bar)
```

### prefer-node-protocol

https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-node-protocol.md

Node.jsのビルトインモジュールをインポートする際、`node:`プロトコルの記載を強制します。
これにより、インポートされているのがビルトインモジュールなのか否かが明確になります。

```js:エラーになるケース
import fs from 'fs'
```

```js:エラーにならないケース
import fs from 'node:fs'
```

`node:`プロトコルを記載することのメリットについては、以下の記事が詳しいです。

https://yosuke-furukawa.hatenablog.com/entry/2021/12/27/003424

### prefer-number-properties

https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-number-properties.md

`isNaN()`などのグローバル空間に生えている関数よりも、`Number.isNaN()`などの`Number`に生えている関数の使用を優先します。

```js:エラーになるケース
isNaN(123)
```

```js:エラーにならないケース
Number.isNaN(123)
```

## おわりに

以前はコードレビューで指摘したりされたりしていたようなことも、eslint-plugin-unicornの導入後はESLintによってコーディング段階で検出されるようになりました。
機械的に判定可能なものの指摘はリンターに任せることで、我々人間はロジックなど本質的な部分に注力することができます。
みなさんもぜひ、eslint-plugin-unicornを使ってみてください。