importに関連するOxlintのルール設定を見直そう
これ何
このブログではLinterとしてESLintとOxlintを併用している。
ESLintの扱いにはもう慣れっこだが(大嘘)、Oxlintはまだまだ雰囲気で使っている。
その結果import周りのルール設定が競合してぐちゃぐちゃになり全部offにするという事態になっている。
なので一度見直そうと思う。
事前知識の整理 - ルールに関連する3つの概念
Oxlintのルールを見直す前に、ルールに関する3つの概念を正しく整理しておく。
- Category
- Plugin
- Automatic Fixes
Category
Oxlintのルールについて詳細にみていく前に、OxlintにおけるCategoryという概念をきちんと理解しておく。
Oxlintの1つ1つのルールはそれぞれ一つのカテゴリー(Category)に属している。
ではどういった基準でカテゴリー分けされているのかというと、それはそれぞれのルールの目的及び振る舞いと関係がある。
| 名称 | 説明 | 代表的なルール |
|---|---|---|
correctness | 明らかに間違っている、あるいは無駄なコードを検出する | eslint/no-debugger, jsx_a11y/alt-text |
suspicious | 間違っている、あるいは無駄な「可能性がある」コードを検出する | typescript/no-unnecessary-type-arguments, react/style-prop-object |
pedantic | 厳格だけれどもユーザーやプロジェクトによっては厳しすぎるかもしれない | eslint/eqeqeq, unicorn/prefer-date-now |
perf | コードのパフォーマンスを向上させる目的がある | eslint/no-await-in-loop, react/no-array-index-key |
style | 一貫したスタイルを維持したり、慣用的な構文を強制したりするのに役立つ | eslint/arrow-body-style, typescript/no-empty-interface |
restriction | 特定のパターン、構文、機能の使用を一旦禁止させ、ケースバイケースで有効化させる | eslint/no-alert, typescript/no-require-imports |
nursery | 開発中だったり、大幅に変更される可能性があったり、厳しすぎるかもしれない | eslint/no-unreachable |
Oxlintはcategory単位でルールに反するコードに対するアクションを許容(Allow, -A)/警告(Warn, -W)/拒否(Deny, -D)の中から指定できる。
以下の例では、correctness及びpedanticなルールに反するコードを全て拒否している。
# Enable all correctness and suspicious rules
oxlint -D correctness -D pedantic
Plugin
OxlintはOxlint単体でESLintの様々なPluginの機能をサポートしている。
ただし唯一oxcだけはちょっと毛色が異なっており、これはOxlintをメンテナンスしているoxc固有のルールとdeepscanというツールから移植されたものが含まれている。
| 名称 | 対応するESLintのPlugin名 |
|---|---|
typescript | typescript-eslint |
unicorn | eslint-plugin-unicorn |
react | eslint-plugin-react, eslint-plugin-react-hooks |
react-perf | eslint-plugin-react-perf |
nextjs | eslint-plugin-next |
oxc | N/A |
import | eslint-plugin-import |
jsdoc | eslint-plugin-jsdoc |
jsx-a11y | eslint-plugin-jsx-a11y |
node | eslint-plugin-node |
promise | eslint-plugin-promise |
jest | eslint-plugin-jest |
vitest | @vitest/eslint-plugin |
CategoryとPluginの関係
Oxlintの各ルールは先述のPluginのいずれかに由来するものであり、その挙動に応じてそれぞれ1つのCategoryというラベルが付与されている、と考えるのがわかりやすいかも。
例としてjsx_a11y/alt-textは以下のように考えることができる。
jsx_a11y/alt-textはeslint-plugin-jsx-a11y由来のルールである<img>要素にalt属性で適切な文字列を設定することはアクセシビリティ上必須である。
逆に言えば<img>要素に適切なalt属性が付与されていないのは間違っているので、jsx_a11y/alt-textはcorrectnessに分類される
Automatic Fixes
Oxlintのルールの一部はautofixが可能だが、このautofixの挙動にも2種類ある。
| 名称 | 説明 | CLI実行時のオプション |
|---|---|---|
| Fixes | コードの挙動に影響を与えず安全である | --fix |
| Suggestions | コードの挙動を変えたり、意図しない変更を加える可能性がある | --fix-suggestions |
また、FixesとSuggestionsなautofixの中には更に危険(Dangerous)なものがあり、--fix-dangerouslyオプションで有効化できる。
つまり、--fix/--fix-suggestions/--fix-dangerouslyオプションの挙動は以下のようにまとめられる。
| オプション | Fixesなautofix | Suggestionsなautofix | Dangerousなautofix |
|---|---|---|---|
--fix | する | しない | しない |
--fix-dangerously | する | しない | する |
--fix-suggestions --fix-dangerously | しない | する | する |
--fix --fix-suggestions --fix-dangerously | する | する | する |
import周りのルールの整理
ここでやっと本題に入る。
今自分が頭を抱えているのは以下のルール。
import/exports-lastimport/group-exportsimport/prefer-default-exportimport/no-duplicatessort-importsno-duplicate-imports
import/exports-last (category: Style)
このルールはモジュールのexport宣言をファイルの末尾に記述することを強制する。
exportは変数・モジュール宣言と一緒に書いてあって欲しい派閥なのでこのルールは無効化する。
import/group-exports (category: Style)
このルールはexport宣言は1箇所にまとめてグループ化しろ、ということを強制する。
例えばこれはNGで
export const first = true;
export const second = true;
これはOKになる。
const first = true;
const second = true;
export { first, second };
exportは変数・モジュール宣言と一緒に書いてあって欲しい派閥なのでこのルールは無効化する(2回目)。
import/prefer-default-export (category: Style)
export宣言が1箇所しかない時にnamed exportよりもdefault exportの使用を優先することを強制するルール。
これちょっと邪魔だな…無効化する。
import/no-duplicates (category: Style)
同じモジュールから複数回importすることを禁止するルール。
これの是非については後述する。
sort-imports (category: Style)
import宣言のソート順を一定のルールになるように強制するルール。
ESLintのsort-importsも参考になる。
例えば、自分のソースコードではこのようになっているimport宣言があるのだが、
import { css } from '../../../styled-system/css';
import type { JSX } from 'solid-js/jsx-runtime';
import type { ComponentProps } from 'solid-js';
import { splitProps } from 'solid-js';
こう直せば良い。型のimport宣言を先に書くことが要注意ポイント。
import type { ComponentProps } from 'solid-js';
import type { JSX } from 'solid-js/jsx-runtime';
import { css } from '../../../styled-system/css';
import { splitProps } from 'solid-js';
これは全然あって良いルールなので有効化する。
no-duplicate-imports (category: Style)
同じモジュールから複数回importすることを禁止するルール。
import/no-duplicatesとは何が違うかというと、以下のようなimport宣言があった時、
import type { ComponentProps, JSX } from 'solid-js';
import { css } from '../../../styled-system/css';
import { splitProps } from 'solid-js';
import/no-duplicatesはこれを許容する一方でno-duplicate-importsは拒否する。
no-duplicate-importsは以下のようにモジュールと型のimportはまとめることを強制する。
import { type ComponentProps, type JSX, splitProps } from 'solid-js';
ESLintのno-duplicate-importsも参考になる。
これの是非については後述する。
import/no-duplicates vs no-import-duplicates
ただこれをやると、今度はimport/consistent-type-specifier-styleと競合する。
import/consistent-type-secifier-styleでは以下のように型とモジュールのimportは分けて記述することを強制する。
import type { ComponentProps, JSX } from 'solid-js';
import { splitProps } from 'solid-js';
つまり、import/no-duplicatesとno-duplicate-importsのどちらを採用するかによってOxlintの設定ファイルの修正内容がちょっと変わる。
import/no-duplicatesを採用する場合、no-duplicate-importsはoffにして無効化するno-duplicate-importsを採用する場合、import/no-duplicatesとimport/consistent-type-specifier-styleをoffにして無効化する
型のimportはモジュールのimportとは分けたい派なので、import/no-duplicates側を採用することにする。
まとめ
今回言及したものだけ取り上げてoxlintrc.jsonを整理するとこうなった。
{
"plugins": ["import"],
"overrides": {
"files": ["src/**/*.ts", "src/**/*.tsx"],
"rules": {
"import/exports-last": "off",
"import/group-exports": "off",
"import/prefer-default-export": "off",
"no-duplicate-imports": "off"
}
}
}