forked from jsx-eslint/eslint-plugin-jsx-a11y
/
getAccessibleChildText.js
63 lines (54 loc) · 2.13 KB
/
getAccessibleChildText.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// @flow
import type { JSXElement, JSXOpeningElement, Node } from 'ast-types-flow';
import { getProp, getLiteralPropValue } from 'jsx-ast-utils';
import isHiddenFromScreenReader from './isHiddenFromScreenReader';
/**
* Returns a new "standardized" string: all whitespace is collapsed to one space,
* and the string is lowercase
* @param {string} input
* @returns lowercase, single-spaced, punctuation-stripped, trimmed string
*/
function standardizeSpaceAndCase(input: string): string {
return input
.trim()
.replace(/[,.?¿!‽¡;:]/g, '') // strip punctuation
.replace(/\s\s+/g, ' ') // collapse spaces
.toLowerCase();
}
/**
* Returns the (recursively-defined) accessible child text of a node, which (in-order) is:
* 1. The element's aria-label
* 2. If the element is a direct literal, the literal value
* 3. Otherwise, merge all of its children
* @param {JSXElement} node - node to traverse
* @returns child text as a string
*/
export default function getAccessibleChildText(node: JSXElement, elementType: (JSXOpeningElement) => string): string {
const ariaLabel = getLiteralPropValue(getProp(node.openingElement.attributes, 'aria-label'));
// early escape-hatch when aria-label is applied
if (ariaLabel) return standardizeSpaceAndCase(ariaLabel);
// early-return if alt prop exists and is an image
const altTag = getLiteralPropValue(getProp(node.openingElement.attributes, 'alt'));
if (elementType(node.openingElement) === 'img' && altTag) return standardizeSpaceAndCase(altTag);
// skip if aria-hidden is true
if (
isHiddenFromScreenReader(
elementType(node.openingElement),
node.openingElement.attributes,
)
) {
return '';
}
const rawChildText = node.children
.map((currentNode: Node): string => {
// $FlowFixMe JSXText is missing in ast-types-flow
if (currentNode.type === 'Literal' || currentNode.type === 'JSXText') {
return String(currentNode.value);
}
if (currentNode.type === 'JSXElement') {
return getAccessibleChildText(currentNode, elementType);
}
return '';
}).join(' ');
return standardizeSpaceAndCase(rawChildText);
}