Skip to content

Commit 2dff52d

Browse files
author
Ethan Cohen
committed
Add no-invalid-aria rule.
Enforce that all aria-* properties are valid (aka listed in the spec) :)
1 parent baf9dc8 commit 2dff52d

File tree

7 files changed

+142
-3
lines changed

7 files changed

+142
-3
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ Then configure the rules you want to use under the rules section.
7575
- [redundant-alt](docs/rules/redundant-alt.md): Enforce img alt attribute does not contain the word image, picture, or photo.
7676
- [no-hash-href](docs/rules/no-hash-href.md): Enforce an anchor element's href prop value is not just #.
7777
- [valid-aria-role](docs/rules/valid-aria-role.md): Enforce that elements with ARIA roles must use a valid, non-abstract ARIA role.
78+
- [no-invalid-aria](docs/rules/no-invalid-aria.md): Enforce all aria-* properties are valid.
7879

7980
## Contributing
8081
Feel free to contribute! I am currently using [Google Chrome's Audit Rules](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules) to map out as rules for this plugin.

TODO.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
# TODO
2-
1. Allow `alt=""` if `role="presentation"` on img-uses-alt rule.

docs/rules/no-invalid-aria.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# no-invalid-aria
2+
3+
Elements cannot use an invalid ARIA attribute. This will fail if it finds an `aria-*` property that is not listed in [WAI-ARIA States and Properties spec](https://www.w3.org/TR/wai-aria/states_and_properties#state_prop_def).
4+
5+
## Rule details
6+
7+
This rule takes no arguments.
8+
9+
### Succeed
10+
```jsx
11+
<!-- Good: Labeled using correctly spelled aria-labelledby -->
12+
<div id="address_label">Enter your address</div>
13+
<input aria-labelledby="address_label">
14+
```
15+
16+
### Fail
17+
18+
```jsx
19+
<!-- Bad: Labeled using incorrectly spelled aria-labeledby -->
20+
<div id="address_label">Enter your address</div>
21+
<input aria-labeledby="address_label">
22+
```

src/index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ module.exports = {
1010
'no-access-key': require('./rules/no-access-key'),
1111
'label-uses-for': require('./rules/label-uses-for'),
1212
'no-hash-href': require('./rules/no-hash-href'),
13-
'valid-aria-role': require('./rules/valid-aria-role')
13+
'valid-aria-role': require('./rules/valid-aria-role'),
14+
'no-invalid-aria': require('./rules/no-invalid-aria')
1415
},
1516
configs: {
1617
recommended: {
@@ -28,7 +29,8 @@ module.exports = {
2829
"jsx-a11y/no-access-key": 2,
2930
"jsx-a11y/label-uses-for": 2,
3031
"jsx-a11y/no-hash-href": 2,
31-
"jsx-a11y/valid-aria-role": 2
32+
"jsx-a11y/valid-aria-role": 2,
33+
"jsx-a11y/no-invalid-aria": 2
3234
}
3335
}
3436
}

src/rules/no-invalid-aria.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* @fileoverview Enforce all aria-* properties are valid.
3+
* @author Ethan Cohen
4+
*/
5+
'use strict';
6+
7+
// ----------------------------------------------------------------------------
8+
// Rule Definition
9+
// ----------------------------------------------------------------------------
10+
11+
import validAriaProperties from '../util/validAriaProperties';
12+
13+
const errorMessage = name => `${name}: This attribute is an invalid ARIA attribute.`;
14+
15+
module.exports = context => ({
16+
JSXAttribute: attribute => {
17+
const name = attribute.name.name;
18+
const normalizedName = name.toUpperCase();
19+
20+
// `aria` needs to be prefix of property.
21+
if (normalizedName.indexOf('ARIA-') !== 0) {
22+
return;
23+
}
24+
25+
const isValid = validAriaProperties.indexOf(normalizedName) > -1;
26+
27+
if (isValid === false) {
28+
context.report({
29+
node: attribute,
30+
message: errorMessage(name)
31+
});
32+
}
33+
}
34+
});
35+
36+
module.exports.schema = [
37+
{ type: 'object' }
38+
];

src/util/validAriaProperties.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Taken from https://www.w3.org/TR/wai-aria/states_and_properties#state_prop_def
2+
3+
export default [
4+
'ARIA-ACTIVEDESCENDANT', 'ARIA-ATOMIC',
5+
'ARIA-AUTOCOMPLETE', 'ARIA-BUSY',
6+
'ARIA-CHECKED', 'ARIA-CONTROLS',
7+
'ARIA-DESCRIBEDBY', 'ARIA-DISABLED',
8+
'ARIA-DROPEFFECT', 'ARIA-EXPANDED',
9+
'ARIA-FLOWTO', 'ARIA-GRABBED',
10+
'ARIA-HASPOPUP', 'ARIA-HIDDEN',
11+
'ARIA-INVALID', 'ARIA-LABEL',
12+
'ARIA-LABELLEDBY', 'ARIA-LEVEL',
13+
'ARIA-LIVE', 'ARIA-MULTILINE',
14+
'ARIA-MULTISELECTABLE', 'ARIA-ORIENTATION',
15+
'ARIA-OWNS', 'ARIA-POSINSET',
16+
'ARIA-PRESSED', 'ARIA-READONLY',
17+
'ARIA-RELEVANT', 'ARIA-REQUIRED',
18+
'ARIA-SELECTED', 'ARIA-SETSIZE',
19+
'ARIA-SORT', 'ARIA-VALUEMAX',
20+
'ARIA-VALUEMIN', 'ARIA-VALUENOW',
21+
'ARIA-VALUETEXT'
22+
];

tests/src/rules/no-invalid-aria.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* @fileoverview Enforce all aria-* properties are valid.
3+
* @author Ethan Cohen
4+
*/
5+
6+
'use strict';
7+
8+
// -----------------------------------------------------------------------------
9+
// Requirements
10+
// -----------------------------------------------------------------------------
11+
12+
import rule from '../../../src/rules/no-invalid-aria';
13+
import { RuleTester } from 'eslint';
14+
15+
const parserOptions = {
16+
ecmaVersion: 6,
17+
ecmaFeatures: {
18+
jsx: true
19+
}
20+
};
21+
22+
// -----------------------------------------------------------------------------
23+
// Tests
24+
// -----------------------------------------------------------------------------
25+
26+
const ruleTester = new RuleTester();
27+
28+
const errorMessage = name => ({
29+
message: `${name}: This attribute is an invalid ARIA attribute.`,
30+
type: 'JSXAttribute'
31+
});
32+
33+
import validAriaProperties from '../../../src/util/validAriaProperties';
34+
35+
// Create basic test cases using all valid role types.
36+
const basicValidityTests = validAriaProperties.map(prop => ({
37+
code: `<div ${prop.toLowerCase()}="foobar" />`,
38+
parserOptions
39+
}));
40+
41+
ruleTester.run('no-invalid-aria', rule, {
42+
valid: [
43+
// Variables should pass, as we are only testing literals.
44+
{ code: '<div />', parserOptions },
45+
{ code: '<div></div>', parserOptions },
46+
{ code: '<div aria="wee"></div>', parserOptions }, // Needs aria-*
47+
{ code: '<div abcARIAdef="true"></div>', parserOptions },
48+
{ code: '<Bar baz />', parserOptions }
49+
].concat(basicValidityTests),
50+
invalid: [
51+
{ code: '<div aria-="foobar" />', errors: [ errorMessage('aria-') ], parserOptions },
52+
{ code: '<div aria-labeledby="foobar" />', errors: [ errorMessage('aria-labeledby') ], parserOptions },
53+
{ code: '<div aria-skldjfaria-klajsd="foobar" />', errors: [ errorMessage('aria-skldjfaria-klajsd') ], parserOptions }
54+
]
55+
});

0 commit comments

Comments
 (0)