39
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptAdvent Calendar 2022

Day 1

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

Last updated at Posted at 2022-11-30

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

導入方法

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

npm
npm i -D eslint-plugin-unicorn
Yarn
yarn add -D eslint-plugin-unicorn

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

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

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

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

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

イチオシのルール

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

better-regex

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

エラーになるケース
/^[0-9]+$/.test('123')
エラーにならないケース
/^\d+$/.test('123')

catch-error-name

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

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

try {
  await fetch('https://example.com')
} catch (err) {
  console.error(err)
}
エラーにならないケース
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

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

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

  return Array.from(document.querySelectorAll('*')).find(element => isFooElement(element))
}
エラーにならないケース
const isFooElement = (element) => {
  return element.textContent.includes('foo')
}

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

expiring-todo-comments

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

日付
// TODO [2022-12-31]: 2022年中に実装する
パッケージのバージョン
// TODO [>=1.0.0]: v1のリリースまでに実装する
エンジンのバージョン
// TODO [engine:node@>=18.0.0]: top level awaitに置き換える
依存パッケージの追加
// TODO [+date-fns]: date-fnsに置き換える
依存パッケージの削除
// TODO [-jquery]: ネイティブのAPIに置き換える
依存パッケージのバージョン
// TODO [uuid@>=8.0.0]: named importに置き換える

no-abusive-eslint-disable

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

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

no-array-callback-reference

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

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

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

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

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

エラーになるケース
if (!isFoo) {
  doBar()
} else {
  doFoo()
}
エラーにならないケース
if (isFoo) {
  doFoo()
} else {
  doBar()
}

no-null

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

エラーになるケース
const foo = null
エラーにならないケース
const foo = undefined

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

prefer-modern-dom-apis

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

エラーになるケース
foo.insertAdjacentElement('beforebegin', bar)
foo.insertAdjacentElement('afterbegin', bar)
foo.insertAdjacentElement('beforeend', bar)
foo.insertAdjacentElement('afterend', bar)
エラーにならないケース
foo.before(bar)
foo.prepend(bar)
foo.append(bar)
foo.after(bar)

prefer-node-protocol

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

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

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

prefer-number-properties

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

エラーになるケース
isNaN(123)
エラーにならないケース
Number.isNaN(123)

おわりに

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

39
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
39
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?