Skip to content

Commit 51a18a8

Browse files
marcysuttonWilcoFiers
authored andcommitted
feat: ARIA supported checks (#1254)
This branch adds a new check to report unsupported ARIA attributes to the user. We have to make a decision on how to modify the rule to support it, which you can find discussed in #918. Closes issue: #918 ## Reviewer checks **Required fields, to be filled out by PR reviewer(s)** - [x] Follows the commit message policy, appropriate for next version - [x] Has documentation updated, a DU ticket, or requires no documentation change - [x] Includes new tests, or was unnecessary - [x] Code is reviewed for security by: @WilcoFiers
1 parent 09a08c2 commit 51a18a8

File tree

15 files changed

+292
-186
lines changed

15 files changed

+292
-186
lines changed

doc/aria-supported.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ For a detailed description about how accessibility support is decided, see [How
1818
| -------------------- | ---------------- |
1919
| aria-describedat | No |
2020
| aria-details | No |
21-
| aria-roledescription | No |
21+
| aria-roledescription | No |

doc/rule-descriptions.md

Lines changed: 77 additions & 77 deletions
Large diffs are not rendered by default.

lib/checks/aria/unsupportedattr.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
let unsupported = Array.from(node.attributes)
2+
.filter(candidate => {
3+
// filter out unsupported attributes
4+
return axe.commons.aria.validateAttr(candidate.name, {
5+
flagUnsupported: true
6+
});
7+
})
8+
.map(candidate => {
9+
return candidate.name.toString();
10+
});
11+
12+
if (unsupported.length) {
13+
this.data(unsupported);
14+
return true;
15+
}
16+
return false;

lib/checks/aria/unsupportedattr.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"id": "aria-unsupported-attr",
3+
"evaluate": "unsupportedattr.js",
4+
"metadata": {
5+
"impact": "critical",
6+
"messages": {
7+
"pass": "ARIA attribute is supported",
8+
"fail": "ARIA attribute is not widely supported in screen readers and assistive technologies: {{~it.data:value}} {{=value}}{{~}}"
9+
}
10+
}
11+
}

lib/checks/aria/unsupportedrole.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"impact": "critical",
66
"messages": {
77
"pass": "ARIA role is supported",
8-
"fail": "The role used is not widely supported in assistive technologies"
8+
"fail": "The role used is not widely supported in screen readers and assistive technologies: {{~it.data:value}} {{=value}}{{~}}"
99
}
1010
}
1111
}

