-
-
Notifications
You must be signed in to change notification settings - Fork 636
/
Copy pathmayHaveAccessibleLabel.js
93 lines (89 loc) · 2.52 KB
/
mayHaveAccessibleLabel.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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/**
* Returns true if a labelling element is found or if it cannot determine if
* a label is present because of expression containers or spread attributes.
* A false return value means that the node definitely does not have a label,
* but a true return return value means that the node may or may not have a
* label.
*
* @flow
*/
import includes from 'array-includes';
import { getPropValue, propName } from 'jsx-ast-utils';
import type { JSXOpeningElement, Node } from 'ast-types-flow';
function tryTrim(value: any) {
return typeof value === 'string' ? value.trim() : value;
}
function hasLabellingProp(
openingElement: JSXOpeningElement,
additionalLabellingProps?: Array<string> = [],
) {
const labellingProps = [].concat(
'alt', // Assume alt is used correctly on an image
'aria-label',
'aria-labelledby',
additionalLabellingProps,
);
return openingElement.attributes.some((attribute): boolean => {
// We must assume that a spread value contains a labelling prop.
if (attribute.type !== 'JSXAttribute') {
return true;
}
// Attribute matches.
if (
includes(labellingProps, propName(attribute))
&& !!tryTrim(getPropValue(attribute))
) {
return true;
}
return false;
});
}
export default function mayHaveAccessibleLabel(
root: Node,
maxDepth: number = 1,
additionalLabellingProps?: Array<string> = [],
): boolean {
function checkElement(
node: Node,
depth: number,
): boolean {
// Bail when maxDepth is exceeded.
if (depth > maxDepth) {
return false;
}
// Check for literal text.
if (node.type === 'Literal' && !!tryTrim(node.value)) {
return true;
}
// Assume an expression container renders a label. It is the best we can
// do in this case.
if (node.type === 'JSXExpressionContainer') {
return true;
}
// Check for JSXText.
// $FlowFixMe Remove after updating ast-types-flow
if (node.type === 'JSXText' && !!tryTrim(node.value)) {
return true;
}
// Check for labelling props.
if (
node.openingElement
/* $FlowFixMe */
&& hasLabellingProp(node.openingElement, additionalLabellingProps)
) {
return true;
}
// Recurse into the child element nodes.
if (node.children) {
/* $FlowFixMe */
for (let i = 0; i < node.children.length; i += 1) {
/* $FlowFixMe */
if (checkElement(node.children[i], depth + 1)) {
return true;
}
}
}
return false;
}
return checkElement(root, 0);
}