Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new rule no-invalid-aria-attributes #2276

Merged
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ Each rule has emojis denoting:
| [no-inline-styles](./docs/rule/no-inline-styles.md) | ✅ | | | |
| [no-input-block](./docs/rule/no-input-block.md) | ✅ | | | |
| [no-input-tagname](./docs/rule/no-input-tagname.md) | ✅ | | | |
| [no-invalid-aria-attributes](./docs/rule/no-invalid-aria-attributes.md) | | | ⌨️ | |
judithhinlung marked this conversation as resolved.
Show resolved Hide resolved
| [no-invalid-block-param-definition](./docs/rule/no-invalid-block-param-definition.md) | ✅ | | | |
| [no-invalid-interactive](./docs/rule/no-invalid-interactive.md) | ✅ | | ⌨️ | |
| [no-invalid-link-text](./docs/rule/no-invalid-link-text.md) | ✅ | | ⌨️ | |
Expand Down
22 changes: 22 additions & 0 deletions docs/rule/no-invalid-aria-attributes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# no-invalid-aria-attributes

This rule checks for the use of invalid ARIA attributes, or any aria-\* property on an element that is not included in the [WAI-ARIA States and Properties spec](https://www.w3.org/WAI/PF/aria-1.1/states_and_properties).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what happens when someone uses a non-existent aria attribute? E.g. A non-existent ARIA attribute has no effect.

judithhinlung marked this conversation as resolved.
Show resolved Hide resolved

## Examples

This rule **forbids** the following:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anything else we can validate about ARIA attributes? Anything we validate about their values or what attributes they are used on? It's easier to make this rule comprehensive now and cover multiple things, since adding violations later will be a breaking change.


```hbs
<input type="text" aria-require="true" />
judithhinlung marked this conversation as resolved.
Show resolved Hide resolved
```

This rule **allows** the following:

```hbs
<input type="text" aria-required="true" />
```

## References

- [Using ARIA, Roles, States, and Properties](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques)

1 change: 1 addition & 0 deletions lib/config/a11y.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
'no-duplicate-landmark-elements': 'error',
'no-empty-headings': 'error',
'no-heading-inside-button': 'error',
'no-invalid-aria-attributes': 'error',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagine we should add this to recommended in the upcoming v4 release right?

'no-invalid-interactive': 'error',
'no-invalid-link-text': 'error',
'no-invalid-link-title': 'error',
Expand Down
89 changes: 89 additions & 0 deletions lib/rules/no-invalid-aria-attributes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
'use strict';

const Rule = require('./_base');

const VALID_ARIA_ATTRIBUTES = new Set([
judithhinlung marked this conversation as resolved.
Show resolved Hide resolved
'aria-activedescendant',
'aria-atomic',
'aria-autocomplete',
'aria-busy',
'aria-checked',
'aria-colcount',
'aria-colindex',
'aria-colspan',
'aria-controls',
'aria-current',
'aria-describedby',
'aria-details',
'aria-disabled',
'aria-dragged',
'aria-dropeffect',
'aria-errormessage',
'aria-expanded',
'aria-flowto',
'aria-haspopup',
'aria-hidden',
'aria-invalid',
'aria-label',
'aria-labelledby',
'aria-level',
'aria-live',
'aria-modal',
'aria-multiline',
'aria-multiselectable',
'aria-orientation',
'aria-owns',
'aria-placeholder',
'aria-posinset',
'aria-pressed',
'aria-readonly',
'aria-relevant',
'aria-required',
'aria-rowcount',
'aria-rowindex',
'aria-rowspan',
'aria-selected',
'aria-setsize',
'aria-sort',
'aria-valuemax',
'aria-valuemin',
'aria-valuenow',
'aria-valuetext',
]);

function getAriaAttributes(node) {
judithhinlung marked this conversation as resolved.
Show resolved Hide resolved
let attributes = node.attributes;
let ariaAttributes = [];
for (let attribute of attributes) {
if (attribute.name.startsWith('aria-')) {
ariaAttributes.push(attribute);
}
}
return ariaAttributes;
}

module.exports = class NoInvalidAriaAttributes extends Rule {
logNode({ node, message }) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This helper doesn't seem to be necessary, can we remove it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove this logNode helper since it's no better than just calling this.log directly.

return this.log({
message,
node,
});
}
visitor() {
return {
ElementNode(node) {
const ariaAttributes = getAriaAttributes(node);
if (ariaAttributes.length > 0) {
for (let attribute of ariaAttributes) {
if (!VALID_ARIA_ATTRIBUTES.has(attribute.name)) {
this.logNode({
message: `${attribute.name} is not a valid ARIA attribute.`,
node,
});
}
}
}
},
};
}
};
102 changes: 102 additions & 0 deletions test/unit/rules/no-invalid-aria-attributes-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
'use strict';

const generateRuleTests = require('../../helpers/rule-test-harness');

generateRuleTests({
name: 'no-invalid-aria-attributes',

config: true,

good: [
'<h1 aria-hidden="true">Valid Heading</h1>',
'<input type="email" aria-required="true" />',
'<div role="slider" aria-valuenow={{this.foo}} aria-valuemax={{this.bar}} aria-valuemin={{this.baz}} />',
'<div role="region" aria-live="polite">Valid live region</div>',
'<CustomComponent @ariaRequired={{this.ariaRequired}} aria-errormessage="errorId" />',
],

bad: [
{
template: '<input aria-text="inaccessible text" />',
verifyResults(results) {
expect(results).toMatchInlineSnapshot(`
Array [
Object {
"column": 0,
"endColumn": 39,
"endLine": 1,
"filePath": "layout.hbs",
"line": 1,
"message": "aria-text is not a valid ARIA attribute.",
"rule": "no-invalid-aria-attributes",
"severity": 2,
"source": "<input aria-text=\\"inaccessible text\\" />",
},
]
`);
},
},
{
template:
'<div role="slider" aria-valuenow={{this.foo}} aria-valuemax={{this.bar}} aria-value-min={{this.baz}} />',
verifyResults(results) {
expect(results).toMatchInlineSnapshot(`
Array [
Object {
"column": 0,
"endColumn": 103,
"endLine": 1,
"filePath": "layout.hbs",
"line": 1,
"message": "aria-value-min is not a valid ARIA attribute.",
"rule": "no-invalid-aria-attributes",
"severity": 2,
"source": "<div role=\\"slider\\" aria-valuenow={{this.foo}} aria-valuemax={{this.bar}} aria-value-min={{this.baz}} />",
},
]
`);
},
},
{
template: '<h1 aria--hidden="true">Broken heading</h1>',
verifyResults(results) {
expect(results).toMatchInlineSnapshot(`
Array [
Object {
"column": 0,
"endColumn": 43,
"endLine": 1,
"filePath": "layout.hbs",
"line": 1,
"message": "aria--hidden is not a valid ARIA attribute.",
"rule": "no-invalid-aria-attributes",
"severity": 2,
"source": "<h1 aria--hidden=\\"true\\">Broken heading</h1>",
},
]
`);
},
},

{
template: '<CustomComponent role="region" aria-alert="polite" />',
verifyResults(results) {
expect(results).toMatchInlineSnapshot(`
Array [
Object {
"column": 0,
"endColumn": 53,
"endLine": 1,
"filePath": "layout.hbs",
"line": 1,
"message": "aria-alert is not a valid ARIA attribute.",
"rule": "no-invalid-aria-attributes",
"severity": 2,
"source": "<CustomComponent role=\\"region\\" aria-alert=\\"polite\\" />",
},
]
`);
},
},
],
});