Skip to content

Commit d6b234d

Browse files
author
Ethan Cohen
committed
[new] - create convention when null is explicit value for literal extraction.
I.E. <div prop={null} /> will return “null” Also, refactor valid-aria-role to use new getLiteralAttributeValue API
1 parent 7568586 commit d6b234d

File tree

4 files changed

+207
-47
lines changed

4 files changed

+207
-47
lines changed

src/rules/valid-aria-role.js

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,50 +9,36 @@
99
// ----------------------------------------------------------------------------
1010

1111
import validRoleTypes from '../util/attributes/role';
12-
import getAttributeValue from '../util/getAttributeValue';
12+
import { getLiteralAttributeValue } from '../util/getAttributeValue';
1313

1414
const errorMessage = 'Elements with ARIA roles must use a valid, non-abstract ARIA role.';
1515

16-
const report = (context, node) => context.report({
17-
node,
18-
message: errorMessage
19-
});
20-
2116
module.exports = context => ({
2217
JSXAttribute: attribute => {
2318
const normalizedName = attribute.name.name.toUpperCase();
2419
if (normalizedName !== 'ROLE') {
2520
return;
2621
}
2722

28-
const normalizedType = attribute.value === null ? 'NULL' : attribute.value.type.toUpperCase();
29-
30-
// Only check literals, as we cannot enforce variables representing role types.
31-
// Check expression containers to determine null or undefined values.
32-
if (normalizedType === 'JSXEXPRESSIONCONTAINER') {
33-
const expressionValue = getAttributeValue(attribute);
34-
const isUndefinedOrNull = expressionValue === undefined || expressionValue === null;
35-
36-
if (isUndefinedOrNull) {
37-
report(context, attribute);
38-
}
23+
const value = getLiteralAttributeValue(attribute);
3924

40-
return;
41-
} else if (normalizedType === 'NULL') {
42-
// Report when <div role /> -- this assumes property truthiness, which is not a valid role.
43-
report(context, attribute);
44-
return;
45-
} else if (normalizedType !== 'LITERAL') {
25+
// If value is undefined, then the role attribute will be dropped in the DOM.
26+
// If value is null, then getLiteralAttributeValue is telling us that the value isn't in the form of a literal.
27+
if (value === undefined || value === null) {
4628
return;
4729
}
4830

49-
// If value is a literal.
50-
const normalizedValues = attribute.value.value.toUpperCase().split(" ");
51-
const isValid = normalizedValues.every(value => validRoleTypes.indexOf(value) > -1);
31+
const normalizedValues = `${value}`.toUpperCase().split(" ");
32+
const isValid = normalizedValues.every(value => Object.keys(validRoleTypes).indexOf(value) > -1);
5233

53-
if (isValid === false) {
54-
report(context, attribute);
34+
if (isValid === true) {
35+
return;
5536
}
37+
38+
context.report({
39+
node: attribute,
40+
message: errorMessage
41+
});
5642
}
5743
});
5844

src/util/attributes/role.json

Lines changed: 185 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,185 @@
1-
[
2-
"ALERT", "ALERTDIALOG", "APPLICATION", "ARTICLE",
3-
"BANNER", "BUTTON", "CHECKBOX", "COLUMNHEADER",
4-
"COMBOBOX", "COMPLEMENTARY",
5-
"CONTENTINFO", "DEFINITION", "DIALOG", "DIRECTORY",
6-
"DOCUMENT", "FORM", "GRID", "GRIDCELL",
7-
"GROUP", "HEADING", "IMG", "LINK", "LIST", "LISTBOX",
8-
"LISTITEM", "LOG", "MAIN", "MARQUEE",
9-
"MATH", "MENU", "MENUBAR", "MENUITEM",
10-
"MENUITEMCHECKBOX", "MENUITEMRADIO", "NAVIGATION", "NOTE",
11-
"OPTION", "PRESENTATION", "PROGRESSBAR", "RADIO",
12-
"RADIOGROUP", "REGION", "ROW", "ROWGROUP", "ROWHEADER",
13-
"SCROLLBAR", "SEARCH", "SEPARATOR", "SLIDER",
14-
"SPINBUTTON", "STATUS", "TAB", "TABLIST", "TABPANEL",
15-
"TEXTBOX", "TIMER", "TOOLBAR", "TOOLTIP",
16-
"TREE", "TREEGRID", "TREEITEM"
17-
]
1+
{
2+
"ALERT": {
3+
"requiredProps": []
4+
},
5+
"ALERTDIALOG": {
6+
"requiredProps": []
7+
},
8+
"APPLICATION": {
9+
"requiredProps": []
10+
},
11+
"ARTICLE": {
12+
"requiredProps": []
13+
},
14+
"BANNER": {
15+
"requiredProps": []
16+
},
17+
"BUTTON": {
18+
"requiredProps": []
19+
},
20+
"CHECKBOX": {
21+
"requiredProps": []
22+
},
23+
"COLUMNHEADER": {
24+
"requiredProps": []
25+
},
26+
"COMBOBOX": {
27+
"requiredProps": []
28+
},
29+
"COMPLEMENTARY": {
30+
"requiredProps": []
31+
},
32+
"CONTENTINFO": {
33+
"requiredProps": []
34+
},
35+
"DEFINITION": {
36+
"requiredProps": []
37+
},
38+
"DIALOG": {
39+
"requiredProps": []
40+
},
41+
"DIRECTORY": {
42+
"requiredProps": []
43+
},
44+
"DOCUMENT": {
45+
"requiredProps": []
46+
},
47+
"FORM": {
48+
"requiredProps": []
49+
},
50+
"GRID": {
51+
"requiredProps": []
52+
},
53+
"GRIDCELL": {
54+
"requiredProps": []
55+
},
56+
"GROUP": {
57+
"requiredProps": []
58+
},
59+
"HEADING": {
60+
"requiredProps": []
61+
},
62+
"IMG": {
63+
"requiredProps": []
64+
},
65+
"LINK": {
66+
"requiredProps": []
67+
},
68+
"LIST": {
69+
"requiredProps": []
70+
},
71+
"LISTBOX": {
72+
"requiredProps": []
73+
},
74+
"LISTITEM": {
75+
"requiredProps": []
76+
},
77+
"LOG": {
78+
"requiredProps": []
79+
},
80+
"MAIN": {
81+
"requiredProps": []
82+
},
83+
"MARQUEE": {
84+
"requiredProps": []
85+
},
86+
"MATH": {
87+
"requiredProps": []
88+
},
89+
"MENU": {
90+
"requiredProps": []
91+
},
92+
"MENUBAR": {
93+
"requiredProps": []
94+
},
95+
"MENUITEM": {
96+
"requiredProps": []
97+
},
98+
"MENUITEMCHECKBOX": {
99+
"requiredProps": []
100+
},
101+
"MENUITEMRADIO": {
102+
"requiredProps": []
103+
},
104+
"NAVIGATION": {
105+
"requiredProps": []
106+
},
107+
"NOTE": {
108+
"requiredProps": []
109+
},
110+
"OPTION": {
111+
"requiredProps": []
112+
},
113+
"PRESENTATION": {
114+
"requiredProps": []
115+
},
116+
"PROGRESSBAR": {
117+
"requiredProps": []
118+
},
119+
"RADIO": {
120+
"requiredProps": []
121+
},
122+
"RADIOGROUP": {
123+
"requiredProps": []
124+
},
125+
"REGION": {
126+
"requiredProps": []
127+
},
128+
"ROW": {
129+
"requiredProps": []
130+
},
131+
"ROWGROUP": {
132+
"requiredProps": []
133+
},
134+
"ROWHEADER": {
135+
"requiredProps": []
136+
},
137+
"SCROLLBAR": {
138+
"requiredProps": []
139+
},
140+
"SEARCH": {
141+
"requiredProps": []
142+
},
143+
"SEPARATOR": {
144+
"requiredProps": []
145+
},
146+
"SLIDER": {
147+
"requiredProps": []
148+
},
149+
"SPINBUTTON": {
150+
"requiredProps": []
151+
},
152+
"STATUS": {
153+
"requiredProps": []
154+
},
155+
"TAB": {
156+
"requiredProps": []
157+
},
158+
"TABLIST": {
159+
"requiredProps": []
160+
},
161+
"TABPANEL": {
162+
"requiredProps": []
163+
},
164+
"TEXTBOX": {
165+
"requiredProps": []
166+
},
167+
"TIMER": {
168+
"requiredProps": []
169+
},
170+
"TOOLBAR": {
171+
"requiredProps": []
172+
},
173+
"TOOLTIP": {
174+
"requiredProps": []
175+
},
176+
"TREE": {
177+
"requiredProps": []
178+
},
179+
"TREEGRID": {
180+
"requiredProps": []
181+
},
182+
"TREEITEM": {
183+
"requiredProps": []
184+
}
185+
}

src/util/values/expressions/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ const TYPES = {
2424
const noop = () => null;
2525

2626
const LITERAL_TYPES = assign({}, TYPES, {
27+
Literal: value => {
28+
const extractedVal = TYPES.Literal(value);
29+
const isNull = extractedVal === null;
30+
// This will be convention for attributes that have null
31+
// value explicitly defined (<div prop={null} /> maps to "null").
32+
return isNull ? "null" : extractedVal;
33+
},
2734
Identifier: value => {
2835
const isUndefined = TYPES.Identifier(value) === undefined;
2936
return isUndefined ? undefined : null;

tests/src/rules/valid-aria-role.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const errorMessage = {
3333
import validRoleTypes from '../../../src/util/attributes/role';
3434

3535
// Create basic test cases using all valid role types.
36-
const basicValidityTests = validRoleTypes.map(role => ({
36+
const basicValidityTests = Object.keys(validRoleTypes).map(role => ({
3737
code: `<div role="${role.toLowerCase()}" />`,
3838
parserOptions
3939
}));
@@ -56,7 +56,6 @@ ruleTester.run('valid-aria-role', rule, {
5656
{ code: '<div role=""></div>', errors: [ errorMessage ], parserOptions },
5757
{ code: '<div role="tabpanel row foobar"></div>', errors: [ errorMessage ], parserOptions },
5858
{ code: '<div role="tabpanel row range"></div>', errors: [ errorMessage ], parserOptions },
59-
{ code: '<div role={undefined}></div>', errors: [ errorMessage ], parserOptions },
6059
{ code: '<div role />', errors: [ errorMessage ], parserOptions },
6160
{ code: '<div role={null}></div>', errors: [ errorMessage ], parserOptions }
6261
]

0 commit comments

Comments
 (0)