ESLint に再入門する

JavaScriptを書く上で必須のツールであるESLintだが、自分はあまりちゃんと理解していない。
最初に設定してしまえばその後はあまり手を加えないし、加えるときも都度調べて対症療法的に対応しているから、基礎は分かっていない。
取り敢えずairbnbextendesしておけば間違いない、くらいの理解。

だがESLintはコードを整理して綺麗にしていくための必須かつ強力なツールだし、新しい環境を作るときにいちいち躓くのも面倒なので、調べることにした。

Lintとは何か、何が素晴らしいか、みたいなことは書かない。個々のルールについても書かない。
ESLint自体の使い方、のような内容を書いていく。

環境は以下。

  • node
    • 10.9.0
  • npm
    • 6.2.0
  • eslint
    • 5.5.0

特に断りが無い限りsrc/に対象のJSファイルが入っている。
そうすると$ npx eslint src/でチェックできる。

rules

ルートディレクトリに.eslintrcを置き、そこにESLintの設定を書いていく。

個別のルールについては、rulesプロパティに書いていく。

例えば以下の設定だと、文末にセミコロンをつけないとエラーになる。errorwarnに変えると、エラーではなく警告になる。

{
  "rules": {
      "semi": ["error", "always"]
  }
}
// error  Missing semicolon  semi
1

/* eslint-disable *//* eslint-enable */で囲むと、その範囲だけESLintを無効に出来る。

/* eslint-disable */
1 // エラーにならない

/* eslint-enable */
1 // エラーになる

ESLintの実行時に--fixオプションをつけると自動的にコードを修正してくれる。自動では補完できないルールもある。

$ npx eslint --fix src/

現在有効になっているESLint設定を確認したい場合は、--print-configオプションを使う。

$ npx eslint --print-config src/
{
  "globals": {},
  "env": {},
  "rules": {
    "semi": [
      "error",
      "always"
    ]
  },
  "parserOptions": {}
}

env

よく使われているルールとして、no-undefがある。

{
  "rules": {
    "no-undef": "error"
  }
}

これは未定義の変数を使っていないかチェックしてくれる、非常に有用なルールである。これをオフにする理由は基本的にないだろう。

だが一つ問題があって、例えばdocumentオブジェクトを使おうとすると、エラーになってしまう。

// error  'document' is not defined  no-undef
document.querySelector('#app');

これは、documentオブジェクトはブラウザ環境でのみ使えるオブジェクトであり、JavaScriptそのものには定義されていないから発生している。

この問題を解決するために使うのが、envプロパティである。
これでbrowserを設定すると、ブラウザ環境固有のオブジェクトも問題なく使えるようになる。

{
  "env": {
    "browser": true
  },
  "rules": {
    "no-undef": "error"
  }
}

指定できる環境はbrowser以外にもたくさんある。
https://eslint.org/docs/user-guide/configuring#specifying-environments

parserOptions

ESLintではデフォルトでは、ES5の構文しか使えない。ES2015以降の構文を使うには設定を上書きする必要がある。

ESLint allows you to specify the JavaScript language options you want to support. By default, ESLint expects ECMAScript 5 syntax. You can override that setting to enable support for other ECMAScript versions as well as JSX by using parser options.

https://eslint.org/docs/user-guide/configuring#specifying-parser-options

具体的にはparserOptionsプロパティを設定できる。

まず、parserOptionsプロパティを何も設定せずに確認してみる。

// error  Parsing error: The keyword 'const' is reserved
const hoge = 1;

パースできていない。

parserOptions"ecmaVersion": 2015を設定することで、パースできるようになる。

{
  "rules": {
    "no-undef": "error"
  },
  "parserOptions": {
    "ecmaVersion": 2015
  }
}

これでES2015を使えるようになったかと思いきや、まだ問題がある。
parserOptionsはあくまでも構文解析に関するオプションなので、ES2015以降に追加された組み込みオブジェクトには対応していない。
そのため例えば、Mapは未定義とされてしまう。

// error  'Map' is not defined  no-undef
const hoge = Map;

これを解決するには、先程説明したenvプロパティにes6を設定すればいい。
ちなみにこの設定では構文解析も拡張してくれるので、parserOptionsは不要になる。

{
  "rules": {
    "no-undef": "error"
  },
  "env": {
    "es6": true
  }
}

但し、拡張するのはES2015までなので、最新の機能を使うにはparserOptionsが必要になる。
以下はオブジェクトの分割代入だが、パースできていない。

// error  Parsing error: Unexpected token ..
const { a, b, ...c } = { a: 1, b: 2, x: 3, y: 4, z: 5};

"ecmaVersion": 2018を指定する必要がある。

{
  "rules": {
    "no-undef": "error"
  },
  "env": {
    "es6": true
  },
  "parserOptions": {
    "ecmaVersion": 2018
  }
}

sourceType

ES2015+の構文のなかでもimport/exportだけは特別扱いであり、専用の設定が必要。
parserOptionsプロパティのなかで"sourceType": "module"を設定しないとパースできない。

