diff --git a/__tests__/src/rules/aria-role-test.js b/__tests__/src/rules/aria-role-test.js index 1286ad898..a584c2f21 100644 --- a/__tests__/src/rules/aria-role-test.js +++ b/__tests__/src/rules/aria-role-test.js @@ -40,6 +40,10 @@ const invalidTests = createTests(invalidRoles).map((test) => { return invalidTest; }); +const allowedInvalidRoles = [{ + allowedInvalidRoles: ['invalid-role', 'other-invalid-role'], +}]; + const ignoreNonDOMSchema = [{ ignoreNonDOM: true, }]; @@ -57,6 +61,9 @@ ruleTester.run('aria-role', rule, { { code: '
' }, { code: '
' }, { code: '' }, + { code: '', options: allowedInvalidRoles }, + { code: '', options: allowedInvalidRoles }, + { code: '', options: allowedInvalidRoles }, { code: '', options: ignoreNonDOMSchema }, { code: '', options: ignoreNonDOMSchema }, { code: '', options: ignoreNonDOMSchema }, @@ -72,6 +79,7 @@ ruleTester.run('aria-role', rule, { { code: '
', errors: [errorMessage] }, { code: '
', errors: [errorMessage] }, { code: '
', errors: [errorMessage] }, + { code: '
', errors: [errorMessage], options: allowedInvalidRoles }, { code: '
', errors: [errorMessage] }, { code: '', errors: [errorMessage] }, { code: '', errors: [errorMessage] }, diff --git a/docs/rules/aria-role.md b/docs/rules/aria-role.md index f8e22f115..0167752b0 100644 --- a/docs/rules/aria-role.md +++ b/docs/rules/aria-role.md @@ -10,12 +10,15 @@ This rule takes one optional object argument of type object: { "rules": { "jsx-a11y/aria-role": [ 2, { + "allowedInvalidRoles": ["text"], "ignoreNonDOM": true }], } } ``` +`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). + For the `ignoreNonDOM` option, this determines if developer created components are checked. ### Succeed diff --git a/src/rules/aria-role.js b/src/rules/aria-role.js index 52bed2b86..57e4096bd 100644 --- a/src/rules/aria-role.js +++ b/src/rules/aria-role.js @@ -14,6 +14,13 @@ import { generateObjSchema } from '../util/schemas'; const errorMessage = 'Elements with ARIA roles must use a valid, non-abstract ARIA role.'; const schema = generateObjSchema({ + allowedInvalidRoles: { + items: { + type: 'string', + }, + type: 'array', + uniqueItems: true, + }, ignoreNonDOM: { type: 'boolean', default: false, @@ -28,42 +35,44 @@ export default { schema: [schema], }, - create: (context) => ({ - JSXAttribute: (attribute) => { - // Determine if ignoreNonDOM is set to true - // If true, then do not run rule. - const options = context.options[0] || {}; - const ignoreNonDOM = !!options.ignoreNonDOM; + create: (context) => { + const options = context.options[0] || {}; + const ignoreNonDOM = !!options.ignoreNonDOM; + const allowedInvalidRoles = new Set(options.allowedInvalidRoles || []); + const validRoles = new Set([...roles.keys()].filter((role) => roles.get(role).abstract === false)); - if (ignoreNonDOM) { - const type = elementType(attribute.parent); - if (!dom.get(type)) { - return; + return ({ + JSXAttribute: (attribute) => { + // If ignoreNonDOM and the parent isn't DOM, don't run rule. + if (ignoreNonDOM) { + const type = elementType(attribute.parent); + if (!dom.get(type)) { + return; + } } - } - // Get prop name - const name = propName(attribute).toUpperCase(); + // Get prop name + const name = propName(attribute).toUpperCase(); - if (name !== 'ROLE') { return; } + if (name !== 'ROLE') { return; } - const value = getLiteralPropValue(attribute); + const value = getLiteralPropValue(attribute); - // If value is undefined, then the role attribute will be dropped in the DOM. - // If value is null, then getLiteralAttributeValue is telling us that the - // value isn't in the form of a literal. - if (value === undefined || value === null) { return; } + // If value is undefined, then the role attribute will be dropped in the DOM. + // If value is null, then getLiteralAttributeValue is telling us that the + // value isn't in the form of a literal. + if (value === undefined || value === null) { return; } - const values = String(value).split(' '); - const validRoles = [...roles.keys()].filter((role) => roles.get(role).abstract === false); - const isValid = values.every((val) => validRoles.indexOf(val) > -1); + const values = String(value).split(' '); + const isValid = values.every((val) => allowedInvalidRoles.has(val) || validRoles.has(val)); - if (isValid === true) { return; } + if (isValid === true) { return; } - context.report({ - node: attribute, - message: errorMessage, - }); - }, - }), + context.report({ + node: attribute, + message: errorMessage, + }); + }, + }); + }, };