Skip to content

Commit 9b22d92

Browse files
committed
[new] - Implement aria-role-supports-attribute (#36)
* [new] - Implement aria-role-supports-attribute * [refactor] - Refactor out implicit roles from JSON to functions to maintain consistency. Update error message to delineate between implicit roles and explicit roles. Also maintain case consistency in messaging. * [new] - Update documentation for aria-role-supports-attribute
1 parent 47ab6e6 commit 9b22d92

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2640
-72
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ Then configure the rules you want to use under the rules section.
7070

7171
## Supported Rules
7272

73+
- [aria-role-supports-attribute](docs/rules/aria-role-supports-attribute.md): Enforce that elements with explicit or implicit roles defined contain only `aria-*` properties supported by that `role`.
7374
- [avoid-positive-tabindex](docs/rules/avoid-positive-tabindex.md): Enforce `tabIndex` value is not greater than zero.
7475
- [img-uses-alt](docs/rules/img-uses-alt.md): Enforce that `<img>` JSX elements use the `alt` prop.
7576
- [label-uses-for](docs/rules/label-uses-for.md): Enforce that `<label>` elements have the `htmlFor` prop.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# aria-role-supports-attribute
2+
3+
Enforce that elements with explicit or implicit roles defined contain only `aria-*` properties supported by that `role`. Many ARIA attributes (states and properties) can only be used on elements with particular roles. Some elements have implicit roles, such as `<a href="#" />`, which will resolve to `role="link"`.
4+
5+
#### References
6+
1. [AX_ARIA_10](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#ax_aria_10)
7+
2. [Supported States & Properties](https://www.w3.org/TR/wai-aria/roles#supportedState)
8+
9+
## Rule details
10+
11+
This rule takes no arguments.
12+
13+
### Succeed
14+
```jsx
15+
<!-- Good: the radiogroup role does support the aria-required property -->
16+
<ul role="radiogroup" aria-required aria-labelledby="foo">
17+
<li tabIndex="-1" role="radio" aria-checked="false">Rainbow Trout</li>
18+
<li tabIndex="-1" role="radio" aria-checked="false">Brook Trout</li>
19+
<li tabIndex="0" role="radio" aria-checked="true">Lake Trout</li>
20+
</ul>
21+
```
22+
23+
### Fail
24+
25+
```jsx
26+
<!-- Bad: the radio role does not support the aria-required property -->
27+
<ul role="radiogroup" aria-labelledby="foo">
28+
<li aria-required tabIndex="-1" role="radio" aria-checked="false">Rainbow Trout</li>
29+
<li aria-required tabIndex="-1" role="radio" aria-checked="false">Brook Trout</li>
30+
<li aria-required tabIndex="0" role="radio" aria-checked="true">Lake Trout</li>
31+
</ul>
32+
```

src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ module.exports = {
1616
'role-requires-aria': require('./rules/role-requires-aria'),
1717
'no-unsupported-elements-use-aria': require('./rules/no-unsupported-elements-use-aria'),
1818
'avoid-positive-tabindex': require('./rules/avoid-positive-tabindex'),
19-
'onclick-has-focus': require('./rules/onclick-has-focus')
19+
'onclick-has-focus': require('./rules/onclick-has-focus'),
20+
'aria-role-supports-attribute': require('./rules/aria-role-supports-attribute')
2021
},
2122
configs: {
2223
recommended: {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* @fileoverview Enforce that elements with explicit or implicit roles defined contain only
3+
* `aria-*` properties supported by that `role`.
4+
* @author Ethan Cohen
5+
*/
6+
'use strict';
7+
8+
// ----------------------------------------------------------------------------
9+
// Rule Definition
10+
// ----------------------------------------------------------------------------
11+
12+
import hasAttribute from '../util/hasAttribute';
13+
import { getLiteralAttributeValue } from '../util/getAttributeValue';
14+
import getNodeType from '../util/getNodeType';
15+
import ROLES from '../util/attributes/role';
16+
import ARIA from '../util/attributes/ARIA';
17+
import getImplicitRole from '../util/getImplicitRole';
18+
19+
const errorMessage = (attr, role, tag, isImplicit) => {
20+
if (isImplicit) {
21+
return `The attribute ${attr} is not supported by the role ${role}. This role is implicit on the element ${tag}.`;
22+
}
23+
24+
return `The attribute ${attr} is not supported by the role ${role}.`;
25+
};
26+
27+
module.exports = context => ({
28+
JSXOpeningElement: node => {
29+
// If role is not explicitly defined, then try and get its implicit role.
30+
const type = getNodeType(node);
31+
const hasRole = hasAttribute(node.attributes, 'role');
32+
const role = hasRole ? getLiteralAttributeValue(hasRole) : getImplicitRole(type, node.attributes);
33+
const isImplicit = role && !hasRole;
34+
35+
// If there is no explicit or implicit role, then assume that the element
36+
// can handle the global set of aria-* properties.
37+
// This actually isn't true - should fix in future release.
38+
if (!role || ROLES[role.toUpperCase()] === undefined) {
39+
return;
40+
}
41+
42+
// Make sure it has no aria-* properties defined outside of its property set.
43+
const propertySet = ROLES[role.toUpperCase()].props;
44+
const invalidAriaPropsForRole = Object.keys(ARIA).filter(attribute => propertySet.indexOf(attribute) === -1);
45+
const invalidAttr = hasAttribute(node.attributes, ...invalidAriaPropsForRole);
46+
47+
if (invalidAttr === false) {
48+
return;
49+
}
50+
51+
context.report({
52+
node,
53+
message: errorMessage(invalidAttr.name.name, role, type, isImplicit)
54+
});
55+
56+
}
57+
});
58+
59+
module.exports.schema = [
60+
{ type: 'object' }
61+
];

src/rules/valid-aria-role.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// Rule Definition
99
// ----------------------------------------------------------------------------
1010

11-
import validRoleTypes from '../util/attributes/role';
11+
import roles from '../util/attributes/role';
1212
import { getLiteralAttributeValue } from '../util/getAttributeValue';
1313

1414
const errorMessage = 'Elements with ARIA roles must use a valid, non-abstract ARIA role.';
@@ -29,7 +29,8 @@ module.exports = context => ({
2929
}
3030

3131
const normalizedValues = String(value).toUpperCase().split(' ');
32-
const isValid = normalizedValues.every(value => Object.keys(validRoleTypes).indexOf(value) > -1);
32+
const validRoles = Object.keys(roles).filter(role => roles[role].abstract === false);
33+
const isValid = normalizedValues.every(value => validRoles.indexOf(value) > -1);
3334

3435
if (isValid === true) {
3536
return;

0 commit comments

Comments
 (0)