diff --git a/.eslintrc b/.eslintrc
index 7024a688..67859870 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -7,6 +7,7 @@
"ignorePatterns": [
"lib/",
"reports/",
+ "examples/",
],
"parser": "@babel/eslint-parser",
"plugins": [
diff --git a/.github/workflows/node-pretest.yml b/.github/workflows/node-pretest.yml
index e501df6b..3a10687f 100644
--- a/.github/workflows/node-pretest.yml
+++ b/.github/workflows/node-pretest.yml
@@ -38,3 +38,15 @@ jobs:
node-version: 'lts/*'
skip-ls-check: true
- run: npm run posttest
+
+ examples:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - uses: ljharb/actions/node/install@main
+ name: 'nvm install lts/* && npm install'
+ with:
+ node-version: 'lts/*'
+ skip-ls-check: true
+ - run: npm run test:examples
\ No newline at end of file
diff --git a/README.md b/README.md
index 02f1ed5c..5f66a744 100644
--- a/README.md
+++ b/README.md
@@ -60,7 +60,8 @@ yarn add eslint-plugin-jsx-a11y --dev
**Note:** If you installed ESLint globally (using the `-g` flag in npm, or the `global` prefix in yarn) then you must also install `eslint-plugin-jsx-a11y` globally.
-## Usage
+
+## Usage - Legacy Config (`.eslintrc`)
Add `jsx-a11y` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix:
@@ -109,6 +110,94 @@ Add `plugin:jsx-a11y/recommended` or `plugin:jsx-a11y/strict` in `extends`:
}
```
+## Usage - Flat Config (`eslint.config.js`)
+
+The default export of `eslint-plugin-jsx-a11y` is a plugin object.
+
+```js
+const jsxA11y = require('eslint-plugin-jsx-a11y');
+
+module.exports = [
+ …
+ {
+ files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
+ plugins: {
+ 'jsx-a11y': jsxA11y,
+ },
+ languageOptions: {
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ rules: {
+ // ... any rules you want
+ 'jsx-a11y/alt-text': 'error',
+ },
+ // ... others are omitted for brevity
+ },
+ …
+];
+```
+
+### Shareable Configs
+
+There are two shareable configs, provided by the plugin.
+
+- `flatConfigs.recommended`
+- `flatConfigs.strict`
+
+#### CJS
+
+```js
+const jsxA11y = require('eslint-plugin-jsx-a11y');
+
+export default [
+ jsxA11y.flatConfigs.recommended,
+ {
+ // Your additional configs and overrides
+ },
+];
+```
+
+#### ESM
+
+```js
+import jsxA11y from 'eslint-plugin-jsx-a11y';
+
+export default [
+ jsxA11y.flatConfigs.recommended,
+ {
+ // Your additional configs and overrides
+ },
+];
+```
+
+**Note**: Our shareable config do configure `files` or [`languageOptions.globals`](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new#configuration-objects).
+For most of the cases, you probably want to configure some of these properties yourself.
+
+```js
+const jsxA11yRecommended = require('eslint-plugin-jsx-a11y');
+const globals = require('globals');
+
+module.exports = [
+ …
+ {
+ files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
+ ...jsxA11y.flatConfigs.recommended,
+ languageOptions: {
+ ...jsxA11y.flatConfigs.recommended.languageOptions,
+ globals: {
+ ...globals.serviceworker,
+ ...globals.browser,
+ },
+ },
+ },
+ …
+];
+```
+
#### Component Mapping
To enable your custom components to be checked as DOM elements, you can set global settings in your configuration file by mapping each custom component name to a DOM element type.
@@ -124,7 +213,7 @@ For example, if you set the `polymorphicPropName` setting to `as` then this elem
will be evaluated as an `h3`. If no `polymorphicPropName` is set, then the component will be evaluated as `Box`.
-⚠️ Polymorphic components can make code harder to maintain; please use this feature with caution.
+⚠️ Polymorphic components can make code harder to maintain; please use this feature with caution.
## Supported Rules
diff --git a/examples/flat-cjs/eslint.config.cjs b/examples/flat-cjs/eslint.config.cjs
new file mode 100644
index 00000000..fb9c0715
--- /dev/null
+++ b/examples/flat-cjs/eslint.config.cjs
@@ -0,0 +1,22 @@
+const globals = require('globals');
+const js = require('@eslint/js');
+const jsxA11y = require('eslint-plugin-jsx-a11y');
+
+module.exports = [
+ js.configs.recommended,
+ jsxA11y.flatConfigs.recommended,
+ {
+ files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
+ languageOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ globals: globals.browser,
+ },
+ ignores: ['dist', 'eslint.config.cjs'],
+ rules: {
+ 'no-unused-vars': 'off',
+ 'jsx-a11y/anchor-ambiguous-text': 'warn',
+ 'jsx-a11y/anchor-is-valid': 'warn',
+ },
+ },
+];
diff --git a/examples/flat-cjs/index.html b/examples/flat-cjs/index.html
new file mode 100644
index 00000000..0c589ecc
--- /dev/null
+++ b/examples/flat-cjs/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Vite + React
+
+
+
+
+
+
diff --git a/examples/flat-cjs/package.json b/examples/flat-cjs/package.json
new file mode 100644
index 00000000..d89c7f08
--- /dev/null
+++ b/examples/flat-cjs/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "flat-cjs",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "lint": "cross-env ESLINT_USE_FLAT_CONFIG=true eslint . --report-unused-disable-directives"
+ },
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.5.0",
+ "cross-env": "^7.0.3",
+ "eslint": "^8.57.0",
+ "eslint-plugin-jsx-a11y": "file:../..",
+ "globals": "^15.6.0"
+ }
+}
diff --git a/examples/flat-cjs/public/vite.svg b/examples/flat-cjs/public/vite.svg
new file mode 100644
index 00000000..e7b8dfb1
--- /dev/null
+++ b/examples/flat-cjs/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/flat-cjs/src/App.css b/examples/flat-cjs/src/App.css
new file mode 100644
index 00000000..b9d355df
--- /dev/null
+++ b/examples/flat-cjs/src/App.css
@@ -0,0 +1,42 @@
+#root {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
diff --git a/examples/flat-cjs/src/App.jsx b/examples/flat-cjs/src/App.jsx
new file mode 100644
index 00000000..b2d55881
--- /dev/null
+++ b/examples/flat-cjs/src/App.jsx
@@ -0,0 +1,36 @@
+import { useState } from 'react';
+import reactLogo from './assets/react.svg';
+import viteLogo from '/vite.svg';
+import './App.css';
+
+function App() {
+ const [count, setCount] = useState(0);
+
+ return (
+ <>
+
+ Vite + React
+
+
+
+ Edit src/App.jsx
and save to test HMR
+
+
+
+ Click on the Vite and React logos to learn more
+
+ >
+ );
+}
+
+export default App;
diff --git a/examples/flat-cjs/src/assets/react.svg b/examples/flat-cjs/src/assets/react.svg
new file mode 100644
index 00000000..6c87de9b
--- /dev/null
+++ b/examples/flat-cjs/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/flat-cjs/src/index.css b/examples/flat-cjs/src/index.css
new file mode 100644
index 00000000..6119ad9a
--- /dev/null
+++ b/examples/flat-cjs/src/index.css
@@ -0,0 +1,68 @@
+:root {
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+ a:hover {
+ color: #747bff;
+ }
+ button {
+ background-color: #f9f9f9;
+ }
+}
diff --git a/examples/flat-cjs/src/main.jsx b/examples/flat-cjs/src/main.jsx
new file mode 100644
index 00000000..54b39dd1
--- /dev/null
+++ b/examples/flat-cjs/src/main.jsx
@@ -0,0 +1,10 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App.jsx'
+import './index.css'
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+ ,
+)
diff --git a/examples/flat-esm/eslint.config.js b/examples/flat-esm/eslint.config.js
new file mode 100644
index 00000000..ae5afaa7
--- /dev/null
+++ b/examples/flat-esm/eslint.config.js
@@ -0,0 +1,22 @@
+import globals from 'globals';
+import js from '@eslint/js';
+import jsxA11y from 'eslint-plugin-jsx-a11y';
+
+export default [
+ js.configs.recommended,
+ jsxA11y.flatConfigs.recommended,
+ {
+ files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
+ languageOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ globals: globals.browser,
+ },
+ ignores: ['dist', 'eslint.config.js'],
+ rules: {
+ 'no-unused-vars': 'off',
+ 'jsx-a11y/anchor-ambiguous-text': 'warn',
+ 'jsx-a11y/anchor-is-valid': 'warn',
+ },
+ },
+];
diff --git a/examples/flat-esm/index.html b/examples/flat-esm/index.html
new file mode 100644
index 00000000..0c589ecc
--- /dev/null
+++ b/examples/flat-esm/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Vite + React
+
+
+
+
+
+
diff --git a/examples/flat-esm/package.json b/examples/flat-esm/package.json
new file mode 100644
index 00000000..cf10c817
--- /dev/null
+++ b/examples/flat-esm/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "flat-esm",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "lint": "cross-env ESLINT_USE_FLAT_CONFIG=true eslint . --report-unused-disable-directives"
+ },
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.5.0",
+ "cross-env": "^7.0.3",
+ "eslint": "^8.57.0",
+ "eslint-plugin-jsx-a11y": "file:../..",
+ "globals": "^15.6.0"
+ }
+}
diff --git a/examples/flat-esm/public/vite.svg b/examples/flat-esm/public/vite.svg
new file mode 100644
index 00000000..e7b8dfb1
--- /dev/null
+++ b/examples/flat-esm/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/flat-esm/src/App.css b/examples/flat-esm/src/App.css
new file mode 100644
index 00000000..b9d355df
--- /dev/null
+++ b/examples/flat-esm/src/App.css
@@ -0,0 +1,42 @@
+#root {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
diff --git a/examples/flat-esm/src/App.jsx b/examples/flat-esm/src/App.jsx
new file mode 100644
index 00000000..b2d55881
--- /dev/null
+++ b/examples/flat-esm/src/App.jsx
@@ -0,0 +1,36 @@
+import { useState } from 'react';
+import reactLogo from './assets/react.svg';
+import viteLogo from '/vite.svg';
+import './App.css';
+
+function App() {
+ const [count, setCount] = useState(0);
+
+ return (
+ <>
+
+ Vite + React
+
+
+
+ Edit src/App.jsx
and save to test HMR
+
+
+
+ Click on the Vite and React logos to learn more
+
+ >
+ );
+}
+
+export default App;
diff --git a/examples/flat-esm/src/assets/react.svg b/examples/flat-esm/src/assets/react.svg
new file mode 100644
index 00000000..6c87de9b
--- /dev/null
+++ b/examples/flat-esm/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/flat-esm/src/index.css b/examples/flat-esm/src/index.css
new file mode 100644
index 00000000..6119ad9a
--- /dev/null
+++ b/examples/flat-esm/src/index.css
@@ -0,0 +1,68 @@
+:root {
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+ a:hover {
+ color: #747bff;
+ }
+ button {
+ background-color: #f9f9f9;
+ }
+}
diff --git a/examples/flat-esm/src/main.jsx b/examples/flat-esm/src/main.jsx
new file mode 100644
index 00000000..54b39dd1
--- /dev/null
+++ b/examples/flat-esm/src/main.jsx
@@ -0,0 +1,10 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App.jsx'
+import './index.css'
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+ ,
+)
diff --git a/examples/legacy/.eslintrc.cjs b/examples/legacy/.eslintrc.cjs
new file mode 100644
index 00000000..1b743411
--- /dev/null
+++ b/examples/legacy/.eslintrc.cjs
@@ -0,0 +1,17 @@
+module.exports = {
+ root: true,
+ env: { browser: true, es2020: true },
+ extends: ['eslint:recommended', 'plugin:jsx-a11y/recommended'],
+ ignorePatterns: ['dist', '.eslintrc.cjs'],
+ parserOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ },
+ settings: { react: { version: '18.2' } },
+ plugins: ['jsx-a11y'],
+ rules: {
+ 'no-unused-vars': 'off',
+ 'jsx-a11y/anchor-ambiguous-text': 'warn',
+ 'jsx-a11y/anchor-is-valid': 'warn',
+ },
+};
diff --git a/examples/legacy/index.html b/examples/legacy/index.html
new file mode 100644
index 00000000..0c589ecc
--- /dev/null
+++ b/examples/legacy/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Vite + React
+
+
+
+
+
+
diff --git a/examples/legacy/package.json b/examples/legacy/package.json
new file mode 100644
index 00000000..35be8148
--- /dev/null
+++ b/examples/legacy/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "legacy",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "lint": "cross-env ESLINT_USE_FLAT_CONFIG=false eslint . --ext js,jsx --report-unused-disable-directives"
+ },
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "cross-env": "^7.0.3",
+ "eslint": "^8.57.0",
+ "eslint-plugin-jsx-a11y": "file:../.."
+ }
+}
diff --git a/examples/legacy/public/vite.svg b/examples/legacy/public/vite.svg
new file mode 100644
index 00000000..e7b8dfb1
--- /dev/null
+++ b/examples/legacy/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/legacy/src/App.css b/examples/legacy/src/App.css
new file mode 100644
index 00000000..b9d355df
--- /dev/null
+++ b/examples/legacy/src/App.css
@@ -0,0 +1,42 @@
+#root {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
diff --git a/examples/legacy/src/App.jsx b/examples/legacy/src/App.jsx
new file mode 100644
index 00000000..b2d55881
--- /dev/null
+++ b/examples/legacy/src/App.jsx
@@ -0,0 +1,36 @@
+import { useState } from 'react';
+import reactLogo from './assets/react.svg';
+import viteLogo from '/vite.svg';
+import './App.css';
+
+function App() {
+ const [count, setCount] = useState(0);
+
+ return (
+ <>
+
+ Vite + React
+
+
+
+ Edit src/App.jsx
and save to test HMR
+
+
+
+ Click on the Vite and React logos to learn more
+
+ >
+ );
+}
+
+export default App;
diff --git a/examples/legacy/src/assets/react.svg b/examples/legacy/src/assets/react.svg
new file mode 100644
index 00000000..6c87de9b
--- /dev/null
+++ b/examples/legacy/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/legacy/src/index.css b/examples/legacy/src/index.css
new file mode 100644
index 00000000..6119ad9a
--- /dev/null
+++ b/examples/legacy/src/index.css
@@ -0,0 +1,68 @@
+:root {
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+ a:hover {
+ color: #747bff;
+ }
+ button {
+ background-color: #f9f9f9;
+ }
+}
diff --git a/examples/legacy/src/main.jsx b/examples/legacy/src/main.jsx
new file mode 100644
index 00000000..54b39dd1
--- /dev/null
+++ b/examples/legacy/src/main.jsx
@@ -0,0 +1,10 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App.jsx'
+import './index.css'
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+ ,
+)
diff --git a/package.json b/package.json
index 382b6828..f14e0b68 100644
--- a/package.json
+++ b/package.json
@@ -29,9 +29,14 @@
"test": "npm run jest",
"posttest": "aud --production",
"test:ci": "npm run jest -- --ci --runInBand",
+ "pretest:examples": "npm run build",
+ "test:examples": "npm run test-example:legacy && npm run test-example:flat-esm && npm run test-example:flat-cjs",
+ "test-example:legacy": "cd examples/legacy && npm install && npm run lint",
+ "test-example:flat-esm": "cd examples/flat-esm && npm install && npm run lint",
+ "test-example:flat-cjs": "cd examples/flat-cjs && npm install && npm run lint",
"jest": "jest --coverage __tests__/**/*",
"pregenerate-list-of-rules": "npm run build",
- "generate-list-of-rules": "eslint-doc-generator --rule-doc-title-format prefix-name --rule-doc-section-options false --config-emoji recommended,☑️",
+ "generate-list-of-rules": "eslint-doc-generator --rule-doc-title-format prefix-name --rule-doc-section-options false --config-emoji recommended,☑️ --ignore-config flat/recommended --ignore-config flat/strict",
"generate-list-of-rules:check": "npm run generate-list-of-rules -- --check",
"version": "auto-changelog && git add CHANGELOG.md",
"postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\""
@@ -128,7 +133,8 @@
"/reports",
"/flow",
"scripts/",
- "CONTRIBUTING.md"
+ "CONTRIBUTING.md",
+ "/examples"
]
}
}
diff --git a/src/configs/flat-config-base.js b/src/configs/flat-config-base.js
new file mode 100644
index 00000000..54b84e5f
--- /dev/null
+++ b/src/configs/flat-config-base.js
@@ -0,0 +1,9 @@
+module.exports = {
+ languageOptions: {
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+};
diff --git a/src/configs/legacy-config-base.js b/src/configs/legacy-config-base.js
new file mode 100644
index 00000000..2d177961
--- /dev/null
+++ b/src/configs/legacy-config-base.js
@@ -0,0 +1,7 @@
+module.exports = {
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+};
diff --git a/src/index.js b/src/index.js
index 752ff612..2fa185fa 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,296 +1,320 @@
/* eslint-disable global-require */
+const flatConfigBase = require('./configs/flat-config-base');
+const legacyConfigBase = require('./configs/legacy-config-base');
+const { name, version } = require('../package.json');
-module.exports = {
- rules: {
- 'accessible-emoji': require('./rules/accessible-emoji'),
- 'alt-text': require('./rules/alt-text'),
- 'anchor-ambiguous-text': require('./rules/anchor-ambiguous-text'),
- 'anchor-has-content': require('./rules/anchor-has-content'),
- 'anchor-is-valid': require('./rules/anchor-is-valid'),
- 'aria-activedescendant-has-tabindex': require('./rules/aria-activedescendant-has-tabindex'),
- 'aria-props': require('./rules/aria-props'),
- 'aria-proptypes': require('./rules/aria-proptypes'),
- 'aria-role': require('./rules/aria-role'),
- 'aria-unsupported-elements': require('./rules/aria-unsupported-elements'),
- 'autocomplete-valid': require('./rules/autocomplete-valid'),
- 'click-events-have-key-events': require('./rules/click-events-have-key-events'),
- 'control-has-associated-label': require('./rules/control-has-associated-label'),
- 'heading-has-content': require('./rules/heading-has-content'),
- 'html-has-lang': require('./rules/html-has-lang'),
- 'iframe-has-title': require('./rules/iframe-has-title'),
- 'img-redundant-alt': require('./rules/img-redundant-alt'),
- 'interactive-supports-focus': require('./rules/interactive-supports-focus'),
- 'label-has-associated-control': require('./rules/label-has-associated-control'),
- 'label-has-for': require('./rules/label-has-for'),
- lang: require('./rules/lang'),
- 'media-has-caption': require('./rules/media-has-caption'),
- 'mouse-events-have-key-events': require('./rules/mouse-events-have-key-events'),
- 'no-access-key': require('./rules/no-access-key'),
- 'no-aria-hidden-on-focusable': require('./rules/no-aria-hidden-on-focusable'),
- 'no-autofocus': require('./rules/no-autofocus'),
- 'no-distracting-elements': require('./rules/no-distracting-elements'),
- 'no-interactive-element-to-noninteractive-role': require('./rules/no-interactive-element-to-noninteractive-role'),
- 'no-noninteractive-element-interactions': require('./rules/no-noninteractive-element-interactions'),
- 'no-noninteractive-element-to-interactive-role': require('./rules/no-noninteractive-element-to-interactive-role'),
- 'no-noninteractive-tabindex': require('./rules/no-noninteractive-tabindex'),
- 'no-onchange': require('./rules/no-onchange'),
- 'no-redundant-roles': require('./rules/no-redundant-roles'),
- 'no-static-element-interactions': require('./rules/no-static-element-interactions'),
- 'prefer-tag-over-role': require('./rules/prefer-tag-over-role'),
- 'role-has-required-aria-props': require('./rules/role-has-required-aria-props'),
- 'role-supports-aria-props': require('./rules/role-supports-aria-props'),
- scope: require('./rules/scope'),
- 'tabindex-no-positive': require('./rules/tabindex-no-positive'),
- },
- configs: {
- recommended: {
- plugins: [
- 'jsx-a11y',
+const allRules = {
+ 'accessible-emoji': require('./rules/accessible-emoji'),
+ 'alt-text': require('./rules/alt-text'),
+ 'anchor-ambiguous-text': require('./rules/anchor-ambiguous-text'),
+ 'anchor-has-content': require('./rules/anchor-has-content'),
+ 'anchor-is-valid': require('./rules/anchor-is-valid'),
+ 'aria-activedescendant-has-tabindex': require('./rules/aria-activedescendant-has-tabindex'),
+ 'aria-props': require('./rules/aria-props'),
+ 'aria-proptypes': require('./rules/aria-proptypes'),
+ 'aria-role': require('./rules/aria-role'),
+ 'aria-unsupported-elements': require('./rules/aria-unsupported-elements'),
+ 'autocomplete-valid': require('./rules/autocomplete-valid'),
+ 'click-events-have-key-events': require('./rules/click-events-have-key-events'),
+ 'control-has-associated-label': require('./rules/control-has-associated-label'),
+ 'heading-has-content': require('./rules/heading-has-content'),
+ 'html-has-lang': require('./rules/html-has-lang'),
+ 'iframe-has-title': require('./rules/iframe-has-title'),
+ 'img-redundant-alt': require('./rules/img-redundant-alt'),
+ 'interactive-supports-focus': require('./rules/interactive-supports-focus'),
+ 'label-has-associated-control': require('./rules/label-has-associated-control'),
+ 'label-has-for': require('./rules/label-has-for'),
+ lang: require('./rules/lang'),
+ 'media-has-caption': require('./rules/media-has-caption'),
+ 'mouse-events-have-key-events': require('./rules/mouse-events-have-key-events'),
+ 'no-access-key': require('./rules/no-access-key'),
+ 'no-aria-hidden-on-focusable': require('./rules/no-aria-hidden-on-focusable'),
+ 'no-autofocus': require('./rules/no-autofocus'),
+ 'no-distracting-elements': require('./rules/no-distracting-elements'),
+ 'no-interactive-element-to-noninteractive-role': require('./rules/no-interactive-element-to-noninteractive-role'),
+ 'no-noninteractive-element-interactions': require('./rules/no-noninteractive-element-interactions'),
+ 'no-noninteractive-element-to-interactive-role': require('./rules/no-noninteractive-element-to-interactive-role'),
+ 'no-noninteractive-tabindex': require('./rules/no-noninteractive-tabindex'),
+ 'no-onchange': require('./rules/no-onchange'),
+ 'no-redundant-roles': require('./rules/no-redundant-roles'),
+ 'no-static-element-interactions': require('./rules/no-static-element-interactions'),
+ 'prefer-tag-over-role': require('./rules/prefer-tag-over-role'),
+ 'role-has-required-aria-props': require('./rules/role-has-required-aria-props'),
+ 'role-supports-aria-props': require('./rules/role-supports-aria-props'),
+ scope: require('./rules/scope'),
+ 'tabindex-no-positive': require('./rules/tabindex-no-positive'),
+};
+
+const recommendedRules = {
+ 'jsx-a11y/alt-text': 'error',
+ 'jsx-a11y/anchor-ambiguous-text': 'off', // TODO: 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',
+ 'searchbox',
+ 'spinbutton',
+ 'switch',
+ 'textbox',
+ ],
+ },
+ ],
+ 'jsx-a11y/label-has-associated-control': 'error',
+ 'jsx-a11y/label-has-for': 'off',
+ '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',
+ {
+ tr: ['none', 'presentation'],
+ canvas: ['img'],
+ },
+ ],
+ 'jsx-a11y/no-noninteractive-element-interactions': [
+ 'error',
+ {
+ handlers: [
+ 'onClick',
+ 'onError',
+ 'onLoad',
+ 'onMouseDown',
+ 'onMouseUp',
+ 'onKeyPress',
+ 'onKeyDown',
+ 'onKeyUp',
+ ],
+ alert: ['onKeyUp', 'onKeyDown', 'onKeyPress'],
+ body: ['onError', 'onLoad'],
+ dialog: ['onKeyUp', 'onKeyDown', 'onKeyPress'],
+ iframe: ['onError', 'onLoad'],
+ img: ['onError', 'onLoad'],
+ },
+ ],
+ 'jsx-a11y/no-noninteractive-element-to-interactive-role': [
+ 'error',
+ {
+ ul: [
+ 'listbox',
+ 'menu',
+ 'menubar',
+ 'radiogroup',
+ 'tablist',
+ 'tree',
+ 'treegrid',
+ ],
+ ol: [
+ 'listbox',
+ 'menu',
+ 'menubar',
+ 'radiogroup',
+ 'tablist',
+ 'tree',
+ 'treegrid',
+ ],
+ li: [
+ 'menuitem',
+ 'menuitemradio',
+ 'menuitemcheckbox',
+ 'option',
+ 'row',
+ 'tab',
+ 'treeitem',
+ ],
+ table: ['grid'],
+ td: ['gridcell'],
+ fieldset: ['radiogroup', 'presentation'],
+ },
+ ],
+ 'jsx-a11y/no-noninteractive-tabindex': [
+ 'error',
+ {
+ tags: [],
+ roles: ['tabpanel'],
+ allowExpressionValues: true,
+ },
+ ],
+ 'jsx-a11y/no-redundant-roles': 'error',
+ 'jsx-a11y/no-static-element-interactions': [
+ 'error',
+ {
+ allowExpressionValues: true,
+ handlers: [
+ 'onClick',
+ 'onMouseDown',
+ 'onMouseUp',
+ 'onKeyPress',
+ 'onKeyDown',
+ 'onKeyUp',
],
- parserOptions: {
- ecmaFeatures: {
- jsx: true,
- },
- },
- rules: {
- 'jsx-a11y/alt-text': 'error',
- 'jsx-a11y/anchor-ambiguous-text': 'off', // TODO: 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',
- 'searchbox',
- 'spinbutton',
- 'switch',
- 'textbox',
- ],
- },
- ],
- 'jsx-a11y/label-has-associated-control': 'error',
- 'jsx-a11y/label-has-for': 'off',
- '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',
- {
- tr: ['none', 'presentation'],
- canvas: ['img'],
- },
- ],
- 'jsx-a11y/no-noninteractive-element-interactions': [
- 'error',
- {
- handlers: [
- 'onClick',
- 'onError',
- 'onLoad',
- 'onMouseDown',
- 'onMouseUp',
- 'onKeyPress',
- 'onKeyDown',
- 'onKeyUp',
- ],
- alert: ['onKeyUp', 'onKeyDown', 'onKeyPress'],
- body: ['onError', 'onLoad'],
- dialog: ['onKeyUp', 'onKeyDown', 'onKeyPress'],
- iframe: ['onError', 'onLoad'],
- img: ['onError', 'onLoad'],
- },
- ],
- 'jsx-a11y/no-noninteractive-element-to-interactive-role': [
- 'error',
- {
- ul: [
- 'listbox',
- 'menu',
- 'menubar',
- 'radiogroup',
- 'tablist',
- 'tree',
- 'treegrid',
- ],
- ol: [
- 'listbox',
- 'menu',
- 'menubar',
- 'radiogroup',
- 'tablist',
- 'tree',
- 'treegrid',
- ],
- li: ['menuitem', 'menuitemradio', 'menuitemcheckbox', 'option', 'row', 'tab', 'treeitem'],
- table: ['grid'],
- td: ['gridcell'],
- fieldset: ['radiogroup', 'presentation'],
- },
- ],
- 'jsx-a11y/no-noninteractive-tabindex': [
- 'error',
- {
- tags: [],
- roles: ['tabpanel'],
- allowExpressionValues: true,
- },
- ],
- 'jsx-a11y/no-redundant-roles': 'error',
- 'jsx-a11y/no-static-element-interactions': [
- 'error',
- {
- allowExpressionValues: true,
- handlers: [
- 'onClick',
- 'onMouseDown',
- 'onMouseUp',
- 'onKeyPress',
- 'onKeyDown',
- 'onKeyUp',
- ],
- },
- ],
- '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',
- },
},
- strict: {
- plugins: [
- 'jsx-a11y',
+ ],
+ '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',
+};
+
+const strictRules = {
+ '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',
],
- parserOptions: {
- ecmaFeatures: {
- jsx: true,
- },
- },
- rules: {
- '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',
- },
+ 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',
};
+
+/** Base plugin object */
+const jsxA11y = {
+ meta: { name, version },
+ rules: { ...allRules },
+};
+
+/**
+ * Given a ruleset and optionally a flat config name, generate a config.
+ * @param {object} rules - ruleset for this config
+ * @param {string} flatConfigName - name for the config if flat
+ * @returns Config for this set of rules.
+ */
+const createConfig = (rules, flatConfigName) => ({
+ ...(flatConfigName
+ ? {
+ ...flatConfigBase,
+ name: `jsx-a11y/${flatConfigName}`,
+ plugins: { 'jsx-a11y': jsxA11y },
+ }
+ : { ...legacyConfigBase, plugins: ['jsx-a11y'] }),
+ rules: { ...rules },
+});
+
+// Create configs for the plugin object
+const configs = {
+ recommended: createConfig(recommendedRules),
+ strict: createConfig(strictRules),
+};
+const flatConfigs = {
+ recommended: createConfig(recommendedRules, 'recommended'),
+ strict: createConfig(strictRules, 'strict'),
+};
+
+module.exports = { ...jsxA11y, configs, flatConfigs };