"parserOptions": {
  "sourceType": "module"
}

parser

parserプロパティを使うことで、パーサーそのものを指定できる。
デフォルトではEspreeというパーサーが使われているが、これでは対応できない構文を使用する際に、他のパーサーを使う。

例えばFlow。

// error  Parsing error: Unexpected token :
function concat(a: string, b: string) {
  return a + b;
}

パース出来ずにエラーになる。

これを解消するには、パーサーにbabel-eslintを使用すればよい。

まずは使用したいパーサーのnpmパッケージをインストールする。

$ npm i -D babel-eslint

次に、.eslintrcでパーサーを指定する。

{
  "parser": "babel-eslint"
}

これで、Flowもパースできるようになる。
ただ、これは単にパースしているだけなので、Flowの文法についてESLintでチェックしたい場合は後述するプラグインという機能を使う必要がある。

ちなみに、babel-eslintはいろんな設定を自動的に付加してくれるようで、パーサーをbabel-eslintにするだけでMapimportなどを使えるようになる。

shareable configuration

shareable configurationという仕組みを使うことで、他人が書いた設定を導入できる。

extendesプロパティで指定できる。

まずは、ESLintが推奨している設定を導入してみる。

{
  "extends": "eslint:recommended"
}

個別のルールは何も設定していないが、いくつかのルールが有効になっている。
例えば、no-undefno-empty

// error  'foo' is not defined   no-undef
// error  Empty block statement  no-empty
if (foo) {
}

extendsしてきたルールの一部を上書きすることも可能。

{
  "extends": "eslint:recommended",
  "rules": {
    "no-undef": "off",
    "no-empty": "warn"
  }
}
// warning  Empty block statement  no-empty
if (foo) {
}

shareable configurationがnpmパッケージとして公開されていれば、自由にそれを導入できる。
例として、広く使われている設定であるeslint-config-airbnbを導入してみる。

github.com

先程のeslint:recommendedrulesを設定するだけだったが、eslint-config-airbnbではenvsourceTypeの設定も行ってくれるため、自分で行う設定がかなり少なくて済むようになる。

例えば以下のコードをESLintの対象にするためには、ここまで説明してきたように様々な設定が必要になる。
デフォルトではconstexportをパースできないし、Mapnot definedになる。

console.log(Map);
const { a, b, ...c } = {
  a: 1, b: 2, x: 3, y: 4, z: 5,
};
export default { a, b, c };

だがeslint-config-airbnbを使えば、ただそれを継承するだけで、上記のコードが動くようになる。

まずはインストール。
コマンドラインで以下を実行する。

(
  export PKG=eslint-config-airbnb;
  npm info "$PKG@latest" peerDependencies --json | command sed 's/[\{\},]//g ; s/: /@/g' | xargs npm install --save-dev "$PKG@latest"
)

次に.eslintrcを編集。

{
  "extends": "airbnb"
}

この設定だけで、ESLintが先程のコードを理解してくれるようになる。

plugins

プラグインは、ESLintのルールを追加する仕組み。
既存のルールをオン・オフするのではなく、独自のルールを追加できる。
ReactやVueのような特定のライブラリや環境のためのルールを追加するのが、主な目的。
先程少し触れたFlowのためのプラグインもある。

github.com

github.com

github.com

例として、eslint-plugin-reactを使ってみる。

まずはチェック対象であるJSファイルを作る。

import 'react';

function myComponent() {
  return (
    <div>hoge</div>
  );
};

そしてnpmパッケージをインストールした上で、.eslintrcを設定する。

$ npm i -D eslint-plugin-react
{
  "plugins": [
    "react"
  ],
  "extends": ["plugin:react/recommended"],
  "parserOptions": {
    "sourceType": "module"
  }
}

"extends": ["plugin:react/recommended"]とすることで、プラグインが推奨するルールが適用され、さらにパーサーの設定も行われるので、JSXがパースされるようになる。

$ npx eslint src/
  5:5  error  'React' must be in scope when using JSX  react/react-in-jsx-scope

自分で設定を追加したり上書きしたりも、もちろん出来る。
例えば以下のようにすると、react/react-in-jsx-scopeのエラーは消える。

{
  "plugins": [
    "react"
  ],
  "extends": ["plugin:react/recommended"],
  "parserOptions": {
    "sourceType": "module"
  },
  "rules": {
    "react/react-in-jsx-scope": "off"
  }
}

Vueの場合はeslint-plugin-vueをインストールした上で、次のようにすればいい。

{
  "plugins": [
    "vue"
  ],
  "extends": ["plugin:vue/essential"]
}

一点だけ注意すべきなのが、ESLintはデフォルトでは.js拡張子のみが対象であるということ。
そのため、.vueはそもそもESLintの対象にならない。
.js以外も対象にするためには--extオプションを使う。

.js.vueを対象にしたい場合は次のようにすればよい。

$ npx eslint --ext .js,.vue src/

参考資料