ESLint の設定から逃げない
はじめに
.eslintrc
ファイルを触る or 見ることはあるけど、内容はあまり理解できていない方も多いのではないでしょうか。
そこで React Native 公式の ESLint 設定である eslint-config-react-native を見ながら ESLint の設定ファイルを読めるようになろう、というのがこの記事の趣旨です。この記事を読むにあたり、React Native 特有の知識は必要ありません。
対象読者は ESLint の設定ファイルを触ったことはあるけど内容を理解できていない方です。入門記事ではないため、ESLint に全く触れたことがない方は、 Getting Started を読んでからこの記事をお読みいただくとより理解しやすいかもしれません。
とは言っても、この記事で扱う内容は決して高度なものではないため、入門者の方がお読みいただいても理解できる箇所は多いと思います。
はじめに、今回見ていく eslint-config-react-native
の内容を記載します。こちらが既に理解できる方はこの記事を読む必要はありません。
使いたい方向け
# prettier は内部で使用しているため必須
npm install -D eslint prettier @react-native/eslint-config
{
"extends": "@react-native"
}
この記事では eslint-config-react-native
の設定ファイルを上から項目順に見ていきます。そのため、説明の順序が分かりづらい箇所もあるかもしれませんが、ご容赦ください。
env
env: {
es6: true,
},
env
で指定した環境のグローバル変数を使用できるようになります。厳密には、ESLint はリンターでありランタイムには関与しないため、 env
を設定しなくてもグローバル変数を使用することはできます。しかし、未定義変数として ESLint がエラーを出すため、それを防ぐために設定します。es6
や es2023
など JavaScript のバージョン以外でよく使われるのは browser
、node
、jest
、ブラウザ拡張機能を開発している場合は webextensions
あたりです。ESLint のデフォルトは ES5 になっています。複数個設定することも可能です。
env: {
es6: true,
node: true,
jest: true,
},
例えば webextensions: true
にした場合、どのようなグローバル変数が使用できるか確認したい場合は globals パッケージの globals.json
を参照します。browser
、chrome
、opr
が追加されることがわかります。false
は globals
のところでも確認しますが、書き換え不可という意味です。
ちなみにこの globals.json
を ESLint のどこから使用しているかというとこのあたりです。
parserOptions
parserOptions: {
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
名前の通りパーサーのオプションを設定します。ESLint は JavaScript のコードそのまま評価しているわけではなく、一度 AST (抽象構文木) に変換してから評価します。その変換する役割を担っているのがパーサーです。ESLint はデフォルトのパーサーとして Espree を使用します。
ESLint と互換性のある場合は、他のパーサーを使用することも可能です。よく使われるのは @babel/eslint-parser や、TypeScript を ESLint が解釈できるようにする @typescript-eslint/parser などです。
パーサーを設定する場合はこのように指定します。eslint-config-react-native
の場合は、ファイル拡張子によって使用するパーサーを切り替えているため、のちに解説する overrides
の中で指定しています。
parser: '@typescript-eslint/parser',
オプションに設定されている項目を見ていきます。
sourceType
はデフォルトで script
に設定されており、ES Modules を使用する場合は module
にする必要があります。
また、React を使用する場合には ecmaFeatures.jsx
の設定が必要です。
ecmaVersion
が設定されている例もよく見ますが、env
で es6: true
にすると、parserOptions
で自動的に ecmaVersion: 6
が設定されるため、省略することができます。es2023
なども同様です。
反対に ecmaVersion: 6
を設定しても es6: true
が自動的に設定されるわけではありません。
ecmaVersion: 6
は ES6 構文をサポートすること、es6: true
は ES6 のグローバル変数を ESLint が解釈できるようにすることが役割です。
「グローバル変数を追加 => 構文をサポート」であり、その逆は成り立たないと勝手に解釈しています。
extends
extends: ['plugin:prettier/recommended'],
語弊を恐れずに言えば、extends
は 継承 です。公式サイトにもこのような説明があります。
別のコンフィギュレーションファイルの すべての特徴(ルール、プラグイン、言語オプションを含む)を継承し、すべてのオプションを変更することができます。
つまり継承元の設定でプラグインが追加されている場合は、自分で追加しなくとも、そのプラグインは使用できるようになります。
継承元の設定は、プラグイン (eslint-plugin-...
) の一部として提供されたり、設定単体 (eslint-config-...
) で提供されたりします。プラグインの場合は、 extends
に指定する際に plugin:
が必要です。eslint-plugin-
や eslint-config-
は省略することが可能です。
つまり、今回の設定は、「プラグインである eslint-plugin-prettier
の recommended
設定を継承するよ」という意味になります。
具体的にはこのような設定が継承されます。
extends
した先でさらに extends
していますね。設定内容を詳しく見ていきます。
extends
と plugins
に prettier
が設定されていますが、extends
は eslint-config-prettier
、plugins
は eslint-plugin-prettier
を指します。それぞれの役割は以下です。
-
eslint-config-prettier
: Prettier と競合する ESLint のルールを無効化 -
eslint-plugin-prettier
: Prettier を ESLint から実行できるようにする
eslint-config-prettier
の中身を見てみると、ひたすらスタイルに関わる ESLint のルールを無効化しています。
下記のように extends
に複数設定している場合、後に書いた設定のルールが優先されます。そのため prettier
は最後に書く必要があることに注意しましょう。
extends: [
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'prettier'
],
また、rules
の prettier/prettier
は eslint-plugin-prettier
の prettier
というルールを指しています。(prettier
が多くてややこしいですね...)
既に extends
についての有益な記事がありますので、さらに詳しく知りたい方はこちらをご参照ください。
plugins
plugins: [
'eslint-comments',
'react',
'react-hooks',
'react-native',
'@react-native',
'jest',
],
使用するプラグインを配列で指定します。extends
同様、 eslint-plugin-
を省略することが可能です。例えば react
は eslint-plugin-react
、jest
は eslint-plugin-jest
、@react-native
は @react-native/eslint-plugin
のことを指します。もちろん省略しないで書くことも可能です。
プラグイン名のルールはこちら
- eslint-plugin-<plugin-name>
- @<scope>/eslint-plugin-<plugin-name>
- @<scope>/eslint-plugin
プラグインにも様々な種類があります (カスタムルールのみでないことに注意してください)
- カスタムルール:
rules
で使用 - カスタム設定:
extends
で使用 - カスタム環境:
env
で使用 - カスタムプロセッサ:
processor
で使用 ( 本記事では出てきません )
settings
ESLint は設定ファイルへの共有設定の追加をサポートしています。プラグインは settings を使用して、すべてのルールで共有される情報を指定します。ESLint の設定ファイルに settings オブジェクトを追加すると、実行されるすべてのルールにそのオブジェクトが提供されます。これはカスタムルールを追加する際に、同じ情報にアクセスして簡単に設定できるようにしたい場合に便利です。
settings: {
react: {
version: 'detect',
},
},
settings
で設定したものはルール本体のコードからアクセスできます。
eslint-plugin-react
を使用する場合 react.version
を設定する必要があります。detect
にするとインストールされている React のバージョンが自動で適用されます。
settings.react.version
が使用されている箇所はこのあたりです。
React のバージョンによってルールの無効化や、検査内容を変えたい場合などに使用されています。
// lib/util/version.js
const semver = require('semver');
function test(semverRange, confVer) {
return semver.satisfies(confVer, semverRange);
}
function testReactVersion(context, semverRange) {
return test(semverRange, getReactVersionFromContext(context));
}
// lib/rules/no-unsafe.js
module.exports = {
meta: {},
// rule 本体
// context に settings オブジェクトの情報が入っています
create(context) {
....
const isApplicable = testReactVersion(context, '>= 16.3.0');
if (!isApplicable) {
return {};
}
....
},
};
overrides
同じディレクトリ内のファイルに対する構成が異なる必要がある場合など、より細かい制御が必要な場合があります。この場合、overrides キーの下に、特定のグロブ・パターンに一致するファイルにのみ適用される構成を、コマンド・ラインで渡すのと同じ形式(例えば、app/*/.test.js )で指定できます。
overrides
では特定ファイルのみに適用される設定を書くことができます。
overrides: [
{
files: ['*.js'],
parser: '@babel/eslint-parser',
plugins: ['ft-flow'],
rules: {
// Flow Plugin
// The following rules are made available via `eslint-plugin-ft-flow`
'ft-flow/define-flow-type': 1,
'ft-flow/use-flow-type': 1,
},
},
{
files: ['*.jsx'],
parser: '@babel/eslint-parser',
},
];
これはプロジェクト内の .js
、.jsx
ファイルのみに適用される設定です。パーサーの指定とプラグインである eslint-plugin-ft-flow
を追加し Flow のルールを利用できるようにしています。rules
の記述方法については後に説明します。
overrides: [
{
files: ['*.ts', '*.tsx'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint/eslint-plugin'],
rules: {
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
},
],
'no-unused-vars': 'off',
'no-shadow': 'off',
'@typescript-eslint/no-shadow': 1,
'no-undef': 'off',
'func-call-spacing': 'off',
'@typescript-eslint/func-call-spacing': 1,
},
},
];
こちらは TypeScript のコードを ESLint が解釈できるようにする設定をしています。
rules
では、typescript-eslint
プラグインが提供する @typescript-eslint/no-unused-vars
などのルールを有効化しています。これらのルールは、TypeScript の型情報を考慮して未使用の変数を検出してくれます。
同時に、no-unused-vars
という ESLint 組み込みのルールは、重複したエラーを防ぐために無効化しています。このように設定することで、TypeScript コードの静的解析をより正確かつ効果的に行うことができます。
typescript-eslint
については充実した公式ドキュメントがありますので、詳細はそちらをご参照ください。
overrides: [
{
files: [
'*.{spec,test}.{js,ts,tsx}',
'**/__{mocks,tests}__/**/*.{js,ts,tsx}',
],
env: {
jest: true,
'jest/globals': true,
},
rules: {
'react-native/no-inline-styles': 0,
quotes: [1, 'single', { avoidEscape: true, allowTemplateLiterals: true }],
},
},
];
この設定については具体的に下記のようなファイルに適用されます。
・example.spec.js
・example.test.ts
・example.spec.tsx
・src/__tests__/util.test.js
・src/__mocks__/api.ts
env
で jest: true
とすることで、afterEach
や it
などのグローバル変数を ESLint が解釈できるようになります。
jest/global: true
は eslint-plugin-jest のカスタム環境を設定しています。ただ、設定ファイルを見る限り jest/global
は指定しなくても問題なさそうです。
globals
ESLint のコアルールのいくつかは、実行時にコードが利用できるグローバル変数の知識に依存しています。グローバル変数は実行時に変更されるだけでなく、異なる環境間で大きく変化する可能性があるため、ESLint はあなたの実行環境にどのようなグローバル変数が存在するかについて仮定しません。どのようなグローバル変数が利用可能かについての知識を必要とするルールを使用したい場合は、設定ファイルでグローバル変数を定義するか、ソースコードで設定コメントを使用してください。
ESLint に解釈してもらいたいグローバル変数は env
以外に、globals
で手動追加することができます。
globals: {
__DEV__: true,
__dirname: false,
__fbBatchedBridgeConfig: false,
AbortController: false,
Blob: true,
alert: false,
cancelAnimationFrame: false,
cancelIdleCallback: false,
clearImmediate: true,
clearInterval: false,
clearTimeout: false,
console: false,
document: false,
ErrorUtils: false,
escape: false,
Event: false,
EventTarget: false,
exports: false,
fetch: false,
File: true,
FileReader: false,
FormData: false,
global: false,
Headers: false,
Intl: false,
Map: true,
module: false,
navigator: false,
process: false,
Promise: true,
requestAnimationFrame: true,
requestIdleCallback: true,
require: false,
Set: true,
setImmediate: true,
setInterval: false,
setTimeout: false,
queueMicrotask: true,
URL: false,
URLSearchParams: false,
WebSocket: true,
window: false,
XMLHttpRequest: false,
}
変数の上書きを許可する場合は true
、上書きを禁止する場合は false
を設定します。
React Native の設定ファイルは boolean 値で設定していますが、現在は writable
と readonly
を使用する書き方が推奨されています。false
は readonly
、true
は writable
と等価です。
{
"globals": {
"var1": "writable",
"var2": "readonly"
}
}
また、env
は内部で globals
を設定しています。例えば env
で es2023: true
とした場合は、ES2023 までで使用できるグローバル変数が globals
に設定されます。
env
で browser: true
や node: true
とした場合、手動で setTimeout
などを globals
に追加する必要はありません。しかし、React Native の実行環境はブラウザや Node.js とは異なるため、browser: true
としてしまうと使用できないグローバル変数までもが globals
に追加されてしまいます。reactnative: true
のような設定は現状存在しないため手動で追加する形になっていいます。
rules
// 一部省略
rules: {
// General
'comma-dangle': [1, 'always-multiline'], // allow or disallow trailing commas
'no-cond-assign': 1, // disallow assignment in conditional expressions
'no-console': 0, // disallow use of console (off by default in the node environment)
...
// ESLint Comments Plugin
// The following rules are made available via `eslint-plugin-eslint-comments`
'eslint-comments/no-aggregating-enable': 1, // disallows eslint-enable comments for multiple eslint-disable comments
'eslint-comments/no-unlimited-disable': 1,
...
// React Plugin
// The following rules are made available via `eslint-plugin-react`.
'react/display-name': 0,
'react/jsx-boolean-value': 0,
}
ルールの重大度の種類は 3 種類あります。
- 0 or off: ルールをオフにする
- 1 or warn: ワーニング を出す
- 2 or error: エラー を出す
設定方法は、ルールの名前をキーにして、値に重大度を設定します。プラグインのルールを使用する場合は、plugin-name/rule-name
という形式で指定します。plugin-name
は eslint-plugin-
を省略した名前です。
ルールのオプションを設定したい場合は [1, 'always-multiline']
のように配列で指定します。
まとめ
この記事を通して ESLint 設定ファイルへの苦手意識が少しでも減っていただけたら嬉しいです。
今回は React Native の設定を見てきましたが、ここまで理解できれば Next.js の設定である、eslint-config-next
なども問題なく読めると思います。
また、ルールの自作にも挑戦してみるとさらに理解が深まるでしょう。(筆者も挑戦中です)
最後までお読みいただきありがとうございました 🙇♂️
Discussion