diff --git a/__tests__/src/util/isDOMElement-test.js b/__tests__/src/util/isDOMElement-test.js index 1245805bc..ff1e24e49 100644 --- a/__tests__/src/util/isDOMElement-test.js +++ b/__tests__/src/util/isDOMElement-test.js @@ -4,11 +4,9 @@ import { elementType } from 'jsx-ast-utils'; import isDOMElement from '../../../src/util/isDOMElement'; import JSXElementMock from '../../../__mocks__/JSXElementMock'; -const domElements = [...dom.keys()]; - describe('isDOMElement', () => { describe('DOM elements', () => { - domElements.forEach((el) => { + dom.forEach((_, el) => { it(`should identify ${el} as a DOM element`, () => { const element = JSXElementMock(el); expect(isDOMElement(elementType(element.openingElement))) diff --git a/__tests__/src/util/isFocusable-test.js b/__tests__/src/util/isFocusable-test.js index 920891f65..76819c297 100644 --- a/__tests__/src/util/isFocusable-test.js +++ b/__tests__/src/util/isFocusable-test.js @@ -9,7 +9,7 @@ import { import JSXAttributeMock from '../../../__mocks__/JSXAttributeMock'; function mergeTabIndex(index, attributes) { - return [...attributes, JSXAttributeMock('tabIndex', index)]; + return [].concat(attributes, JSXAttributeMock('tabIndex', index)); } describe('isFocusable', () => { diff --git a/package.json b/package.json index 89f7da2f9..57310782d 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "axobject-query": "^3.2.1", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", + "es-iterator-helpers": "^1.0.12", "has": "^1.0.3", "jsx-ast-utils": "^3.3.5", "language-tags": "=1.0.5", diff --git a/src/rules/aria-activedescendant-has-tabindex.js b/src/rules/aria-activedescendant-has-tabindex.js index 0f229e2fb..e240ec0bb 100644 --- a/src/rules/aria-activedescendant-has-tabindex.js +++ b/src/rules/aria-activedescendant-has-tabindex.js @@ -18,8 +18,6 @@ const errorMessage = 'An element that manages focus with `aria-activedescendant` const schema = generateObjSchema(); -const domElements = [...dom.keys()]; - export default { meta: { docs: { @@ -42,7 +40,7 @@ export default { const type = elementType(node); // Do not test higher level JSX components, as we do not know what // low-level DOM element this maps to. - if (domElements.indexOf(type) === -1) { + if (!dom.has(type)) { return; } const tabIndex = getTabIndex(getProp(attributes, 'tabIndex')); diff --git a/src/rules/aria-role.js b/src/rules/aria-role.js index 4fa499b57..6b85ae855 100644 --- a/src/rules/aria-role.js +++ b/src/rules/aria-role.js @@ -9,6 +9,9 @@ import { dom, roles } from 'aria-query'; import { getLiteralPropValue, propName } from 'jsx-ast-utils'; +import iterFrom from 'es-iterator-helpers/Iterator.from'; +import filter from 'es-iterator-helpers/Iterator.prototype.filter'; + import getElementType from '../util/getElementType'; import { generateObjSchema } from '../util/schemas'; @@ -28,7 +31,7 @@ const schema = generateObjSchema({ }, }); -const validRoles = new Set([...roles.keys()].filter((role) => roles.get(role).abstract === false)); +const validRoles = new Set(filter(iterFrom(roles.keys()), (role) => roles.get(role).abstract === false)); export default { meta: { diff --git a/src/rules/click-events-have-key-events.js b/src/rules/click-events-have-key-events.js index aa76349de..6c9cf90fe 100644 --- a/src/rules/click-events-have-key-events.js +++ b/src/rules/click-events-have-key-events.js @@ -9,7 +9,6 @@ import { dom } from 'aria-query'; import { getProp, hasAnyProp } from 'jsx-ast-utils'; -import includes from 'array-includes'; import { generateObjSchema } from '../util/schemas'; import getElementType from '../util/getElementType'; import isHiddenFromScreenReader from '../util/isHiddenFromScreenReader'; @@ -19,7 +18,6 @@ import isPresentationRole from '../util/isPresentationRole'; const errorMessage = 'Visible, non-interactive elements with click handlers must have at least one keyboard listener.'; const schema = generateObjSchema(); -const domElements = [...dom.keys()]; export default { meta: { @@ -42,7 +40,7 @@ export default { const type = elementType(node); const requiredProps = ['onkeydown', 'onkeyup', 'onkeypress']; - if (!includes(domElements, type)) { + if (!dom.has(type)) { // Do not test higher level JSX components, as we do not know what // low-level DOM element this maps to. return; diff --git a/src/rules/interactive-supports-focus.js b/src/rules/interactive-supports-focus.js index 2dec63006..e872c5d10 100644 --- a/src/rules/interactive-supports-focus.js +++ b/src/rules/interactive-supports-focus.js @@ -36,12 +36,12 @@ import getTabIndex from '../util/getTabIndex'; // ---------------------------------------------------------------------------- const schema = generateObjSchema({ + // TODO: convert to use iterFilter and iterFrom tabbable: enumArraySchema([...roles.keys()].filter((name) => ( !roles.get(name).abstract && roles.get(name).superClass.some((klasses) => includes(klasses, 'widget')) ))), }); -const domElements = [...dom.keys()]; const interactiveProps = [].concat( eventHandlersByType.mouse, @@ -69,7 +69,7 @@ export default ({ const hasInteractiveProps = hasAnyProp(attributes, interactiveProps); const hasTabindex = getTabIndex(getProp(attributes, 'tabIndex')) !== undefined; - if (!includes(domElements, type)) { + if (!dom.has(type)) { // Do not test higher level JSX components, as we do not know what // low-level DOM element this maps to. return; diff --git a/src/rules/no-interactive-element-to-noninteractive-role.js b/src/rules/no-interactive-element-to-noninteractive-role.js index cc70b7cfb..d98b9f688 100644 --- a/src/rules/no-interactive-element-to-noninteractive-role.js +++ b/src/rules/no-interactive-element-to-noninteractive-role.js @@ -27,8 +27,6 @@ import isPresentationRole from '../util/isPresentationRole'; const errorMessage = 'Interactive elements should not be assigned non-interactive roles.'; -const domElements = [...dom.keys()]; - export default ({ meta: { docs: { @@ -62,7 +60,7 @@ export default ({ const type = elementType(node); const role = getLiteralPropValue(getProp(node.attributes, 'role')); - if (!includes(domElements, type)) { + if (!dom.has(type)) { // Do not test higher level JSX components, as we do not know what // low-level DOM element this maps to. return; diff --git a/src/rules/no-noninteractive-element-interactions.js b/src/rules/no-noninteractive-element-interactions.js index ee4b874a0..9f9dfe13e 100644 --- a/src/rules/no-noninteractive-element-interactions.js +++ b/src/rules/no-noninteractive-element-interactions.js @@ -32,7 +32,6 @@ import isPresentationRole from '../util/isPresentationRole'; const errorMessage = 'Non-interactive elements should not be assigned mouse or keyboard event listeners.'; -const domElements = [...dom.keys()]; const defaultInteractiveProps = [].concat( eventHandlersByType.focus, eventHandlersByType.image, @@ -72,7 +71,7 @@ export default ({ && getPropValue(getProp(attributes, prop)) != null )); - if (!includes(domElements, type)) { + if (!dom.has(type)) { // Do not test higher level JSX components, as we do not know what // low-level DOM element this maps to. return; diff --git a/src/rules/no-noninteractive-element-to-interactive-role.js b/src/rules/no-noninteractive-element-to-interactive-role.js index 601039aa1..00f490870 100644 --- a/src/rules/no-noninteractive-element-to-interactive-role.js +++ b/src/rules/no-noninteractive-element-to-interactive-role.js @@ -25,8 +25,6 @@ import isInteractiveRole from '../util/isInteractiveRole'; const errorMessage = 'Non-interactive elements should not be assigned interactive roles.'; -const domElements = [...dom.keys()]; - export default ({ meta: { docs: { @@ -60,7 +58,7 @@ export default ({ const type = elementType(node); const role = getExplicitRole(type, node.attributes); - if (!includes(domElements, type)) { + if (!dom.has(type)) { // Do not test higher level JSX components, as we do not know what // low-level DOM element this maps to. return; diff --git a/src/rules/no-static-element-interactions.js b/src/rules/no-static-element-interactions.js index e48c4bd0f..038d7c1e4 100644 --- a/src/rules/no-static-element-interactions.js +++ b/src/rules/no-static-element-interactions.js @@ -16,7 +16,6 @@ import { hasProp, } from 'jsx-ast-utils'; import type { JSXOpeningElement } from 'ast-types-flow'; -import includes from 'array-includes'; import type { ESLintConfig, ESLintContext, ESLintVisitorSelectorConfig } from '../../flow/eslint'; import { arraySchema, generateObjSchema } from '../util/schemas'; import getElementType from '../util/getElementType'; @@ -31,7 +30,6 @@ import isPresentationRole from '../util/isPresentationRole'; const errorMessage = 'Avoid non-native interactive elements. If using native HTML is not possible, add an appropriate role and support for tabbing, mouse, keyboard, and touch inputs to an interactive content element.'; -const domElements = [...dom.keys()]; const defaultInteractiveProps = [].concat( eventHandlersByType.focus, eventHandlersByType.keyboard, @@ -69,7 +67,7 @@ export default ({ && getPropValue(getProp(attributes, prop)) != null )); - if (!includes(domElements, type)) { + if (!dom.has(type)) { // Do not test higher level JSX components, as we do not know what // low-level DOM element this maps to. return; diff --git a/src/util/isAbstractRole.js b/src/util/isAbstractRole.js index 75bc48c22..723ff1bc8 100644 --- a/src/util/isAbstractRole.js +++ b/src/util/isAbstractRole.js @@ -3,16 +3,17 @@ import { roles, } from 'aria-query'; import { getProp, getLiteralPropValue } from 'jsx-ast-utils'; +import iterFrom from 'es-iterator-helpers/Iterator.from'; +import filter from 'es-iterator-helpers/Iterator.prototype.filter'; -const abstractRoles = new Set([...roles.keys()] - .filter((role) => roles.get(role).abstract)); +const abstractRoles = new Set(filter(iterFrom(roles.keys()), (role) => roles.get(role).abstract)); -const DOMElements = [...dom.keys()]; +const DOMElements = new Set(dom.keys()); const isAbstractRole = (tagName, attributes) => { // Do not test higher level JSX components, as we do not know what // low-level DOM element this maps to. - if (DOMElements.indexOf(tagName) === -1) { + if (!DOMElements.has(tagName)) { return false; } diff --git a/src/util/isDOMElement.js b/src/util/isDOMElement.js index 216ed51ca..82e286d4a 100644 --- a/src/util/isDOMElement.js +++ b/src/util/isDOMElement.js @@ -2,15 +2,12 @@ * @flow */ import { dom } from 'aria-query'; -import includes from 'array-includes'; - -const domElements = [...dom.keys()]; /** * Returns boolean indicating whether the given element is a DOM element. */ const isDOMElement = ( tagName: string, -): boolean => includes(domElements, tagName); +): boolean => dom.has(tagName); export default isDOMElement; diff --git a/src/util/isInteractiveElement.js b/src/util/isInteractiveElement.js index 3475d678c..0f9714540 100644 --- a/src/util/isInteractiveElement.js +++ b/src/util/isInteractiveElement.js @@ -13,9 +13,13 @@ import { } from 'axobject-query'; import includes from 'array-includes'; import flatMap from 'array.prototype.flatmap'; +import iterFrom from 'es-iterator-helpers/Iterator.from'; +// import iterFlatMap from 'es-iterator-helpers/Iterator.prototype.flatMap'; +import filter from 'es-iterator-helpers/Iterator.prototype.filter'; +import some from 'es-iterator-helpers/Iterator.prototype.some'; + import attributesComparator from './attributesComparator'; -const domKeys = [...dom.keys()]; const roleKeys = [...roles.keys()]; const elementRoleEntries = [...elementRoles]; @@ -51,22 +55,24 @@ const interactiveRoles = new Set(roleKeys 'toolbar', )); -const nonInteractiveElementRoleSchemas = flatMap( +// TODO: convert to use iterFlatMap and iterFrom +const interactiveElementRoleSchemas = flatMap( elementRoleEntries, - ([elementSchema, roleSet]) => ([...roleSet].every((role): boolean => nonInteractiveRoles.has(role)) ? [elementSchema] : []), + ([elementSchema, rolesArr]) => (rolesArr.some((role): boolean => interactiveRoles.has(role)) ? [elementSchema] : []), ); -const interactiveElementRoleSchemas = flatMap( +// TODO: convert to use iterFlatMap and iterFrom +const nonInteractiveElementRoleSchemas = flatMap( elementRoleEntries, - ([elementSchema, roleSet]) => ([...roleSet].some((role): boolean => interactiveRoles.has(role)) ? [elementSchema] : []), + ([elementSchema, rolesArr]) => (rolesArr.every((role): boolean => nonInteractiveRoles.has(role)) ? [elementSchema] : []), ); -const interactiveAXObjects = new Set([...AXObjects.keys()] - .filter((name) => AXObjects.get(name).type === 'widget')); +const interactiveAXObjects = new Set(filter(iterFrom(AXObjects.keys()), (name) => AXObjects.get(name).type === 'widget')); +// TODO: convert to use iterFlatMap and iterFrom const interactiveElementAXObjectSchemas = flatMap( [...elementAXObjects], - ([elementSchema, AXObjectSet]) => ([...AXObjectSet].every((role): boolean => interactiveAXObjects.has(role)) ? [elementSchema] : []), + ([elementSchema, AXObjectsArr]) => (AXObjectsArr.every((role): boolean => interactiveAXObjects.has(role)) ? [elementSchema] : []), ); function checkIsInteractiveElement(tagName, attributes): boolean { @@ -78,18 +84,18 @@ function checkIsInteractiveElement(tagName, attributes): boolean { } // Check in elementRoles for inherent interactive role associations for // this element. - const isInherentInteractiveElement = interactiveElementRoleSchemas.some(elementSchemaMatcher); + const isInherentInteractiveElement = some(iterFrom(interactiveElementRoleSchemas), elementSchemaMatcher); if (isInherentInteractiveElement) { return true; } // Check in elementRoles for inherent non-interactive role associations for // this element. - const isInherentNonInteractiveElement = nonInteractiveElementRoleSchemas.some(elementSchemaMatcher); + const isInherentNonInteractiveElement = some(iterFrom(nonInteractiveElementRoleSchemas), elementSchemaMatcher); if (isInherentNonInteractiveElement) { return false; } // Check in elementAXObjects for AX Tree associations for this element. - const isInteractiveAXElement = interactiveElementAXObjectSchemas.some(elementSchemaMatcher); + const isInteractiveAXElement = some(iterFrom(interactiveElementAXObjectSchemas), elementSchemaMatcher); if (isInteractiveAXElement) { return true; } @@ -109,7 +115,7 @@ const isInteractiveElement = ( ): boolean => { // Do not test higher level JSX components, as we do not know what // low-level DOM element this maps to. - if (!includes(domKeys, tagName)) { + if (!dom.has(tagName)) { return false; } diff --git a/src/util/isNonInteractiveElement.js b/src/util/isNonInteractiveElement.js index 85e1037a9..a37326dab 100644 --- a/src/util/isNonInteractiveElement.js +++ b/src/util/isNonInteractiveElement.js @@ -14,6 +14,10 @@ import { import type { Node } from 'ast-types-flow'; import includes from 'array-includes'; import flatMap from 'array.prototype.flatmap'; +import iterFrom from 'es-iterator-helpers/Iterator.from'; +// import iterFlatMap from 'es-iterator-helpers/Iterator.prototype.flatMap'; +import filter from 'es-iterator-helpers/Iterator.prototype.filter'; +import some from 'es-iterator-helpers/Iterator.prototype.some'; import attributesComparator from './attributesComparator'; @@ -62,22 +66,24 @@ const interactiveRoles = new Set(roleKeys 'toolbar', )); -const nonInteractiveElementRoleSchemas = flatMap( +// TODO: convert to use iterFlatMap and iterFrom +const interactiveElementRoleSchemas = flatMap( elementRoleEntries, - ([elementSchema, roleSet]) => ([...roleSet].every((role): boolean => nonInteractiveRoles.has(role)) ? [elementSchema] : []), + ([elementSchema, rolesArr]) => (rolesArr.some((role): boolean => interactiveRoles.has(role)) ? [elementSchema] : []), ); -const interactiveElementRoleSchemas = flatMap( +// TODO: convert to use iterFlatMap and iterFrom +const nonInteractiveElementRoleSchemas = flatMap( elementRoleEntries, - ([elementSchema, roleSet]) => ([...roleSet].some((role): boolean => interactiveRoles.has(role)) ? [elementSchema] : []), + ([elementSchema, rolesArr]) => (rolesArr.every((role): boolean => nonInteractiveRoles.has(role)) ? [elementSchema] : []), ); -const nonInteractiveAXObjects = new Set([...AXObjects.keys()] - .filter((name) => includes(['window', 'structure'], AXObjects.get(name).type))); +const nonInteractiveAXObjects = new Set(filter(iterFrom(AXObjects.keys()), (name) => includes(['window', 'structure'], AXObjects.get(name).type))); +// TODO: convert to use iterFlatMap and iterFrom const nonInteractiveElementAXObjectSchemas = flatMap( [...elementAXObjects], - ([elementSchema, AXObjectSet]) => ([...AXObjectSet].every((role): boolean => nonInteractiveAXObjects.has(role)) ? [elementSchema] : []), + ([elementSchema, AXObjectsArr]) => (AXObjectsArr.every((role): boolean => nonInteractiveAXObjects.has(role)) ? [elementSchema] : []), ); function checkIsNonInteractiveElement(tagName, attributes): boolean { @@ -89,21 +95,18 @@ function checkIsNonInteractiveElement(tagName, attributes): boolean { } // Check in elementRoles for inherent non-interactive role associations for // this element. - const isInherentNonInteractiveElement = nonInteractiveElementRoleSchemas - .some(elementSchemaMatcher); + const isInherentNonInteractiveElement = some(iterFrom(nonInteractiveElementRoleSchemas), elementSchemaMatcher); if (isInherentNonInteractiveElement) { return true; } // Check in elementRoles for inherent interactive role associations for // this element. - const isInherentInteractiveElement = interactiveElementRoleSchemas - .some(elementSchemaMatcher); + const isInherentInteractiveElement = some(iterFrom(interactiveElementRoleSchemas), elementSchemaMatcher); if (isInherentInteractiveElement) { return false; } // Check in elementAXObjects for AX Tree associations for this element. - const isNonInteractiveAXElement = nonInteractiveElementAXObjectSchemas - .some(elementSchemaMatcher); + const isNonInteractiveAXElement = some(iterFrom(nonInteractiveElementAXObjectSchemas), elementSchemaMatcher); if (isNonInteractiveAXElement) { return true; }