eslint-plugin-rn-a11y というプラグインを作ってるので話
OthersESLint Plugin 作ってますという無限にあるエントリの 1 つ。
https://github.com/grgr-dkrk/eslint-plugin-rn-a11y/
これは何
React Native のアクセシビリティチェックプラグイン。
実は React Native でアクセシビリティをチェックするプラグインは eslint-plugin-react-native-a11y としてすでにあるけど、ルールのほとんどが TypeScript の型でカバー可能、対応バージョンが古く、さらにメンテがあまりされていないので、fork 的な形で作った。
一部ルールはそちらのプラグインから移植している。それらのルールの権利はそちらに帰する。
何をするの
まだできることは少ない。
- deprecated な props の使用禁止(types の更新が最近まで忘れ去られていたものなので、一時的に追加)
- touchable なコンポーネントをアクセシブルにするためのチェック
- 画像に代替テキストを設定するための提案
- accessibilityLabel や accessibilityHint の valid
- E2E のテスト ID 代わりに accessibilityLabel を設定していないかのチェック
- アクセシブルでなくなる props の使用禁止(本当は RN 側で修正したい)
その他実験的なルールの実装を進めてる。
簡易ボタンサイズ・コントラストチェッカーなど。
特徴
React の eslint-plugin-jsx-a11y は HoC の対応に弱い。
一部ルールにしか対応していない。styled-component が読めないなどをよく聞く。
このプラグインでは .eslintrc.js
のオプションとして、汎用的に設定できるようカバー。
具体的には以下コードのように、各ルールにコンポーネント名の配列を渡すようにできる。
eslintrc.js
// eslintrc.js
const Image = ['MyPict']
const Touchable = ['MyTouchableOpacity', 'MyPressable']
const CustomComponents = {
Image,
Touchable,
}
module.exports = {
/* ... */
rules: {
'rn-a11y/no-nested-touchables': ['error', CustomComponents],
'rn-a11y/touchable-has-alt': ['error', CustomComponents],
'rn-a11y/no-long-alt': ['error', { ...CustomComponents, limit: 100 }],
},
}
ユースケース的に、コンポーネント名を列挙するスクリプトを書く、という信頼性に欠けるものではある。
あと React と違って使うシーンは限られる…。
実装時の話
ただの宣伝になってはいけないので、実装時に詰まったことを書き残す。
型定義
このライブラリは TypeScript で実装している。
既存の JSX 用 ESLint プラグインは JavaScript での実装なので気にならなかったが、TypeScript ではかなり引っかかる。
というのも ESLint の Visitor 部分や jsx-ast-utils
が対応する型定義を持ってないため。
ESLint はオリジナルの型を extends すればいいけど、 jsx-ast-utils
はそもそも型定義がないので作った。
JSX 向けに ast-types-flow
という Flow 用の型定義があるので、これをローカル側に持って jsx-ast-utils
の宣言にマージしている。
ただ、普通にローカルの型定義を import することはできず、型宣言が無効になってしまう。
inline import で対処してなんとかなった。
jsx-ast-utils.d.ts
export function elementType(
// これ
node: import('../ast').JSXElement,
): string
これに対する要望的な Issue が TypeScript 辺りで見た気がするものの失念したので省略。
TypeScript の parser
テストは Jest と、ESLint の RuleTester
インスタンスを使う。
今回は TypeScript に対してやるので、さらに @typescript-eslint/parser
が必須になる。
ところが以下は NG。
myRule.test.ts
const ruleTester = new RuleTester({
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
// ここ。NG
parser: '@typescript-eslint/parser',
})
ESLint のバージョン 6 で破壊的変更があり、parser
オプションに絶対パスを使用しなければならなくなった。
Migrating to v6.0.0 - ESLint
そのため parser
に require.resolve()
を使うことで、ようやくパースできる。
myRule.test.ts
const ruleTester = new RuleTester({
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
// ここ
parser: require.resolve('@typescript-eslint/parser'),
})
Option 使用時のエラー
schema
でオプションの type を設定しておかないと、オプションを指定しても ESLint 側でエラーになる。
これを完全に失念していて詰まっていた。
myRule.ts
export const rule: Rule.RuleModule = {
meta: {
type: 'problem',
docs: {
description: 'Disallow using deprecated props.',
},
// これ
schema: {
type: 'object',
items: { type: 'array' },
uniqueItems: true,
},
},
// 省略
ちなみに RuleTester
では options を用いた時のテストもできる。便利。
myRule.test.ts
valid: [
{
code: `<Image accessible accessibilityLabel="${'a'.repeat(
130,
)}" source={require('test.jpg')} />`,
options: [
{
limit: 130,
},
],
},
]
Publish
eslint-plugin-react-native-a11y
という名前はすでにあるので、 eslint-plugin-rn-a11y
という胡散臭い名前にしている。
これが胡散臭すぎたのか npm のスパムチェックにひっかかってしまった。
指示に応じてメールを送ると、メールじゃなくてサポートに送ってねという返信が来た。
詳細は書いてなかったけど年末だったから?とりあえずサポートページで問い合わせた。
- スパム扱いされた
- このライブラリは eslint-plugin-react-native-a11y の拡張でありコピー品ではない
ことを伝えてようやく解除された。
オチ
ありません