Repository: ember-template-lint/ember-template-lint
Pull request #633 (2019-01-24 11:37:35)

Add rule: [a11y] no-positive-tabindex
https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#ax_focus_03
https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/tabindex-no-positive.md


Patch:
diff --git a/docs/rule/no-positive-tabindex.md b/docs/rule/no-positive-tabindex.md
new file mode 100644
index 00000000..677ed472
--- /dev/null
+++ b/docs/rule/no-positive-tabindex.md
@@ -0,0 +1,30 @@
+## no-positive-tabindex
+
+### `<* tabindex>`
+
+Avoid positive tabIndex property values to synchronize the flow of the page with keyboard tab order.
+
+This rule takes no arguments.
+
+This rule **allows** the following:
+
+```hbs
+<span tabindex="0">foo</span>
+<span tabindex="-1">bar</span>
+<span tabindex={{0}}>baz</span>
+```
+
+This rule **forbids** the following:
+
+```hbs
+<span tabindex="5">foo</span>
+<span tabindex="3">bar</span>
+<span tabindex={{dynamicValue}}>zoo</span>
+<span tabindex="1">baz</span>
+<span tabindex="2">never really sure what goes after baz</span>
+```
+
+### References
+1. [AX_FOCUS_03](https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#ax_focus_03)
+1. [w3.org/TR/wai-aria-practices/#kbd_general_between](https://www.w3.org/TR/wai-aria-practices/#kbd_general_between)
+1. [w3.org/TR/2009/WD-wai-aria-practices-20090224/#focus_tabindex](https://www.w3.org/TR/2009/WD-wai-aria-practices-20090224/#focus_tabindex)
\ No newline at end of file
diff --git a/docs/rules.md b/docs/rules.md
index 54f2f8ff..bf063fc3 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -27,6 +27,7 @@
 * [no-negated-condition](rule/no-negated-condition.md)
 * [no-nested-interactive](rule/no-nested-interactive.md)
 * [no-outlet-outside-routes](rule/no-outlet-outside-routes.md)
+* [no-positive-tabindex](rule/no-positive-tabindex.md)
 * [no-partial](rule/no-partial.md)
 * [no-quoteless-attributes](rule/no-quoteless-attributes.md)
 * [no-shadowed-elements](rule/no-shadowed-elements.md)
diff --git a/lib/rules/index.js b/lib/rules/index.js
index 679a2560..48c89906 100644
--- a/lib/rules/index.js
+++ b/lib/rules/index.js
@@ -33,6 +33,7 @@ module.exports = {
   'no-nested-interactive': require('./lint-no-nested-interactive'),
   'no-outlet-outside-routes': require('./lint-no-outlet-outside-routes'),
   'no-partial': require('./lint-no-partial'),
+  'no-positive-tabindex': require('./lint-no-positive-tabindex'),
   'no-quoteless-attributes': require('./lint-no-quoteless-attributes'),
   'no-shadowed-elements': require('./lint-no-shadowed-elements'),
   'no-trailing-dot-in-path-expression': require('./lint-trailing-dot-in-path-expression'),
diff --git a/lib/rules/lint-no-positive-tabindex.js b/lib/rules/lint-no-positive-tabindex.js
new file mode 100644
index 00000000..d2b8ce1d
--- /dev/null
+++ b/lib/rules/lint-no-positive-tabindex.js
@@ -0,0 +1,60 @@
+'use strict';
+
+const AstNodeInfo = require('../helpers/ast-node-info');
+const Rule = require('./base');
+
+const errorMessage = 'Avoid positive integer values for tabindex.';
+const errorMessageForNaNCase = 'Tabindex values must be negative numeric.';
+
+module.exports = class NoPositiveTabindex extends Rule {
+  visitor() {
+    return {
+      ElementNode(node) {
+        const tabindex = AstNodeInfo.findAttribute(node, 'tabindex');
+
+        if (!tabindex || !tabindex.value) {
+          return;
+        }
+
+        let tabindexValue = NaN;
+
+        if (tabindex.value.type === 'MustacheStatement') {
+          if (tabindex.value.path) {
+            if (tabindex.value.path.type === 'NumberLiteral') {
+              tabindexValue = tabindex.value.path.original;
+            } else if (tabindex.value.path.type === 'StringLiteral') {
+              tabindexValue = parseInt(tabindex.value.path.original, 10);
+            }
+          }
+        } else if (tabindex.value.type === 'ConcatStatement') {
+          let part = tabindex.value.parts[0];
+          if (part.type === 'MustacheStatement') {
+            if (part.path.type === 'NumberLiteral') {
+              tabindexValue = part.path.original;
+            } else if (part.path.type === 'StringLiteral') {
+              tabindexValue = parseInt(part.path.original, 10);
+            }
+          }
+        } else if (tabindex.value.type === 'TextNode') {
+          tabindexValue = parseInt(tabindex.value.chars, 10);
+        }
+
+        if (isNaN(tabindexValue)) {
+          this.log({
+            message: errorMessageForNaNCase,
+            line: node.loc && node.loc.start.line,
+            column: node.loc && node.loc.start.column,
+            source: this.sourceForNode(tabindex),
+          });
+        } else if (tabindexValue > 0) {
+          this.log({
+            message: errorMessage,
+            line: node.loc && node.loc.start.line,
+            column: node.loc && node.loc.start.column,
+            source: this.sourceForNode(tabindex),
+          });
+        }
+      },
+    };
+  }
+};


Test patch:
diff --git a/test/unit/rules/lint-no-positive-tabindex-test.js b/test/unit/rules/lint-no-positive-tabindex-test.js
new file mode 100644
index 00000000..80c5d43c
--- /dev/null
+++ b/test/unit/rules/lint-no-positive-tabindex-test.js
@@ -0,0 +1,87 @@
+'use strict';
+
+const generateRuleTests = require('../../helpers/rule-test-harness');
+
+generateRuleTests({
+  name: 'no-positive-tabindex',
+
+  config: true,
+
+  good: [
+    '<button tabindex="0"></button>',
+    '<button tabindex="-1"></button>',
+    '<span tabindex={{-1}}>baz</span>',
+    '<span tabindex={{"-1"}}>baz</span>',
+    '<span tabindex="{{-1}}">baz</span>',
+    '<span tabindex="{{"-1"}}">baz</span>',
+  ],
+
+  bad: [
+    {
+      template: '<button tabindex={{someProperty}}></button>',
+
+      result: {
+        message: 'Tabindex values must be negative numeric.',
+        moduleId: 'layout.hbs',
+        source: 'tabindex={{someProperty}}',
+        line: 1,
+        column: 0,
+      },
+    },
+    {
+      template: '<button tabindex="1"></button>',
+
+      result: {
+        message: 'Avoid positive integer values for tabindex.',
+        moduleId: 'layout.hbs',
+        source: 'tabindex="1"',
+        line: 1,
+        column: 0,
+      },
+    },
+    {
+      template: '<button tabindex="text"></button>',
+
+      result: {
+        message: 'Tabindex values must be negative numeric.',
+        moduleId: 'layout.hbs',
+        source: 'tabindex="text"',
+        line: 1,
+        column: 0,
+      },
+    },
+    {
+      template: '<button tabindex={{true}}></button>',
+
+      result: {
+        message: 'Tabindex values must be negative numeric.',
+        moduleId: 'layout.hbs',
+        source: 'tabindex={{true}}',
+        line: 1,
+        column: 0,
+      },
+    },
+    {
+      template: '<button tabindex="{{false}}"></button>',
+
+      result: {
+        message: 'Tabindex values must be negative numeric.',
+        moduleId: 'layout.hbs',
+        source: 'tabindex="{{false}}"',
+        line: 1,
+        column: 0,
+      },
+    },
+    {
+      template: '<button tabindex="{{5}}"></button>',
+
+      result: {
+        message: 'Avoid positive integer values for tabindex.',
+        moduleId: 'layout.hbs',
+        source: 'tabindex="{{5}}"',
+        line: 1,
+        column: 0,
+      },
+    },
+  ],
+});