-
Notifications
You must be signed in to change notification settings - Fork 791
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(rule): aria-allowed-role (#945)
- Loading branch information
Showing
17 changed files
with
2,280 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/** | ||
* Implements allowed roles defined at: | ||
* https://www.w3.org/TR/html-aria/#docconformance | ||
* https://www.w3.org/TR/SVG2/struct.html#implicit-aria-semantics | ||
*/ | ||
const { allowImplicit = true, ignoredTags = [] } = options || {}; | ||
const tagName = node.nodeName.toUpperCase(); | ||
|
||
// check if the element should be ignored, by an user setting | ||
if (ignoredTags.map(t => t.toUpperCase()).includes(tagName)) { | ||
return true; | ||
} | ||
|
||
const unallowedRoles = axe.commons.aria.getElementUnallowedRoles( | ||
node, | ||
allowImplicit | ||
); | ||
|
||
if (unallowedRoles.length) { | ||
this.data(unallowedRoles); | ||
return false; | ||
} | ||
return true; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"id": "aria-allowed-role", | ||
"evaluate": "aria-allowed-role.js", | ||
"options": { | ||
"allowImplicit": true, | ||
"ignoredTags": [] | ||
}, | ||
"metadata": { | ||
"impact": "minor", | ||
"messages": { | ||
"pass": "ARIA role is allowed for given element", | ||
"fail": "role{{=it.data && it.data.length > 1 ? 's' : ''}} {{=it.data.join(', ')}} {{=it.data && it.data.length > 1 ? 'are' : ' is'}} not allowed for given element" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/* global aria */ | ||
/** | ||
* gets all unallowed roles for a given node | ||
* @method getElementUnallowedRoles | ||
* @param {Object} node HTMLElement to validate | ||
* @param {String} tagName tag name of a node | ||
* @param {String} allowImplicit option to allow implicit roles, defaults to true | ||
* @return {Array<String>} retruns an array of roles that are not allowed on the given node | ||
*/ | ||
aria.getElementUnallowedRoles = function getElementUnallowedRoles( | ||
node, | ||
allowImplicit | ||
) { | ||
/** | ||
* Get roles applied to a given node | ||
* @param {HTMLElement} node HTMLElement | ||
* @return {Array<String>} return an array of roles applied to the node, if no roles, return an empty array. | ||
*/ | ||
// TODO: not moving this to outer namespace yet, work with wilco to see overlap with his PR(WIP) - aria.getRole | ||
function getRoleSegments(node) { | ||
let roles = []; | ||
if (!node) { | ||
return roles; | ||
} | ||
if (node.hasAttribute('role')) { | ||
const nodeRoles = axe.utils.tokenList( | ||
node.getAttribute('role').toLowerCase() | ||
); | ||
roles = roles.concat(nodeRoles); | ||
} | ||
if (node.hasAttributeNS('http://www.idpf.org/2007/ops', 'type')) { | ||
const epubRoles = axe.utils | ||
.tokenList( | ||
node | ||
.getAttributeNS('http://www.idpf.org/2007/ops', 'type') | ||
.toLowerCase() | ||
) | ||
.map(role => `doc-${role}`); | ||
roles = roles.concat(epubRoles); | ||
} | ||
return roles; | ||
} | ||
|
||
const tagName = node.nodeName.toUpperCase(); | ||
|
||
// by pass custom elements | ||
if (!axe.utils.isHtmlElement(node)) { | ||
return []; | ||
} | ||
|
||
const roleSegments = getRoleSegments(node); | ||
const implicitRole = axe.commons.aria.implicitRole(node); | ||
|
||
// stores all roles that are not allowed for a specific element most often an element only has one explicit role | ||
const unallowedRoles = roleSegments.filter(role => { | ||
if (!axe.commons.aria.isValidRole(role)) { | ||
// do not check made-up/ fake roles | ||
return false; | ||
} | ||
|
||
// check if an implicit role may be set explicit following a setting | ||
if (!allowImplicit && role === implicitRole) { | ||
// edge case: setting implicit role row on tr element is allowed when child of table[role='grid'] | ||
if ( | ||
!( | ||
role === 'row' && | ||
tagName === 'TR' && | ||
axe.utils.matchesSelector(node, 'table[role="grid"] > tr') | ||
) | ||
) { | ||
return true; | ||
} | ||
} | ||
if (!aria.isAriaRoleAllowedOnElement(node, role)) { | ||
return true; | ||
} | ||
}); | ||
|
||
return unallowedRoles; | ||
}; |
Oops, something went wrong.
c270a46
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://dequeuniversity.com/rules/axe/3.1/aria-allowed-role page created. Content requires review.