Skip to content

Commit 0d34619

Browse files
Merge pull request #2498 from johanrd/fix/2475
Post-merge review of #2475 (`template-no-invalid-role`)
2 parents 8541cd3 + 1cb8715 commit 0d34619

2 files changed

Lines changed: 102 additions & 17 deletions

File tree

lib/rules/template-no-invalid-role.js

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ const VALID_ROLES = new Set([
44
'application',
55
'article',
66
'associationlist',
7+
'associationlistitemkey',
8+
'associationlistitemvalue',
79
'banner',
810
'blockquote',
911
'button',
1012
'caption',
13+
'cell',
1114
'checkbox',
1215
'code',
1316
'columnheader',
@@ -87,16 +90,18 @@ const VALID_ROLES = new Set([
8790
]);
8891

8992
// Elements with semantic meaning that should not be given role="presentation" or role="none"
93+
// List from https://developer.mozilla.org/en-US/docs/Web/HTML/Element
9094
const SEMANTIC_ELEMENTS = new Set([
9195
'a',
9296
'abbr',
93-
'address',
94-
'article',
95-
'aside',
97+
'applet',
98+
'area',
99+
'audio',
96100
'b',
97101
'bdi',
98102
'bdo',
99103
'blockquote',
104+
'br',
100105
'button',
101106
'caption',
102107
'cite',
@@ -110,50 +115,50 @@ const SEMANTIC_ELEMENTS = new Set([
110115
'details',
111116
'dfn',
112117
'dialog',
118+
'dir',
113119
'dl',
114120
'dt',
115121
'em',
122+
'embed',
116123
'fieldset',
117124
'figcaption',
118125
'figure',
119-
'footer',
120126
'form',
121-
'h1',
122-
'h2',
123-
'h3',
124-
'h4',
125-
'h5',
126-
'h6',
127-
'header',
128-
'hgroup',
129127
'hr',
130128
'i',
129+
'iframe',
131130
'input',
132131
'ins',
133132
'kbd',
134133
'label',
135134
'legend',
136135
'main',
136+
'map',
137137
'mark',
138138
'menu',
139+
'menuitem',
139140
'meter',
140-
'nav',
141+
'noembed',
142+
'object',
141143
'ol',
142144
'optgroup',
143145
'option',
144146
'output',
145147
'p',
148+
'param',
146149
'pre',
147150
'progress',
148151
'q',
152+
'rb',
149153
'rp',
150154
'rt',
155+
'rtc',
151156
'ruby',
152157
's',
153158
'samp',
154-
'section',
155159
'select',
156160
'small',
161+
'source',
157162
'strong',
158163
'sub',
159164
'summary',
@@ -167,9 +172,13 @@ const SEMANTIC_ELEMENTS = new Set([
167172
'thead',
168173
'time',
169174
'tr',
175+
'track',
176+
'tt',
170177
'u',
171178
'ul',
172179
'var',
180+
'video',
181+
'wbr',
173182
]);
174183

175184
/** @type {import('eslint').Rule.RuleModule} */
@@ -221,8 +230,10 @@ module.exports = {
221230
return;
222231
}
223232

233+
const roleLower = role.toLowerCase();
234+
224235
// Check for nonexistent roles
225-
if (catchNonexistentRoles && !VALID_ROLES.has(role)) {
236+
if (catchNonexistentRoles && !VALID_ROLES.has(roleLower)) {
226237
context.report({
227238
node: roleAttr,
228239
messageId: 'invalid',
@@ -231,8 +242,13 @@ module.exports = {
231242
return;
232243
}
233244

234-
// Check for presentation/none role on semantic elements
235-
if ((role === 'presentation' || role === 'none') && SEMANTIC_ELEMENTS.has(node.tag)) {
245+
// Check for presentation/none role on semantic elements (case-insensitive per WAI-ARIA 1.2:
246+
// "Case-sensitivity of the comparison inherits from the case-sensitivity of the host language"
247+
// and HTML is case-insensitive — https://www.w3.org/TR/wai-aria-1.2/#document-handling_author-errors_roles)
248+
if (
249+
(roleLower === 'presentation' || roleLower === 'none') &&
250+
SEMANTIC_ELEMENTS.has(node.tag)
251+
) {
236252
context.report({
237253
node: roleAttr,
238254
messageId: 'presentationOnSemantic',

tests/lib/rules/template-no-invalid-role.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ ruleTester.run('template-no-invalid-role', rule, {
5656
'<template><AwesomeThing role="presentation"></AwesomeThing></template>',
5757
'<template><table role="textbox"></table></template>',
5858
'<template><div role="{{if this.inModal "dialog" "contentinfo" }}"></div></template>',
59+
60+
// Missing VALID_ROLES entries: associationlistitemkey, associationlistitemvalue, cell
61+
'<template><div role="associationlistitemkey">Key</div></template>',
62+
'<template><div role="associationlistitemvalue">Value</div></template>',
63+
'<template><td role="cell">Data</td></template>',
64+
65+
// Case-insensitive role matching
66+
'<template><div role="Button">Click</div></template>',
67+
'<template><div role="NAVIGATION">Nav</div></template>',
68+
'<template><div role="ALERT">Alert</div></template>',
5969
],
6070

6171
invalid: [
@@ -156,6 +166,32 @@ ruleTester.run('template-no-invalid-role', rule, {
156166
output: null,
157167
errors: [{ message: "Invalid ARIA role 'COMMAND INTERFACE'. Must be a valid ARIA role." }],
158168
},
169+
170+
// Newly added SEMANTIC_ELEMENTS: presentation/none on iframe, video, audio
171+
{
172+
code: '<template><iframe role="presentation"></iframe></template>',
173+
output: null,
174+
errors: [
175+
{ message: 'The role "presentation" should not be used on the semantic element <iframe>.' },
176+
],
177+
},
178+
{
179+
code: '<template><video role="none"></video></template>',
180+
output: null,
181+
errors: [{ message: 'The role "none" should not be used on the semantic element <video>.' }],
182+
},
183+
{
184+
code: '<template><audio role="presentation"></audio></template>',
185+
output: null,
186+
errors: [
187+
{ message: 'The role "presentation" should not be used on the semantic element <audio>.' },
188+
],
189+
},
190+
{
191+
code: '<template><embed role="none"></template>',
192+
output: null,
193+
errors: [{ message: 'The role "none" should not be used on the semantic element <embed>.' }],
194+
},
159195
],
160196
});
161197

@@ -187,6 +223,14 @@ hbsRuleTester.run('template-no-invalid-role', rule, {
187223
'<AwesomeThing role="presentation"></AwesomeThing>',
188224
'<table role="textbox"></table>',
189225
'<div role="{{if this.inModal "dialog" "contentinfo" }}"></div>',
226+
// Missing VALID_ROLES entries: associationlistitemkey, associationlistitemvalue, cell
227+
'<div role="associationlistitemkey">Key</div>',
228+
'<div role="associationlistitemvalue">Value</div>',
229+
'<td role="cell">Data</td>',
230+
// Case-insensitive role matching
231+
'<div role="Button">Click</div>',
232+
'<div role="NAVIGATION">Nav</div>',
233+
'<div role="ALERT">Alert</div>',
190234
// catchNonexistentRoles: false — non-existent roles are not flagged
191235
{
192236
code: '<div role="command interface"></div>',
@@ -260,5 +304,30 @@ hbsRuleTester.run('template-no-invalid-role', rule, {
260304
output: null,
261305
errors: [{ message: "Invalid ARIA role 'COMMAND INTERFACE'. Must be a valid ARIA role." }],
262306
},
307+
// Newly added SEMANTIC_ELEMENTS: presentation/none on iframe, video, audio, embed
308+
{
309+
code: '<iframe role="presentation"></iframe>',
310+
output: null,
311+
errors: [
312+
{ message: 'The role "presentation" should not be used on the semantic element <iframe>.' },
313+
],
314+
},
315+
{
316+
code: '<video role="none"></video>',
317+
output: null,
318+
errors: [{ message: 'The role "none" should not be used on the semantic element <video>.' }],
319+
},
320+
{
321+
code: '<audio role="presentation"></audio>',
322+
output: null,
323+
errors: [
324+
{ message: 'The role "presentation" should not be used on the semantic element <audio>.' },
325+
],
326+
},
327+
{
328+
code: '<embed role="none">',
329+
output: null,
330+
errors: [{ message: 'The role "none" should not be used on the semantic element <embed>.' }],
331+
},
263332
],
264333
});

0 commit comments

Comments
 (0)