From 2e13bb60ba77a778782f820cb17a331e9519551a Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Tue, 8 Oct 2024 20:27:54 +1300 Subject: [PATCH] fix: add eslint-plugin-jsx-a11y --- knip.jsonc | 1 + package.json | 5 +++ pnpm-lock.yaml | 3 ++ src/configs/react.ts | 80 ++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 87 insertions(+), 2 deletions(-) diff --git a/knip.jsonc b/knip.jsonc index cac78c5f..f42a6c5b 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -23,6 +23,7 @@ "eslint-plugin-import-x", "eslint-plugin-jsdoc", "eslint-plugin-jsonc", + "eslint-plugin-jsx-a11y", "eslint-plugin-markdown", "eslint-plugin-n", "eslint-plugin-no-only-tests", diff --git a/package.json b/package.json index 697f5b2d..41f08ccf 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "eslint-plugin-import-x": "4.3.1", "eslint-plugin-jsdoc": "50.3.1", "eslint-plugin-jsonc": "2.16.0", + "eslint-plugin-jsx-a11y": "6.10.0", "eslint-plugin-markdown": "5.1.0", "eslint-plugin-n": "17.10.3", "eslint-plugin-no-only-tests": "3.3.0", @@ -158,6 +159,7 @@ "eslint-plugin-import-x": "*", "eslint-plugin-jsdoc": "*", "eslint-plugin-jsonc": "*", + "eslint-plugin-jsx-a11y": "*", "eslint-plugin-markdown": "*", "eslint-plugin-n": "*", "eslint-plugin-no-only-tests": "*", @@ -238,6 +240,9 @@ "eslint-plugin-jsonc": { "optional": true }, + "eslint-plugin-jsx-a11y": { + "optional": true + }, "eslint-plugin-markdown": { "optional": true }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9caf7c7..bd5f085d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -138,6 +138,9 @@ importers: eslint-plugin-jsonc: specifier: 2.16.0 version: 2.16.0(eslint@9.12.0(jiti@2.3.3)) + eslint-plugin-jsx-a11y: + specifier: 6.10.0 + version: 6.10.0(eslint@9.12.0(jiti@2.3.3)) eslint-plugin-markdown: specifier: 5.1.0 version: 5.1.0(eslint@9.12.0(jiti@2.3.3)) diff --git a/src/configs/react.ts b/src/configs/react.ts index 918e5260..d030c9f5 100644 --- a/src/configs/react.ts +++ b/src/configs/react.ts @@ -24,11 +24,12 @@ export async function react( ): Promise { const { files, i18n, overrides, typescript, parserOptions } = options; - const [pluginReact, pluginReactHooks, pluginReactRefresh] = (await loadPackages([ + const [pluginReact, pluginReactHooks, pluginReactRefresh, pluginJsxA11y] = (await loadPackages([ "@eslint-react/eslint-plugin", "eslint-plugin-react-hooks", "eslint-plugin-react-refresh", - ])) as [ESLint.Plugin, ESLint.Plugin, ESLint.Plugin]; + "eslint-plugin-jsx-a11y", + ])) as [ESLint.Plugin, ESLint.Plugin, ESLint.Plugin, ESLint.Plugin]; const parserTs = typescript ? await interopDefault(import("@typescript-eslint/parser")) : undefined; @@ -53,6 +54,7 @@ export async function react( plugins["@eslint-react/naming-convention"] ?? assert.fail(`Failed to find "@eslint-react/naming-convention".`), "react-refresh": pluginReactRefresh, + "jsx-a11y": pluginJsxA11y, }, }, { @@ -138,6 +140,80 @@ export async function react( "react/prefer-shorthand-boolean": "error", "react/prefer-shorthand-fragment": "error", + "jsx-a11y/alt-text": "error", + "jsx-a11y/anchor-has-content": "error", + "jsx-a11y/anchor-is-valid": "error", + "jsx-a11y/aria-activedescendant-has-tabindex": "error", + "jsx-a11y/aria-props": "error", + "jsx-a11y/aria-proptypes": "error", + "jsx-a11y/aria-role": "error", + "jsx-a11y/aria-unsupported-elements": "error", + "jsx-a11y/autocomplete-valid": "error", + "jsx-a11y/click-events-have-key-events": "error", + "jsx-a11y/control-has-associated-label": [ + "off", + { + ignoreElements: ["audio", "canvas", "embed", "input", "textarea", "tr", "video"], + ignoreRoles: [ + "grid", + "listbox", + "menu", + "menubar", + "radiogroup", + "row", + "tablist", + "toolbar", + "tree", + "treegrid", + ], + includeRoles: ["alert", "dialog"], + }, + ], + "jsx-a11y/heading-has-content": "error", + "jsx-a11y/html-has-lang": "error", + "jsx-a11y/iframe-has-title": "error", + "jsx-a11y/img-redundant-alt": "error", + "jsx-a11y/interactive-supports-focus": [ + "error", + { + tabbable: [ + "button", + "checkbox", + "link", + "progressbar", + "searchbox", + "slider", + "spinbutton", + "switch", + "textbox", + ], + }, + ], + "jsx-a11y/label-has-for": "off", + "jsx-a11y/label-has-associated-control": "error", + "jsx-a11y/media-has-caption": "error", + "jsx-a11y/mouse-events-have-key-events": "error", + "jsx-a11y/no-access-key": "error", + "jsx-a11y/no-autofocus": "error", + "jsx-a11y/no-distracting-elements": "error", + "jsx-a11y/no-interactive-element-to-noninteractive-role": "error", + "jsx-a11y/no-noninteractive-element-interactions": [ + "error", + { + body: ["onError", "onLoad"], + iframe: ["onError", "onLoad"], + img: ["onError", "onLoad"], + }, + ], + "jsx-a11y/no-noninteractive-element-to-interactive-role": "error", + "jsx-a11y/no-noninteractive-tabindex": "error", + "jsx-a11y/no-redundant-roles": "error", + "jsx-a11y/no-static-element-interactions": "error", + "jsx-a11y/role-has-required-aria-props": "error", + "jsx-a11y/role-supports-aria-props": "error", + "jsx-a11y/scope": "error", + "jsx-a11y/tabindex-no-positive": "error", + ...(typescript ? { "react/no-leaked-conditional-rendering": "error",