Skip to content

Commit 4f6ec08

Browse files
committed
[new] - Implement avoid-positive-tabindex (#26)
Fixes #16
1 parent 1267588 commit 4f6ec08

File tree

5 files changed

+133
-2
lines changed

5 files changed

+133
-2
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ Then configure the rules you want to use under the rules section.
7979
- [valid-aria-proptype](docs/rules/valid-aria-proptype.md): Enforce ARIA state and property values are valid.
8080
- [role-requires-aria](docs/rules/role-requires-aria.md): Enforce that elements with ARIA roles must have all required attributes for that role.
8181
- [no-unsupported-elements-use-aria](docs/rules/no-unsupported-elements-use-aria.md): Enforce that elements that do not support ARIA roles, states and properties do not have those attributes.
82+
- [avoid-positive-tabindex](docs/rules/avoid-positive-tabindex.md): Enforce tabIndex value is not greater than zero.
8283

8384
## Contributing
8485
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.

docs/rules/avoid-positive-tabindex.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# avoid-positive-tabindex
2+
3+
Avoid positive tabIndex property values to synchronize the flow of the page with keyboard tab order.
4+
5+
#### References
6+
1. [AX_FOCUS_03](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#ax_focus_03)
7+
8+
## Rule details
9+
10+
This rule takes no arguments.
11+
12+
### Succeed
13+
```jsx
14+
<span tabIndex="0">foo</span>
15+
<span tabIndex="-1">bar</span>
16+
<span tabIndex={0}>baz</span>
17+
```
18+
19+
### Fail
20+
```jsx
21+
<span tabIndex="5">foo</span>
22+
<span tabIndex="3">bar</span>
23+
<span tabIndex="1">baz</span>
24+
<span tabIndex="2">never really sure what goes after baz</span>
25+
```
26+

src/index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ module.exports = {
1414
'valid-aria-proptypes': require('./rules/valid-aria-proptypes'),
1515
'no-invalid-aria': require('./rules/no-invalid-aria'),
1616
'role-requires-aria': require('./rules/role-requires-aria'),
17-
'no-unsupported-elements-use-aria': require('./rules/no-unsupported-elements-use-aria')
17+
'no-unsupported-elements-use-aria': require('./rules/no-unsupported-elements-use-aria'),
18+
'avoid-positive-tabindex': require('./rules/avoid-positive-tabindex')
1819
},
1920
configs: {
2021
recommended: {
@@ -36,7 +37,8 @@ module.exports = {
3637
'jsx-a11y/valid-aria-proptypes': 2,
3738
'jsx-a11y/no-invalid-aria': 2,
3839
'jsx-a11y/role-requires-aria': 2,
39-
'jsx-a11y/no-unsupported-elements-use-aria': 2
40+
'jsx-a11y/no-unsupported-elements-use-aria': 2,
41+
'jsx-a11y/avoid-positive-tabindex': 2
4042
}
4143
}
4244
}

src/rules/avoid-positive-tabindex.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* @fileoverview Enforce tabIndex value is not greater than zero.
3+
* @author Ethan Cohen
4+
*/
5+
'use strict';
6+
7+
// ----------------------------------------------------------------------------
8+
// Rule Definition
9+
// ----------------------------------------------------------------------------
10+
11+
import { getLiteralAttributeValue } from '../util/getAttributeValue';
12+
13+
const errorMessage = 'Avoid positive integer values for tabIndex.';
14+
15+
module.exports = context => ({
16+
JSXAttribute: attribute => {
17+
const name = attribute.name.name;
18+
const normalizedName = name.toUpperCase();
19+
20+
// Check if tabIndex is the attribute
21+
if (normalizedName !== 'TABINDEX') {
22+
return;
23+
}
24+
25+
// Only check literals because we can't infer values from certain expressions.
26+
const value = Number(getLiteralAttributeValue(attribute));
27+
28+
if (isNaN(value) || value <= 0) {
29+
return;
30+
}
31+
32+
context.report({
33+
node: attribute,
34+
message: errorMessage
35+
});
36+
}
37+
});
38+
39+
module.exports.schema = [
40+
{ type: 'object' }
41+
];
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* @fileoverview Enforce tabIndex value is not greater than zero.
3+
* @author Ethan Cohen
4+
*/
5+
6+
'use strict';
7+
8+
// -----------------------------------------------------------------------------
9+
// Requirements
10+
// -----------------------------------------------------------------------------
11+
12+
import rule from '../../../src/rules/avoid-positive-tabindex';
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 expectedError = {
29+
message: 'Avoid positive integer values for tabIndex.',
30+
type: 'JSXAttribute'
31+
};
32+
33+
ruleTester.run('avoid-positive-tabindex', rule, {
34+
valid: [
35+
{ code: '<div />;', parserOptions },
36+
{ code: '<div {...props} />', parserOptions },
37+
{ code: '<div tabIndex={undefined} />', parserOptions },
38+
{ code: '<div tabIndex={`${undefined}`} />', parserOptions },
39+
{ code: '<div tabIndex={`${undefined}${undefined}`} />', parserOptions },
40+
{ code: '<div tabIndex={0} />', parserOptions },
41+
{ code: '<div tabIndex={-1} />', parserOptions },
42+
{ code: '<div tabIndex={null} />', parserOptions },
43+
{ code: '<div tabIndex={bar()} />', parserOptions },
44+
{ code: '<div tabIndex={bar} />', parserOptions },
45+
{ code: '<div tabIndex={"foobar"} />', parserOptions },
46+
{ code: '<div tabIndex="0" />', parserOptions },
47+
{ code: '<div tabIndex="-1" />', parserOptions },
48+
{ code: '<div tabIndex="-5" />', parserOptions },
49+
{ code: '<div tabIndex="-5.5" />', parserOptions },
50+
{ code: '<div tabIndex={-5.5} />', parserOptions },
51+
{ code: '<div tabIndex={-5} />', parserOptions }
52+
],
53+
54+
invalid: [
55+
{ code: '<div tabIndex="1" />', errors: [ expectedError ], parserOptions },
56+
{ code: '<div tabIndex={1} />', errors: [ expectedError ], parserOptions },
57+
{ code: '<div tabIndex={"1"} />', errors: [ expectedError ], parserOptions },
58+
{ code: '<div tabIndex={`1`} />', errors: [ expectedError ], parserOptions },
59+
{ code: '<div tabIndex={1.589} />', errors: [ expectedError ], parserOptions }
60+
]
61+
});

0 commit comments

Comments
 (0)