lib/commons/aria/attributes.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
* @return {Array}
1010
*/
1111
aria.requiredAttr = function(role) {
12-
'use strict';
1312
var roles = aria.lookupTable.role[role],
1413
attr = roles && roles.attributes && roles.attributes.required;
1514
return attr || [];
@@ -24,7 +23,6 @@ aria.requiredAttr = function(role) {
2423
* @return {Array}
2524
*/
2625
aria.allowedAttr = function(role) {
27-
'use strict';
2826
var roles = aria.lookupTable.role[role],
2927
attr = (roles && roles.attributes && roles.attributes.allowed) || [],
3028
requiredAttr =
@@ -39,9 +37,13 @@ aria.allowedAttr = function(role) {
3937
* @memberof axe.commons.aria
4038
* @instance
4139
* @param {String} att The attribute name
40+
* @param {Object} options Use `flagUnsupported: true` to report unsupported attributes
4241
* @return {Boolean}
4342
*/
44-
aria.validateAttr = function(att) {
45-
'use strict';
46-
return !!aria.lookupTable.attributes[att];
43+
aria.validateAttr = function(att, { flagUnsupported = false } = {}) {
44+
const attrDefinition = aria.lookupTable.attributes[att];
45+
if (flagUnsupported && attrDefinition) {
46+
return !!attrDefinition.unsupported;
47+
}
48+
return !!attrDefinition;
4749
};

lib/commons/aria/index.js

Lines changed: 102 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -10,177 +10,233 @@ var aria = (commons.aria = {}),
1010
lookupTable.attributes = {
1111
'aria-activedescendant': {
1212
type: 'idref',
13-
allowEmpty: true
13+
allowEmpty: true,
14+
unsupported: false
1415
},
1516
'aria-atomic': {
1617
type: 'boolean',
17-
values: ['true', 'false']
18+
values: ['true', 'false'],
19+
unsupported: false
1820
},
1921
'aria-autocomplete': {
2022
type: 'nmtoken',
21-
values: ['inline', 'list', 'both', 'none']
23+
values: ['inline', 'list', 'both', 'none'],
24+
unsupported: false
2225
},
2326
'aria-busy': {
2427
type: 'boolean',
25-
values: ['true', 'false']
28+
values: ['true', 'false'],
29+
unsupported: false
2630
},
2731
'aria-checked': {
2832
type: 'nmtoken',
29-
values: ['true', 'false', 'mixed', 'undefined']
33+
values: ['true', 'false', 'mixed', 'undefined'],
34+
unsupported: false
3035
},
3136
'aria-colcount': {
32-
type: 'int'
37+
type: 'int',
38+
unsupported: false
3339
},
3440
'aria-colindex': {
35-
type: 'int'
41+
type: 'int',
42+
unsupported: false
3643
},
3744
'aria-colspan': {
38-
type: 'int'
45+
type: 'int',
46+
unsupported: false
3947
},
4048
'aria-controls': {
4149
type: 'idrefs',
42-
allowEmpty: true
50+
allowEmpty: true,
51+
unsupported: false
4352
},
4453
'aria-current': {
4554
type: 'nmtoken',
4655
allowEmpty: true,
47-
values: ['page', 'step', 'location', 'date', 'time', 'true', 'false']
56+
values: ['page', 'step', 'location', 'date', 'time', 'true', 'false'],
57+
unsupported: false
4858
},
4959
'aria-describedby': {
5060
type: 'idrefs',
51-
allowEmpty: true
61+
allowEmpty: true,
62+
unsupported: false
63+
},
64+
'aria-describedat': {
65+
unsupported: true,
66+
unstandardized: true
67+
},
68+
'aria-details': {
69+
unsupported: true
5270
},
5371
'aria-disabled': {
5472
type: 'boolean',
55-
values: ['true', 'false']
73+
values: ['true', 'false'],
74+
unsupported: false
5675
},
5776
'aria-dropeffect': {
5877
type: 'nmtokens',
59-
values: ['copy', 'move', 'reference', 'execute', 'popup', 'none']
78+
values: ['copy', 'move', 'reference', 'execute', 'popup', 'none'],
79+
unsupported: false
6080
},
6181
'aria-errormessage': {
6282
type: 'idref',
63-
allowEmpty: true
83+
allowEmpty: true,
84+
unsupported: false
6485
},
6586
'aria-expanded': {
6687
type: 'nmtoken',
67-
values: ['true', 'false', 'undefined']
88+
values: ['true', 'false', 'undefined'],
89+
unsupported: false
6890
},
6991
'aria-flowto': {
7092
type: 'idrefs',
71-
allowEmpty: true
93+
allowEmpty: true,
94+
unsupported: false
7295
},
7396
'aria-grabbed': {
7497
type: 'nmtoken',
75-
values: ['true', 'false', 'undefined']
98+
values: ['true', 'false', 'undefined'],
99+
unsupported: false
76100
},
77101
'aria-haspopup': {
78102
type: 'nmtoken',
79103
allowEmpty: true,
80-
values: ['true', 'false', 'menu', 'listbox', 'tree', 'grid', 'dialog']
104+
values: ['true', 'false', 'menu', 'listbox', 'tree', 'grid', 'dialog'],
105+
unsupported: false
81106
},
82107
'aria-hidden': {
83108
type: 'boolean',
84-
values: ['true', 'false']
109+
values: ['true', 'false'],
110+
unsupported: false
85111
},
86112
'aria-invalid': {
87113
type: 'nmtoken',
88114
allowEmpty: true,
89-
values: ['true', 'false', 'spelling', 'grammar']
115+
values: ['true', 'false', 'spelling', 'grammar'],
116+
unsupported: false
90117
},
91118
'aria-keyshortcuts': {
92119
type: 'string',
93-
allowEmpty: true
120+
allowEmpty: true,
121+
unsupported: false
94122
},
95123
'aria-label': {
96124
type: 'string',
97-
allowEmpty: true
125+
allowEmpty: true,
126+
unsupported: false
98127
},
99128
'aria-labelledby': {
100129
type: 'idrefs',
101-
allowEmpty: true
130+
allowEmpty: true,
131+
unsupported: false
102132
},
103133
'aria-level': {
104-
type: 'int'
134+
type: 'int',
135+
unsupported: false
105136
},
106137
'aria-live': {
107138
type: 'nmtoken',
108-
values: ['off', 'polite', 'assertive']
139+
values: ['off', 'polite', 'assertive'],
140+
unsupported: false
109141
},
110142
'aria-modal': {
111143
type: 'boolean',
112-
values: ['true', 'false']
144+
values: ['true', 'false'],
145+
unsupported: false
113146
},
114147
'aria-multiline': {
115148
type: 'boolean',
116-
values: ['true', 'false']
149+
values: ['true', 'false'],
150+
unsupported: false
117151
},
118152
'aria-multiselectable': {
119153
type: 'boolean',
120-
values: ['true', 'false']
154+
values: ['true', 'false'],
155+
unsupported: false
121156
},
122157
'aria-orientation': {
123158
type: 'nmtoken',
124-
values: ['horizontal', 'vertical']
159+
values: ['horizontal', 'vertical'],
160+
unsupported: false
125161
},
126162
'aria-owns': {
127163
type: 'idrefs',
128-
allowEmpty: true
164+
allowEmpty: true,
165+
unsupported: false
129166
},
130167
'aria-placeholder': {
131168
type: 'string',
132-
allowEmpty: true
169+
allowEmpty: true,
170+
unsupported: false
133171
},
134172
'aria-posinset': {
135-
type: 'int'
173+
type: 'int',
174+
unsupported: false
136175
},
137176
'aria-pressed': {
138177
type: 'nmtoken',
139-
values: ['true', 'false', 'mixed', 'undefined']
178+
values: ['true', 'false', 'mixed', 'undefined'],
179+
unsupported: false
140180
},
141181
'aria-readonly': {
142182
type: 'boolean',
143-
values: ['true', 'false']
183+
values: ['true', 'false'],
184+
unsupported: false
144185
},
145186
'aria-relevant': {
146187
type: 'nmtokens',
147-
values: ['additions', 'removals', 'text', 'all']
188+
values: ['additions', 'removals', 'text', 'all'],
189+
unsupported: false
148190
},
149191
'aria-required': {
150192
type: 'boolean',
151-
values: ['true', 'false']
193+
values: ['true', 'false'],
194+
unsupported: false
195+
},
196+
'aria-roledescription': {
197+
unsupported: true
152198
},
153199
'aria-rowcount': {
154-
type: 'int'
200+
type: 'int',
201+
unsupported: false
155202
},
156203
'aria-rowindex': {
157-
type: 'int'
204+
type: 'int',
205+
unsupported: false
158206
},
159207
'aria-rowspan': {
160-
type: 'int'
208+
type: 'int',
209+
unsupported: false
161210
},
162211
'aria-selected': {
163212
type: 'nmtoken',
164-
values: ['true', 'false', 'undefined']
213+
values: ['true', 'false', 'undefined'],
214+
unsupported: false
165215
},
166216
'aria-setsize': {
167-
type: 'int'
217+
type: 'int',
218+
unsupported: false
168219
},
169220
'aria-sort': {
170221
type: 'nmtoken',
171-
values: ['ascending', 'descending', 'other', 'none']
222+
values: ['ascending', 'descending', 'other', 'none'],
223+
unsupported: false
172224
},
173225
'aria-valuemax': {
174-
type: 'decimal'
226+
type: 'decimal',
227+
unsupported: false
175228
},
176229
'aria-valuemin': {
177-
type: 'decimal'
230+
type: 'decimal',
231+
unsupported: false
178232
},
179233
'aria-valuenow': {
180-
type: 'decimal'
234+
type: 'decimal',
235+
unsupported: false
181236
},
182237
'aria-valuetext': {
183-
type: 'string'
238+
type: 'string',
239+
unsupported: false
184240
}
185241
};
186242

lib/rules/aria-allowed-attr-matches.js

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
1-
var role = node.getAttribute('role');
2-
if (!role) {
3-
role = axe.commons.aria.implicitRole(node);
4-
}
5-
var allowed = axe.commons.aria.allowedAttr(role);
6-
if (role && allowed) {
7-
var aria = /^aria-/;
8-
if (node.hasAttributes()) {
9-
var attrs = node.attributes;
10-
for (var i = 0, l = attrs.length; i < l; i++) {
11-
if (aria.test(attrs[i].name)) {
12-
return true;
13-
}
1+
const aria = /^aria-/;
2+
if (node.hasAttributes()) {
3+
let attrs = node.attributes;
4+
for (let i = 0, l = attrs.length; i < l; i++) {
5+
if (aria.test(attrs[i].name)) {
6+
return true;
147
}
158
}
169
}

lib/rules/aria-allowed-attr.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
},
99
"all": [],
1010
"any": ["aria-allowed-attr"],
11-
"none": []
11+
"none": ["aria-unsupported-attr"]
1212
}

0 commit comments

Comments
 (0)