From 8c9445e968173567635dba3841085829cc7c23fb Mon Sep 17 00:00:00 2001
From: Clay Miller
Date: Sat, 17 Dec 2022 05:30:12 +0000
Subject: [PATCH 1/6] wip: role-supports-aria-props
---
.eslintrc.js | 2 +-
README.md | 5 +-
docs/rules/role-supports-aria-props.md | 74 ++++
lib/index.js | 1 +
lib/rules/role-supports-aria-props.js | 88 +++++
lib/utils/object-map.js | 58 +++
package-lock.json | 451 +++++++++++++++++++---
package.json | 1 +
tests/role-supports-aria-props.js | 511 +++++++++++++++++++++++++
tests/utils/object-map.js | 180 +++++++++
10 files changed, 1319 insertions(+), 52 deletions(-)
create mode 100644 docs/rules/role-supports-aria-props.md
create mode 100644 lib/rules/role-supports-aria-props.js
create mode 100644 lib/utils/object-map.js
create mode 100644 tests/role-supports-aria-props.js
create mode 100644 tests/utils/object-map.js
diff --git a/.eslintrc.js b/.eslintrc.js
index 4f7ea4cb..79f1918f 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,7 +1,7 @@
module.exports = {
root: true,
parserOptions: {
- ecmaVersion: 2020
+ ecmaVersion: 13
},
env: {
es6: true,
diff --git a/README.md b/README.md
index 6f53657a..3bc0a82b 100644
--- a/README.md
+++ b/README.md
@@ -54,8 +54,8 @@ For each component, you may specify a `default` and/or `props`. `default` may ma
"settings": {
"github": {
"components": {
- "Box": { "default": "p" },
- "Link": { "props": {"as": { "undefined": "a", "a": "a", "button": "button"}}},
+ "Box": {"default": "p"},
+ "Link": {"props": {"as": {"undefined": "a", "a": "a", "button": "button"}}}
}
}
}
@@ -89,6 +89,7 @@ This config will be interpreted in the following way:
- [No Useless Passive](./docs/rules/no-useless-passive.md)
- [Prefer Observers](./docs/rules/prefer-observers.md)
- [Require Passive Events](./docs/rules/require-passive-events.md)
+- [Role Supports ARIA Props](./docs/rules/role-supports-aria-props.md)
- [Unescaped HTML Literal](./docs/rules/unescaped-html-literal.md)
#### Accessibility-focused rules (prefixed with a11y)
diff --git a/docs/rules/role-supports-aria-props.md b/docs/rules/role-supports-aria-props.md
new file mode 100644
index 00000000..57592028
--- /dev/null
+++ b/docs/rules/role-supports-aria-props.md
@@ -0,0 +1,74 @@
+# Role Supports ARIA Props
+
+## Rule Details
+
+This rule enforces that elements with explicit or implicit roles defined contain only `aria-*` properties supported by that `role`.
+
+For example, this rule aims to discourage common misuse of the `aria-label` and `aria-labelledby` attribute. `aria-label` and `aria-labelledby` support is only guaranteed on interactive elements like `button` or `a`, or on static elements like `div` and `span` with a permitted `role`. This rule will allow `aria-label` and `aria-labelledby` usage on `div` and `span` elements if it set to a role other than the ones listed in [WSC: a list of ARIA roles which cannot be named](https://w3c.github.io/aria/#namefromprohibited). This rule will never permit usage of `aria-label` and `aria-labelledby` on `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `strong`, `i`, `p`, `b`, or `code`.
+
+### "Help! I'm trying to set a tooltip on a static element and this rule flagged it!"
+
+Please do not use tooltips on static elements. It is a highly discouraged, inaccessible pattern.
+See [Primer: Tooltip alternatives](https://primer.style/design/accessibility/tooltip-alternatives) for what to do instead.
+
+### Resources
+
+- [w3c/aria Consider prohibiting author naming certain roles #833](https://github.com/w3c/aria/issues/833)
+- [Not so short note on aria-label usage - Big Table Edition](https://html5accessibility.com/stuff/2020/11/07/not-so-short-note-on-aria-label-usage-big-table-edition/)
+- [Your tooltips are bogus](https://heydonworks.com/article/your-tooltips-are-bogus/)
+- [Primer: Tooltip alternatives](https://primer.style/design/accessibility/tooltip-alternatives)
+
+### Disclaimer
+
+There are conflicting resources and opinions on what elements should support these naming attributes. For now, this rule will operate under a relatively simple heuristic aimed to minimize false positives. This may have room for future improvements. Learn more at [W3C Name Calcluation](https://w3c.github.io/aria/#namecalculation).
+
+### **Incorrect** code for this rule đź‘Ž
+
+```erb
+I am some text.
+```
+
+```erb
+Please be careful of the following.
+```
+
+```erb
+Goodbye
+```
+
+```erb
+Page title
+```
+
+### **Correct** code for this rule đź‘Ť
+
+```erb
+
+
+
+```
+
+```erb
+
+
+
+Add bold text or turn selection into bold text
+```
+
+```erb
+Hello
+```
+
+```erb
+Goodbye
+```
+
+```erb
+Page title
+```
+
+```erb
+
+
Heading
+
+```
diff --git a/lib/index.js b/lib/index.js
index 8ed725f9..94608ae7 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -17,6 +17,7 @@ module.exports = {
'no-then': require('./rules/no-then'),
'no-useless-passive': require('./rules/no-useless-passive'),
'prefer-observers': require('./rules/prefer-observers'),
+ 'role-supports-aria-props': require('./rules/role-supports-aria-props'),
'require-passive-events': require('./rules/require-passive-events'),
'unescaped-html-literal': require('./rules/unescaped-html-literal')
},
diff --git a/lib/rules/role-supports-aria-props.js b/lib/rules/role-supports-aria-props.js
new file mode 100644
index 00000000..e636a6cb
--- /dev/null
+++ b/lib/rules/role-supports-aria-props.js
@@ -0,0 +1,88 @@
+// @ts-check
+const {aria, elementRoles, roles} = require('aria-query')
+const {getProp, getPropValue, propName} = require('jsx-ast-utils')
+const {getElementType} = require('../utils/get-element-type')
+const ObjectMap = require('../utils/object-map')
+
+// Clean-up `elementRoles` from `aria-query`
+const elementRolesMap = new ObjectMap()
+for (const [key, value] of elementRoles.entries()) {
+ delete key.constraints
+ key.attributes = key.attributes?.filter(attribute => !('constraints' in attribute))
+ if (!key.attributes || key.attributes?.length === 0) {
+ delete key.attributes
+ }
+ elementRolesMap.set(key, value)
+}
+elementRolesMap.delete({name: 'menuitem'})
+elementRolesMap.set({name: 'menuitem', attributes: [{name: 'type', value: 'toolbar'}]}, ['toolbar'])
+
+module.exports = {
+ meta: {
+ docs: {
+ description:
+ 'Enforce that elements with explicit or implicit roles defined contain only `aria-*` properties supported by that `role`.',
+ url: require('../url')(module)
+ },
+ schema: []
+ },
+
+ create(context) {
+ return {
+ JSXOpeningElement(node) {
+ // Get the element’s explicit or implicit role
+ const key = {name: getElementType(context, node)}
+ for (const prop of ['aria-expanded', 'type', 'size', 'role', 'href', 'multiple', 'scope']) {
+ const value = getPropValue(getProp(node.attributes, prop))
+ if (!('attributes' in key)) {
+ key.attributes = []
+ }
+ if (value) {
+ if (prop === 'href') {
+ key.attributes.push({name: prop})
+ } else {
+ key.attributes.push({name: prop, value})
+ }
+ }
+ }
+ const role = getPropValue(getProp(node.attributes, 'role')) ?? elementRolesMap.get(key)?.[0]
+
+ // Return early if role could not be determined
+ if (!role) return
+
+ // Get allowed ARIA attributes:
+ // - From the role itself
+ let allowedProps = Object.keys(roles.get(role)?.props || {})
+ // - From parent roles
+ for (const parentRole of roles.get(role)?.superClass.flat() ?? []) {
+ allowedProps = allowedProps.concat(Object.keys(roles.get(parentRole)?.props || {}))
+ }
+ // Dedupe, for performance
+ allowedProps = Array.from(new Set(allowedProps))
+
+ // Get prohibited ARIA attributes:
+ // - From the role itself
+ let prohibitedProps = roles.get(role)?.prohibitedProps || []
+ // - From parent roles
+ for (const parentRole of roles.get(role)?.superClass.flat() ?? []) {
+ prohibitedProps = prohibitedProps.concat(roles.get(parentRole)?.prohibitedProps || [])
+ }
+ // - From comparing allowed vs all ARIA attributes
+ prohibitedProps = prohibitedProps.concat(aria.keys().filter(x => !allowedProps.includes(x)))
+ // Dedupe, for performance
+ prohibitedProps = Array.from(new Set(prohibitedProps))
+
+ for (const prop of node.attributes) {
+ if (getPropValue(prop) == null || prop.type === 'JSXSpreadAttribute') return
+
+ if (prohibitedProps?.includes(propName(prop))) {
+ context.report({
+ node,
+ message: `The attribute ${propName(prop)} is not supported by the role ${role}.`
+ })
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/lib/utils/object-map.js b/lib/utils/object-map.js
new file mode 100644
index 00000000..5c54a8bf
--- /dev/null
+++ b/lib/utils/object-map.js
@@ -0,0 +1,58 @@
+// @ts-check
+const {isDeepStrictEqual} = require('util')
+
+/**
+ * ObjectMap extends Map, but determines key equality using Node.js’ `util.isDeepStrictEqual` rather than using [SameValueZero](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#key_equality). This makes using objects as keys a bit simpler.
+ */
+module.exports = class ObjectMap extends Map {
+ #data
+
+ constructor(iterable = []) {
+ super()
+ this.#data = iterable
+ }
+
+ clear() {
+ this.#data = []
+ }
+
+ delete(key) {
+ if (!this.has(key)) {
+ return false
+ }
+ this.#data = this.#data.filter(([existingKey]) => !isDeepStrictEqual(existingKey, key))
+ return true
+ }
+
+ entries() {
+ return this.#data[Symbol.iterator]()
+ }
+
+ forEach(cb) {
+ for (const [key, value] of this.#data) {
+ cb(value, key, this.#data)
+ }
+ }
+
+ get(key) {
+ return this.#data.find(([existingKey]) => isDeepStrictEqual(existingKey, key))?.[1]
+ }
+
+ has(key) {
+ return this.#data.findIndex(([existingKey]) => isDeepStrictEqual(existingKey, key)) !== -1
+ }
+
+ keys() {
+ return this.#data.map(([key]) => key)[Symbol.iterator]()
+ }
+
+ set(key, value) {
+ this.delete(key)
+ this.#data.push([key, value])
+ return this
+ }
+
+ values() {
+ return this.#data.map(([, value]) => value)[Symbol.iterator]()
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index 26f580c8..0149b2b2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,7 @@
"@github/browserslist-config": "^1.0.0",
"@typescript-eslint/eslint-plugin": "^5.1.0",
"@typescript-eslint/parser": "^5.1.0",
+ "aria-query": "^5.1.3",
"eslint-config-prettier": ">=8.0.0",
"eslint-plugin-escompat": "^3.3.3",
"eslint-plugin-eslint-comments": "^3.2.0",
@@ -52,12 +53,12 @@
}
},
"node_modules/@babel/runtime-corejs3": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.18.9.tgz",
- "integrity": "sha512-qZEWeccZCrHA2Au4/X05QW5CMdm4VjUDCrGq5gf1ZDcM4hRqreKrtwAn7yci9zfgAS9apvnsFXiGBHBAxZdK9A==",
+ "version": "7.20.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.20.6.tgz",
+ "integrity": "sha512-tqeujPiuEfcH067mx+7otTQWROVMKHXEaOQcAeNV5dDdbPWvPcFA8/W9LXw2NfjNmOetqLl03dfnG2WALPlsRQ==",
"dependencies": {
- "core-js-pure": "^3.20.2",
- "regenerator-runtime": "^0.13.4"
+ "core-js-pure": "^3.25.1",
+ "regenerator-runtime": "^0.13.11"
},
"engines": {
"node": ">=6.9.0"
@@ -522,15 +523,11 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/aria-query": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz",
- "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==",
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz",
+ "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==",
"dependencies": {
- "@babel/runtime": "^7.10.2",
- "@babel/runtime-corejs3": "^7.10.2"
- },
- "engines": {
- "node": ">=6.0"
+ "deep-equal": "^2.0.5"
}
},
"node_modules/array-includes": {
@@ -589,6 +586,17 @@
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
"integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag=="
},
+ "node_modules/available-typed-arrays": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
+ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/axe-core": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.3.tgz",
@@ -853,9 +861,9 @@
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"node_modules/core-js-pure": {
- "version": "3.23.5",
- "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.23.5.tgz",
- "integrity": "sha512-8t78LdpKSuCq4pJYCYk8hl7XEkAX+BP16yRIwL3AanTksxuEf7CM83vRyctmiEL8NDZ3jpUcv56fk9/zG3aIuw==",
+ "version": "3.26.1",
+ "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.1.tgz",
+ "integrity": "sha512-VVXcDpp/xJ21KdULRq/lXdLzQAtX7+37LzpyfFM973il0tWSsDEoyzG38G14AjTpK9VTfiNM9jnFauq/CpaWGQ==",
"hasInstallScript": true,
"funding": {
"type": "opencollective",
@@ -920,6 +928,31 @@
"node": ">=6"
}
},
+ "node_modules/deep-equal": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.1.0.tgz",
+ "integrity": "sha512-2pxgvWu3Alv1PoWEyVg7HS8YhGlUFUV7N5oOvfL6d+7xAmLSemMwv/c8Zv/i9KFzxV5Kt5CAvQc70fLwVuf4UA==",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "es-get-iterator": "^1.1.2",
+ "get-intrinsic": "^1.1.3",
+ "is-arguments": "^1.1.1",
+ "is-date-object": "^1.0.5",
+ "is-regex": "^1.1.4",
+ "isarray": "^2.0.5",
+ "object-is": "^1.1.5",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.4.3",
+ "side-channel": "^1.0.4",
+ "which-boxed-primitive": "^1.0.2",
+ "which-collection": "^1.0.1",
+ "which-typed-array": "^1.1.8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -1018,6 +1051,24 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/es-get-iterator": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz",
+ "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.0",
+ "has-symbols": "^1.0.1",
+ "is-arguments": "^1.1.0",
+ "is-map": "^2.0.2",
+ "is-set": "^2.0.2",
+ "is-string": "^1.0.5",
+ "isarray": "^2.0.5"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/es-to-primitive": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
@@ -1316,6 +1367,18 @@
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
}
},
+ "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz",
+ "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.2",
+ "@babel/runtime-corejs3": "^7.10.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
"node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -1686,6 +1749,14 @@
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz",
"integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA=="
},
+ "node_modules/for-each": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
+ "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
+ "dependencies": {
+ "is-callable": "^1.1.3"
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -1754,13 +1825,13 @@
}
},
"node_modules/get-intrinsic": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
- "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
+ "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
- "has-symbols": "^1.0.1"
+ "has-symbols": "^1.0.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -1844,6 +1915,17 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/gopd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+ "dependencies": {
+ "get-intrinsic": "^1.1.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/grapheme-splitter": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
@@ -1979,6 +2061,21 @@
"node": ">= 0.4"
}
},
+ "node_modules/is-arguments": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
+ "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-bigint": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
@@ -2072,6 +2169,14 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-map": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz",
+ "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-negative-zero": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
@@ -2137,6 +2242,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-set": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz",
+ "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-shared-array-buffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
@@ -2176,6 +2289,24 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-typed-array": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz",
+ "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-unicode-supported": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
@@ -2188,6 +2319,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/is-weakmap": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz",
+ "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-weakref": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
@@ -2199,6 +2338,23 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-weakset": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz",
+ "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
+ },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -2588,6 +2744,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/object-is": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
+ "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
@@ -2597,9 +2768,9 @@
}
},
"node_modules/object.assign": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.3.tgz",
- "integrity": "sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA==",
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
+ "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
"dependencies": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.4",
@@ -2838,9 +3009,9 @@
}
},
"node_modules/regenerator-runtime": {
- "version": "0.13.9",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
- "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
+ "version": "0.13.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"node_modules/regexp.prototype.flags": {
"version": "1.4.3",
@@ -3283,6 +3454,39 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/which-collection": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz",
+ "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==",
+ "dependencies": {
+ "is-map": "^2.0.1",
+ "is-set": "^2.0.1",
+ "is-weakmap": "^2.0.1",
+ "is-weakset": "^2.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-typed-array": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz",
+ "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-tostringtag": "^1.0.0",
+ "is-typed-array": "^1.1.10"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
@@ -3443,12 +3647,12 @@
}
},
"@babel/runtime-corejs3": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.18.9.tgz",
- "integrity": "sha512-qZEWeccZCrHA2Au4/X05QW5CMdm4VjUDCrGq5gf1ZDcM4hRqreKrtwAn7yci9zfgAS9apvnsFXiGBHBAxZdK9A==",
+ "version": "7.20.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.20.6.tgz",
+ "integrity": "sha512-tqeujPiuEfcH067mx+7otTQWROVMKHXEaOQcAeNV5dDdbPWvPcFA8/W9LXw2NfjNmOetqLl03dfnG2WALPlsRQ==",
"requires": {
- "core-js-pure": "^3.20.2",
- "regenerator-runtime": "^0.13.4"
+ "core-js-pure": "^3.25.1",
+ "regenerator-runtime": "^0.13.11"
}
},
"@eslint/eslintrc": {
@@ -3734,12 +3938,11 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"aria-query": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz",
- "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==",
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz",
+ "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==",
"requires": {
- "@babel/runtime": "^7.10.2",
- "@babel/runtime-corejs3": "^7.10.2"
+ "deep-equal": "^2.0.5"
}
},
"array-includes": {
@@ -3780,6 +3983,11 @@
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
"integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag=="
},
+ "available-typed-arrays": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
+ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw=="
+ },
"axe-core": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.3.tgz",
@@ -3966,9 +4174,9 @@
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"core-js-pure": {
- "version": "3.23.5",
- "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.23.5.tgz",
- "integrity": "sha512-8t78LdpKSuCq4pJYCYk8hl7XEkAX+BP16yRIwL3AanTksxuEf7CM83vRyctmiEL8NDZ3jpUcv56fk9/zG3aIuw=="
+ "version": "3.26.1",
+ "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.1.tgz",
+ "integrity": "sha512-VVXcDpp/xJ21KdULRq/lXdLzQAtX7+37LzpyfFM973il0tWSsDEoyzG38G14AjTpK9VTfiNM9jnFauq/CpaWGQ=="
},
"cross-spawn": {
"version": "7.0.3",
@@ -4008,6 +4216,28 @@
"type-detect": "^4.0.0"
}
},
+ "deep-equal": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.1.0.tgz",
+ "integrity": "sha512-2pxgvWu3Alv1PoWEyVg7HS8YhGlUFUV7N5oOvfL6d+7xAmLSemMwv/c8Zv/i9KFzxV5Kt5CAvQc70fLwVuf4UA==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "es-get-iterator": "^1.1.2",
+ "get-intrinsic": "^1.1.3",
+ "is-arguments": "^1.1.1",
+ "is-date-object": "^1.0.5",
+ "is-regex": "^1.1.4",
+ "isarray": "^2.0.5",
+ "object-is": "^1.1.5",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.4.3",
+ "side-channel": "^1.0.4",
+ "which-boxed-primitive": "^1.0.2",
+ "which-collection": "^1.0.1",
+ "which-typed-array": "^1.1.8"
+ }
+ },
"deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -4085,6 +4315,21 @@
"unbox-primitive": "^1.0.2"
}
},
+ "es-get-iterator": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz",
+ "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.0",
+ "has-symbols": "^1.0.1",
+ "is-arguments": "^1.1.0",
+ "is-map": "^2.0.2",
+ "is-set": "^2.0.2",
+ "is-string": "^1.0.5",
+ "isarray": "^2.0.5"
+ }
+ },
"es-to-primitive": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
@@ -4376,6 +4621,15 @@
"semver": "^6.3.0"
},
"dependencies": {
+ "aria-query": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz",
+ "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==",
+ "requires": {
+ "@babel/runtime": "^7.10.2",
+ "@babel/runtime-corejs3": "^7.10.2"
+ }
+ },
"emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -4579,6 +4833,14 @@
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz",
"integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA=="
},
+ "for-each": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
+ "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
+ "requires": {
+ "is-callable": "^1.1.3"
+ }
+ },
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -4625,13 +4887,13 @@
"dev": true
},
"get-intrinsic": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
- "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
+ "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
- "has-symbols": "^1.0.1"
+ "has-symbols": "^1.0.3"
}
},
"get-symbol-description": {
@@ -4685,6 +4947,14 @@
"slash": "^3.0.0"
}
},
+ "gopd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+ "requires": {
+ "get-intrinsic": "^1.1.3"
+ }
+ },
"grapheme-splitter": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
@@ -4778,6 +5048,15 @@
"side-channel": "^1.0.4"
}
},
+ "is-arguments": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
+ "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ }
+ },
"is-bigint": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
@@ -4838,6 +5117,11 @@
"is-extglob": "^2.1.1"
}
},
+ "is-map": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz",
+ "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg=="
+ },
"is-negative-zero": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
@@ -4876,6 +5160,11 @@
"has-tostringtag": "^1.0.0"
}
},
+ "is-set": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz",
+ "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g=="
+ },
"is-shared-array-buffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
@@ -4900,12 +5189,29 @@
"has-symbols": "^1.0.2"
}
},
+ "is-typed-array": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz",
+ "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==",
+ "requires": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-tostringtag": "^1.0.0"
+ }
+ },
"is-unicode-supported": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
"integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
"dev": true
},
+ "is-weakmap": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz",
+ "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA=="
+ },
"is-weakref": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
@@ -4914,6 +5220,20 @@
"call-bind": "^1.0.2"
}
},
+ "is-weakset": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz",
+ "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.1"
+ }
+ },
+ "isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
+ },
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -5213,15 +5533,24 @@
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
"integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ=="
},
+ "object-is": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
+ "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3"
+ }
+ },
"object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
},
"object.assign": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.3.tgz",
- "integrity": "sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA==",
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
+ "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.4",
@@ -5377,9 +5706,9 @@
}
},
"regenerator-runtime": {
- "version": "0.13.9",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
- "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
+ "version": "0.13.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"regexp.prototype.flags": {
"version": "1.4.3",
@@ -5655,6 +5984,30 @@
"is-symbol": "^1.0.3"
}
},
+ "which-collection": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz",
+ "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==",
+ "requires": {
+ "is-map": "^2.0.1",
+ "is-set": "^2.0.1",
+ "is-weakmap": "^2.0.1",
+ "is-weakset": "^2.0.1"
+ }
+ },
+ "which-typed-array": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz",
+ "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==",
+ "requires": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-tostringtag": "^1.0.0",
+ "is-typed-array": "^1.1.10"
+ }
+ },
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
diff --git a/package.json b/package.json
index f5374fc5..b248b22c 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,7 @@
"@github/browserslist-config": "^1.0.0",
"@typescript-eslint/eslint-plugin": "^5.1.0",
"@typescript-eslint/parser": "^5.1.0",
+ "aria-query": "^5.1.3",
"eslint-config-prettier": ">=8.0.0",
"eslint-plugin-escompat": "^3.3.3",
"eslint-plugin-eslint-comments": "^3.2.0",
diff --git a/tests/role-supports-aria-props.js b/tests/role-supports-aria-props.js
new file mode 100644
index 00000000..3d2bccc2
--- /dev/null
+++ b/tests/role-supports-aria-props.js
@@ -0,0 +1,511 @@
+// @ts-check
+const rule = require('../lib/rules/role-supports-aria-props')
+const RuleTester = require('eslint').RuleTester
+
+const ruleTester = new RuleTester({
+ parserOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ ecmaFeatures: {
+ jsx: true
+ }
+ }
+})
+
+function getErrorMessage(attribute, role) {
+ return `The attribute ${attribute} is not supported by the role ${role}.`
+}
+
+ruleTester.run('role-supports-aria-props', rule, {
+ valid: [
+ {code: ' '},
+ {code: '
'},
+ {code: '
'},
+ {code: '
'},
+ {code: '
'},
+ {code: ' '},
+ {code: ' '},
+
+ // IMPLICIT ROLE TESTS
+ // A TESTS - implicit role is `link`
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+
+ // this will have global
+ {code: ' '},
+
+ // AREA TESTS - implicit role is `link`
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+
+ // this will have global
+ {code: ' '},
+
+ // LINK TESTS - implicit role is `link`
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+
+ // this will have global
+ {code: ' '},
+
+ // IMG TESTS - no implicit role
+ {code: ' '},
+
+ // this will have role of `img`
+ {code: ' '},
+
+ // MENU TESTS - implicit role is `toolbar` when `type="toolbar"`
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+
+ // this will have global
+ {code: ' '},
+
+ // MENUITEM TESTS
+ // when `type="command`, the implicit role is `menuitem`
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ // when `type="checkbox`, the implicit role is `menuitemcheckbox`
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ // when `type="radio`, the implicit role is `menuitemradio`
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+
+ // these will have global
+ {code: ' '},
+ {code: ' '},
+
+ // INPUT TESTS
+ // when `type="button"`, the implicit role is `button`
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ // when `type="image"`, the implicit role is `button`
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ // when `type="reset"`, the implicit role is `button`
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ // when `type="submit"`, the implicit role is `button`
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ // when `type="checkbox"`, the implicit role is `checkbox`
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ // when `type="radio"`, the implicit role is `radio`
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ // when `type="range"`, the implicit role is `slider`
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+
+ // these will have role of `textbox`,
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+
+ // Allow null/undefined values regardless of role
+ {code: ' '},
+ {code: ' '},
+
+ // OTHER TESTS
+ {code: ' '},
+ {code: ''},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ' '},
+ {code: ''},
+ {code: ' '},
+ {code: ' '},
+ {code: '
'},
+ {code: '
'}
+ ],
+
+ invalid: [
+ // implicit basic checks
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-checked', 'link')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-checked', 'link')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-checked', 'link')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-checked', 'img')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-checked', 'toolbar')]
+ },
+ {
+ code: '',
+ errors: [getErrorMessage('aria-checked', 'complementary')]
+ },
+ {
+ code: '',
+ errors: [getErrorMessage('aria-expanded', 'list')]
+ }
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-expanded', 'group')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-expanded', 'dialog')]
+ // },
+ // {
+ // code: '',
+ // errors: [getErrorMessage('aria-expanded', 'complementary')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-expanded', 'article')]
+ // },
+ // {
+ // code: '',
+ // errors: [getErrorMessage('aria-expanded', 'document')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-expanded', 'listitem')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-expanded', 'navigation')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-expanded', 'list')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-expanded', 'status')]
+ // },
+ // {
+ // code: '',
+ // errors: [getErrorMessage('aria-expanded', 'region')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-expanded', 'rowgroup')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-expanded', 'rowgroup')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-expanded', 'rowgroup')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-invalid', 'radio')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-selected', 'radio')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-haspopup', 'radio')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-haspopup', 'checkbox')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-invalid', 'button')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-invalid', 'button')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-invalid', 'button')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-invalid', 'button')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-invalid', 'menuitem')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-selected', 'menuitemradio')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-haspopup', 'toolbar')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-invalid', 'toolbar')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-expanded', 'toolbar')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-invalid', 'link')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-invalid', 'link')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-invalid', 'link')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-checked', 'link')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-label', 'generic')]
+ // },
+ // {
+ // code: ' ',
+ // errors: [getErrorMessage('aria-labelledby', 'generic')]
+ // },
+ // {
+ // code: '
',
+ // errors: [getErrorMessage('aria-label', 'generic')]
+ // },
+ // {
+ // code: '
',
+ // errors: [getErrorMessage('aria-labelledby', 'generic')]
+ // }
+ ]
+})
diff --git a/tests/utils/object-map.js b/tests/utils/object-map.js
new file mode 100644
index 00000000..98a888f0
--- /dev/null
+++ b/tests/utils/object-map.js
@@ -0,0 +1,180 @@
+// @ts-check
+const ObjectMap = require('../../lib/utils/object-map')
+const {describe, it} = require('mocha')
+const expect = require('chai').expect
+
+describe('ObjectMap', function () {
+ it('constructs an ObjectMap', function () {
+ const objectMap = new ObjectMap()
+ expect(objectMap instanceof ObjectMap).to.be.true
+ })
+
+ it('extends Map', function () {
+ const objectMap = new ObjectMap()
+ expect(objectMap instanceof Map).to.be.true
+ })
+
+ it('populates data from constructor argument', function () {
+ const iterable = [[{name: 'form'}, ['form']]]
+ const objectMap = new ObjectMap(iterable)
+ expect(Array.from(objectMap.entries())).to.deep.equal(iterable)
+ })
+
+ it('clears data', function () {
+ const iterable = [[{name: 'form'}, ['form']]]
+ const objectMap = new ObjectMap(iterable)
+ objectMap.clear()
+ expect(Array.from(objectMap.entries())).to.be.empty
+ })
+
+ it('deletes data that exists', function () {
+ const iterable = [[{name: 'form'}, ['form']]]
+ const objectMap = new ObjectMap(iterable)
+ const returnValue = objectMap.delete({name: 'form'})
+ expect(Array.from(objectMap.entries())).to.be.empty
+ expect(returnValue).to.be.true
+ })
+
+ it('doesn’t delete data that doesn’t exist', function () {
+ const iterable = [[{name: 'form'}, ['form']]]
+ const objectMap = new ObjectMap(iterable)
+ const returnValue = objectMap.delete({name: ''})
+ expect(Array.from(objectMap.entries())).to.deep.equal(iterable)
+ expect(returnValue).to.be.false
+ })
+
+ it('lists entries that exist', function () {
+ const iterable = [
+ [{name: 'form'}, ['form']],
+ [{name: 'span'}, ['generic']]
+ ]
+ const objectMap = new ObjectMap(iterable)
+ const iterator = objectMap.entries()
+ expect(typeof iterator[Symbol.iterator]).to.equal('function')
+ expect(iterator.next()).to.deep.equal({value: iterable[0], done: false})
+ expect(iterator.next()).to.deep.equal({value: iterable[1], done: false})
+ expect(iterator.next()).to.deep.equal({value: undefined, done: true})
+ })
+
+ it('doesn’t list entries that don’t exist', function () {
+ const objectMap = new ObjectMap()
+ const iterator = objectMap.entries()
+ expect(typeof iterator[Symbol.iterator]).to.equal('function')
+ expect(iterator.next()).to.deep.equal({value: undefined, done: true})
+ })
+
+ it('finds key that exists', function () {
+ const iterable = [[{name: 'form'}, ['form']]]
+ const objectMap = new ObjectMap(iterable)
+ expect(objectMap.has({name: 'form'})).to.be.true
+ })
+
+ it('finds key that exists in a different order', function () {
+ const iterable = [[{name: 'form', attributes: [{name: 'action', value: 'POST'}]}, ['form']]]
+ const objectMap = new ObjectMap(iterable)
+ expect(objectMap.has({attributes: [{value: 'POST', name: 'action'}], name: 'form'})).to.be.true
+ })
+
+ it('doesn’t find key that doesn’t exist', function () {
+ const iterable = [[{name: 'form'}, ['form']]]
+ const objectMap = new ObjectMap(iterable)
+ expect(objectMap.has({name: ''})).to.be.false
+ })
+
+ it('doesn’t find key when none exist', function () {
+ const objectMap = new ObjectMap()
+ expect(objectMap.has({name: ''})).to.be.false
+ })
+
+ it('calls callback for each datum', function () {
+ const iterable = [
+ [{name: 'form'}, ['form']],
+ [{name: 'span'}, ['generic']]
+ ]
+ const objectMap = new ObjectMap(iterable)
+ const values = []
+ const keys = []
+ // eslint-disable-next-line github/array-foreach
+ objectMap.forEach((value, key) => {
+ values.push(value)
+ keys.push(key)
+ })
+ expect(values).to.deep.equal([['form'], ['generic']])
+ expect(keys).to.deep.equal([{name: 'form'}, {name: 'span'}])
+ })
+
+ it('finds value given key that exists', function () {
+ const iterable = [[{name: 'form'}, ['form']]]
+ const objectMap = new ObjectMap(iterable)
+ expect(objectMap.get({name: 'form'})).to.deep.equal(['form'])
+ })
+
+ it('finds value given key that exists in a different order', function () {
+ const iterable = [[{name: 'form', attributes: [{name: 'action', value: 'POST'}]}, ['form']]]
+ const objectMap = new ObjectMap(iterable)
+ expect(objectMap.get({attributes: [{value: 'POST', name: 'action'}], name: 'form'})).to.deep.equal(['form'])
+ })
+
+ it('doesn’t find value given key that doesn’t exist', function () {
+ const iterable = [[{name: 'form'}, ['form']]]
+ const objectMap = new ObjectMap(iterable)
+ expect(objectMap.get({name: ''})).to.be.undefined
+ })
+
+ it('doesn’t find value when none exist', function () {
+ const objectMap = new ObjectMap()
+ expect(objectMap.get({name: ''})).to.be.undefined
+ })
+
+ it('lists keys', function () {
+ const iterable = [
+ [{name: 'form'}, ['form']],
+ [{name: 'span'}, ['generic']]
+ ]
+ const objectMap = new ObjectMap(iterable)
+ const iterator = objectMap.keys()
+ expect(typeof iterator[Symbol.iterator]).to.equal('function')
+ expect(iterator.next()).to.deep.equal({value: iterable[0][0], done: false})
+ expect(iterator.next()).to.deep.equal({value: iterable[1][0], done: false})
+ expect(iterator.next()).to.deep.equal({value: undefined, done: true})
+ })
+
+ it('sets value when none exist', function () {
+ const key = {name: 'form'}
+ const value = ['form']
+ const objectMap = new ObjectMap()
+ objectMap.set(key, value)
+ expect(Array.from(objectMap.entries())).to.deep.equal([[key, value]])
+ })
+
+ it('sets value when some exist', function () {
+ const iterable = [[{name: 'form'}, ['form']]]
+ const key = {name: 'span'}
+ const value = ['generic']
+ const objectMap = new ObjectMap(iterable)
+ objectMap.set(key, value)
+ expect(Array.from(objectMap.entries())).to.deep.equal([iterable[0], [key, value]])
+ })
+
+ it('replaces value under existing key', function () {
+ const iterable = [[{name: 'form'}, ['form']]]
+ const key = {name: 'form'}
+ const value = ['generic']
+ const objectMap = new ObjectMap(iterable)
+ objectMap.set(key, value)
+ expect(Array.from(objectMap.entries())).to.deep.equal([[key, value]])
+ })
+
+ it('lists values', function () {
+ const iterable = [
+ [{name: 'form'}, ['form']],
+ [{name: 'span'}, ['generic']]
+ ]
+ const objectMap = new ObjectMap(iterable)
+ const iterator = objectMap.values()
+ expect(typeof iterator[Symbol.iterator]).to.equal('function')
+ expect(iterator.next()).to.deep.equal({value: iterable[0][1], done: false})
+ expect(iterator.next()).to.deep.equal({value: iterable[1][1], done: false})
+ expect(iterator.next()).to.deep.equal({value: undefined, done: true})
+ })
+})
From 4c71d04f4a2e6db649e4cb4eb672542a1030c3b6 Mon Sep 17 00:00:00 2001
From: Clay Miller
Date: Sat, 17 Dec 2022 06:15:50 +0000
Subject: [PATCH 2/6] fix: Tests passing
---
lib/rules/role-supports-aria-props.js | 21 +-
tests/role-supports-aria-props.js | 285 +++++++++++++-------------
2 files changed, 160 insertions(+), 146 deletions(-)
diff --git a/lib/rules/role-supports-aria-props.js b/lib/rules/role-supports-aria-props.js
index e636a6cb..bc580c88 100644
--- a/lib/rules/role-supports-aria-props.js
+++ b/lib/rules/role-supports-aria-props.js
@@ -7,15 +7,22 @@ const ObjectMap = require('../utils/object-map')
// Clean-up `elementRoles` from `aria-query`
const elementRolesMap = new ObjectMap()
for (const [key, value] of elementRoles.entries()) {
+ // - Remove unused `constraints` key
delete key.constraints
key.attributes = key.attributes?.filter(attribute => !('constraints' in attribute))
+ // - Remove empty `attributes` key
if (!key.attributes || key.attributes?.length === 0) {
delete key.attributes
}
elementRolesMap.set(key, value)
}
+// - Remove insufficiently-disambiguated `menuitem` entry
elementRolesMap.delete({name: 'menuitem'})
+// - Disambiguate `menuitem` and `menu` roles by `type`
+elementRolesMap.set({name: 'menuitem', attributes: [{name: 'type', value: 'command'}]}, ['menuitem'])
+elementRolesMap.set({name: 'menuitem', attributes: [{name: 'type', value: 'radio'}]}, ['menuitemradio'])
elementRolesMap.set({name: 'menuitem', attributes: [{name: 'type', value: 'toolbar'}]}, ['toolbar'])
+elementRolesMap.set({name: 'menu', attributes: [{name: 'type', value: 'toolbar'}]}, ['toolbar'])
module.exports = {
meta: {
@@ -30,14 +37,18 @@ module.exports = {
create(context) {
return {
JSXOpeningElement(node) {
- // Get the element’s explicit or implicit role
+ // Assemble a key for looking-up the element’s role in the `elementRolesMap`
+ // - Get the element’s name
const key = {name: getElementType(context, node)}
+ // - Get the element’s disambiguating attributes
for (const prop of ['aria-expanded', 'type', 'size', 'role', 'href', 'multiple', 'scope']) {
+ // - Only provide `aria-expanded` when it’s required for disambiguation
+ if (prop === 'aria-expanded' && key.name !== 'summary') continue
const value = getPropValue(getProp(node.attributes, prop))
- if (!('attributes' in key)) {
- key.attributes = []
- }
if (value) {
+ if (!('attributes' in key)) {
+ key.attributes = []
+ }
if (prop === 'href') {
key.attributes.push({name: prop})
} else {
@@ -45,6 +56,7 @@ module.exports = {
}
}
}
+ // Get the element’s explicit or implicit role
const role = getPropValue(getProp(node.attributes, 'role')) ?? elementRolesMap.get(key)?.[0]
// Return early if role could not be determined
@@ -73,6 +85,7 @@ module.exports = {
prohibitedProps = Array.from(new Set(prohibitedProps))
for (const prop of node.attributes) {
+ // Return early if prohibited ARIA attribute is set to an ignorable value
if (getPropValue(prop) == null || prop.type === 'JSXSpreadAttribute') return
if (prohibitedProps?.includes(propName(prop))) {
diff --git a/tests/role-supports-aria-props.js b/tests/role-supports-aria-props.js
index 3d2bccc2..606f4024 100644
--- a/tests/role-supports-aria-props.js
+++ b/tests/role-supports-aria-props.js
@@ -1,4 +1,15 @@
// @ts-check
+
+// Tests in this file were adapted from https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/__tests__/src/rules/role-supports-aria-props-test.js, which was authored by Ethan Cohen and is distributed under the MIT license as follows:
+//
+// The MIT License (MIT) Copyright (c) 2016 Ethan Cohen
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
const rule = require('../lib/rules/role-supports-aria-props')
const RuleTester = require('eslint').RuleTester
@@ -91,9 +102,6 @@ ruleTester.run('role-supports-aria-props', rule, {
// this will have global
{code: ' '},
- // IMG TESTS - no implicit role
- {code: ' '},
-
// this will have role of `img`
{code: ' '},
@@ -114,9 +122,6 @@ ruleTester.run('role-supports-aria-props', rule, {
{code: ' '},
{code: ' '},
- // this will have global
- {code: ' '},
-
// MENUITEM TESTS
// when `type="command`, the implicit role is `menuitem`
{code: ' '},
@@ -370,142 +375,138 @@ ruleTester.run('role-supports-aria-props', rule, {
{
code: '',
errors: [getErrorMessage('aria-expanded', 'list')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-expanded', 'group')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-expanded', 'dialog')]
+ },
+ {
+ code: '',
+ errors: [getErrorMessage('aria-expanded', 'complementary')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-expanded', 'article')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-expanded', 'document')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-expanded', 'listitem')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-expanded', 'navigation')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-expanded', 'list')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-expanded', 'status')]
+ },
+ {
+ code: '',
+ errors: [getErrorMessage('aria-expanded', 'region')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-expanded', 'rowgroup')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-expanded', 'rowgroup')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-expanded', 'rowgroup')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-invalid', 'radio')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-selected', 'radio')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-haspopup', 'radio')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-haspopup', 'checkbox')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-invalid', 'button')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-invalid', 'button')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-invalid', 'button')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-invalid', 'button')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-invalid', 'menuitem')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-selected', 'menuitemradio')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-haspopup', 'toolbar')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-invalid', 'toolbar')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-expanded', 'toolbar')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-invalid', 'link')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-invalid', 'link')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-invalid', 'link')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-label', 'generic')]
+ },
+ {
+ code: ' ',
+ errors: [getErrorMessage('aria-labelledby', 'generic')]
+ },
+ {
+ code: '
',
+ errors: [getErrorMessage('aria-label', 'generic')]
+ },
+ {
+ code: '
',
+ errors: [getErrorMessage('aria-labelledby', 'generic')]
}
- // {
- // code: ' ',
- // errors: [getErrorMessage('aria-expanded', 'group')]
- // },
- // {
- // code: ' ',
- // errors: [getErrorMessage('aria-expanded', 'dialog')]
- // },
- // {
- // code: '',
- // errors: [getErrorMessage('aria-expanded', 'complementary')]
- // },
- // {
- // code: ' ',
- // errors: [getErrorMessage('aria-expanded', 'article')]
- // },
- // {
- // code: '