Skip to content

Commit 566011b

Browse files
JoshuaKGoldbergljharb
authored andcommittedDec 2, 2021
[New] aria-role: add allowedInvalidRoles option
1 parent 30deacb commit 566011b

File tree

3 files changed

+49
-29
lines changed

3 files changed

+49
-29
lines changed
 

‎__tests__/src/rules/aria-role-test.js

+8
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ const invalidTests = createTests(invalidRoles).map((test) => {
4040
return invalidTest;
4141
});
4242

43+
const allowedInvalidRoles = [{
44+
allowedInvalidRoles: ['invalid-role', 'other-invalid-role'],
45+
}];
46+
4347
const ignoreNonDOMSchema = [{
4448
ignoreNonDOM: true,
4549
}];
@@ -57,6 +61,9 @@ ruleTester.run('aria-role', rule, {
5761
{ code: '<div role="doc-abstract" />' },
5862
{ code: '<div role="doc-appendix doc-bibliography" />' },
5963
{ code: '<Bar baz />' },
64+
{ code: '<img role="invalid-role" />', options: allowedInvalidRoles },
65+
{ code: '<img role="invalid-role tabpanel" />', options: allowedInvalidRoles },
66+
{ code: '<img role="invalid-role other-invalid-role" />', options: allowedInvalidRoles },
6067
{ code: '<Foo role="bar" />', options: ignoreNonDOMSchema },
6168
{ code: '<fakeDOM role="bar" />', options: ignoreNonDOMSchema },
6269
{ code: '<img role="presentation" />', options: ignoreNonDOMSchema },
@@ -72,6 +79,7 @@ ruleTester.run('aria-role', rule, {
7279
{ code: '<div role="tabpanel row range"></div>', errors: [errorMessage] },
7380
{ code: '<div role="doc-endnotes range"></div>', errors: [errorMessage] },
7481
{ code: '<div role />', errors: [errorMessage] },
82+
{ code: '<div role="unknown-invalid-role" />', errors: [errorMessage], options: allowedInvalidRoles },
7583
{ code: '<div role={null}></div>', errors: [errorMessage] },
7684
{ code: '<Foo role="datepicker" />', errors: [errorMessage] },
7785
{ code: '<Foo role="Button" />', errors: [errorMessage] },

‎docs/rules/aria-role.md

+3
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@ This rule takes one optional object argument of type object:
1010
{
1111
"rules": {
1212
"jsx-a11y/aria-role": [ 2, {
13+
"allowedInvalidRoles": ["text"],
1314
"ignoreNonDOM": true
1415
}],
1516
}
1617
}
1718
```
1819

20+
`allowedInvalidRules` is an optional string array of custom roles that should be allowed in addition to the ARIA spec, such as for cases when you [need to use a non-standard role](https://axesslab.com/text-splitting).
21+
1922
For the `ignoreNonDOM` option, this determines if developer created components are checked.
2023

2124
### Succeed

‎src/rules/aria-role.js

+38-29
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ import { generateObjSchema } from '../util/schemas';
1414
const errorMessage = 'Elements with ARIA roles must use a valid, non-abstract ARIA role.';
1515

1616
const schema = generateObjSchema({
17+
allowedInvalidRoles: {
18+
items: {
19+
type: 'string',
20+
},
21+
type: 'array',
22+
uniqueItems: true,
23+
},
1724
ignoreNonDOM: {
1825
type: 'boolean',
1926
default: false,
@@ -28,42 +35,44 @@ export default {
2835
schema: [schema],
2936
},
3037

31-
create: (context) => ({
32-
JSXAttribute: (attribute) => {
33-
// Determine if ignoreNonDOM is set to true
34-
// If true, then do not run rule.
35-
const options = context.options[0] || {};
36-
const ignoreNonDOM = !!options.ignoreNonDOM;
38+
create: (context) => {
39+
const options = context.options[0] || {};
40+
const ignoreNonDOM = !!options.ignoreNonDOM;
41+
const allowedInvalidRoles = new Set(options.allowedInvalidRoles || []);
42+
const validRoles = new Set([...roles.keys()].filter((role) => roles.get(role).abstract === false));
3743

38-
if (ignoreNonDOM) {
39-
const type = elementType(attribute.parent);
40-
if (!dom.get(type)) {
41-
return;
44+
return ({
45+
JSXAttribute: (attribute) => {
46+
// If ignoreNonDOM and the parent isn't DOM, don't run rule.
47+
if (ignoreNonDOM) {
48+
const type = elementType(attribute.parent);
49+
if (!dom.get(type)) {
50+
return;
51+
}
4252
}
43-
}
4453

45-
// Get prop name
46-
const name = propName(attribute).toUpperCase();
54+
// Get prop name
55+
const name = propName(attribute).toUpperCase();
4756

48-
if (name !== 'ROLE') { return; }
57+
if (name !== 'ROLE') { return; }
4958

50-
const value = getLiteralPropValue(attribute);
59+
const value = getLiteralPropValue(attribute);
5160

52-
// If value is undefined, then the role attribute will be dropped in the DOM.
53-
// If value is null, then getLiteralAttributeValue is telling us that the
54-
// value isn't in the form of a literal.
55-
if (value === undefined || value === null) { return; }
61+
// If value is undefined, then the role attribute will be dropped in the DOM.
62+
// If value is null, then getLiteralAttributeValue is telling us that the
63+
// value isn't in the form of a literal.
64+
if (value === undefined || value === null) { return; }
5665

57-
const values = String(value).split(' ');
58-
const validRoles = [...roles.keys()].filter((role) => roles.get(role).abstract === false);
59-
const isValid = values.every((val) => validRoles.indexOf(val) > -1);
66+
const values = String(value).split(' ');
67+
const isValid = values.every((val) => allowedInvalidRoles.has(val) || validRoles.has(val));
6068

61-
if (isValid === true) { return; }
69+
if (isValid === true) { return; }
6270

63-
context.report({
64-
node: attribute,
65-
message: errorMessage,
66-
});
67-
},
68-
}),
71+
context.report({
72+
node: attribute,
73+
message: errorMessage,
74+
});
75+
},
76+
});
77+
},
6978
};

2 commit comments

Comments
 (2)

Julia1996 commented on May 14, 2022

@Julia1996

Hi @ljharb and @JoshuaKGoldberg
Thank you for this new feature, it's very useful! Could you, please, release a new version so I can update my dependencies and use this feature?

ljharb commented on May 18, 2022

@ljharb
Member

@Julia1996 theres no timeline for a release, but I’ll try to cut one in the near future.

Please sign in to comment.