Repository: ember-cli/eslint-plugin-ember
Pull request #702 (2020-03-10 21:52:01)

Add new rule `no-invalid-test-waiters`
Implements #701 


Patch:
diff --git a/README.md b/README.md
index 7888d50c..256afabd 100644
--- a/README.md
+++ b/README.md
@@ -178,6 +178,7 @@ Rules are grouped by category to help you understand their purpose. Each rule ha
 |    | Rule ID | Description |
 |:---|:--------|:------------|
 | :white_check_mark: | [no-ember-testing-in-module-scope](./docs/rules/no-ember-testing-in-module-scope.md) | disallow use of `Ember.testing` in module scope |
+|  | [no-invalid-test-waiters](./docs/rules/no-invalid-test-waiters.md) | disallow incorrect usage of test waiter APIs |
 |  | [no-legacy-test-waiters](./docs/rules/no-legacy-test-waiters.md) | disallow the use of the legacy test waiter APIs |
 |  | [no-pause-test](./docs/rules/no-pause-test.md) | disallow usage of the `pauseTest` helper in tests |
 |  | [no-replace-test-comments](./docs/rules/no-replace-test-comments.md) | disallow 'Replace this with your real tests' comments in test files |
diff --git a/lib/index.js b/lib/index.js
index 8a4f2a42..6acc8409 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -35,6 +35,7 @@ module.exports = {
     'no-incorrect-computed-macros': require('./rules/no-incorrect-computed-macros'),
     'no-invalid-debug-function-arguments': require('./rules/no-invalid-debug-function-arguments'),
     'no-invalid-dependent-keys': require('./rules/no-invalid-dependent-keys'),
+    'no-invalid-test-waiters': require('./rules/no-invalid-test-waiters'),
     'no-jquery': require('./rules/no-jquery'),
     'no-legacy-test-waiters': require('./rules/no-legacy-test-waiters'),
     'no-mixins': require('./rules/no-mixins'),
diff --git a/lib/recommended-rules.js b/lib/recommended-rules.js
index 86a60c17..6fc019ab 100644
--- a/lib/recommended-rules.js
+++ b/lib/recommended-rules.js
@@ -38,6 +38,7 @@ module.exports = {
   "ember/no-incorrect-computed-macros": "off",
   "ember/no-invalid-debug-function-arguments": "error",
   "ember/no-invalid-dependent-keys": "off",
+  "ember/no-invalid-test-waiters": "off",
   "ember/no-jquery": "off",
   "ember/no-legacy-test-waiters": "off",
   "ember/no-mixins": "off",


Test patch:
diff --git a/docs/rules/no-invalid-test-waiters.md b/docs/rules/no-invalid-test-waiters.md
new file mode 100644
index 00000000..968591b0
--- /dev/null
+++ b/docs/rules/no-invalid-test-waiters.md
@@ -0,0 +1,71 @@
+# no-invalid-test-waiters
+
+Prevents invalid usage of test waiters.
+
+## Rule Details
+
+The new test waiters APIs, found in the [ember-test-waiters](https://github.com/emberjs/ember-test-waiters) addon, have recommended best practices that ensure you are successful with their usage. This rule ensures that all usages are adhering to recommended best practices.
+
+## Examples
+
+Examples of **incorrect** code for this rule:
+
+```js
+import { buildWaiter } from 'ember-test-waiters';
+
+function useWaiter() {
+  const myOtherWaiter = buildWaiter('the second');
+}
+```
+
+```js
+import { buildWaiter as b } from 'ember-test-waiters';
+
+function useWaiter() {
+  const myOtherWaiter = b('the second');
+}
+```
+
+```js
+import { buildWaiter } from 'ember-test-waiters';
+
+const someFunction = () => {
+  buildWaiter('waiterName');
+};
+```
+
+Examples of **correct** code for this rule:
+
+```js
+import { buildWaiter } from 'ember-test-waiters';
+
+const myWaiter = buildWaiter('waiterName');
+```
+
+```js
+import { buildWaiter } from 'table-waiters';
+
+function useWaiter() {
+  const myOtherWaiter = buildWaiter('the second');
+}
+```
+
+```js
+import { buildWaiter } from 'ember-test-waiters';
+
+function useWaiter() {
+  const myWaiter = somethingElse.buildWaiter('waiterName');
+}
+```
+
+```js
+import { buildWaiter } from 'ember-test-waiters';
+
+function useWaiter() {
+  const myWaiter = buildWaiter.somethingElse('waiterName');
+}
+```
+
+## References
+
+For more information on the new test waiters API, please visit [ember-test-waiters](https://github.com/emberjs/ember-test-waiters).
diff --git a/lib/rules/no-invalid-test-waiters.js b/lib/rules/no-invalid-test-waiters.js
new file mode 100644
index 00000000..2f421d5f
--- /dev/null
+++ b/lib/rules/no-invalid-test-waiters.js
@@ -0,0 +1,69 @@
+'use strict';
+
+const { getImportIdentifier } = require('../utils/import');
+const utils = require('../utils/utils');
+
+const MODULE_SCOPE_ERROR_MESSAGE = 'The buildWaiter function should be invoked in module scope.';
+const DIRECT_ASSIGNMENT_ERROR_MESSAGE =
+  'The result of the `buildWaiter` function must be a direct assignment to a module scoped variable';
+
+function isInModuleScope(node) {
+  const parentVariableDeclaration = utils.getParent(
+    node,
+    current => current.type === 'VariableDeclaration'
+  );
+
+  return parentVariableDeclaration.parent.type === 'Program';
+}
+
+function isDirectVariableDeclaration(node) {
+  return node.parent.type === 'VariableDeclarator';
+}
+
+module.exports = {
+  meta: {
+    type: 'suggestion',
+    docs: {
+      description: 'disallow incorrect usage of test waiter APIs',
+      category: 'Testing',
+      recommended: false,
+      url:
+        'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-invalid-test-waiters.md',
+    },
+    fixable: null,
+    schema: [],
+  },
+
+  MODULE_SCOPE_ERROR_MESSAGE,
+  DIRECT_ASSIGNMENT_ERROR_MESSAGE,
+
+  create(context) {
+    let buildWaiter;
+
+    return {
+      ImportDeclaration(node) {
+        buildWaiter = getImportIdentifier(node, 'ember-test-waiters', 'buildWaiter');
+      },
+
+      CallExpression(node) {
+        if (node.callee.name !== buildWaiter) {
+          return;
+        }
+
+        if (!isDirectVariableDeclaration(node)) {
+          context.report({
+            node,
+            message: DIRECT_ASSIGNMENT_ERROR_MESSAGE,
+          });
+        }
+
+        if (!isInModuleScope(node)) {
+          context.report({
+            node,
+            message: MODULE_SCOPE_ERROR_MESSAGE,
+          });
+        }
+      },
+    };
+  },
+};
diff --git a/tests/lib/rules/no-invalid-test-waiters.js b/tests/lib/rules/no-invalid-test-waiters.js
new file mode 100644
index 00000000..f3f163ea
--- /dev/null
+++ b/tests/lib/rules/no-invalid-test-waiters.js
@@ -0,0 +1,80 @@
+'use strict';
+
+const rule = require('../../../lib/rules/no-invalid-test-waiters');
+
+const { MODULE_SCOPE_ERROR_MESSAGE, DIRECT_ASSIGNMENT_ERROR_MESSAGE } = rule;
+const RuleTester = require('eslint').RuleTester;
+
+const ruleTester = new RuleTester({
+  parserOptions: {
+    ecmaVersion: 6,
+    sourceType: 'module',
+  },
+});
+
+ruleTester.run('no-invalid-test-waiters', rule, {
+  valid: [
+    `
+    import { buildWaiter } from 'ember-test-waiters';
+
+    let myWaiter = buildWaiter('waiterName');
+  `,
+    `
+    import { buildWaiter } from 'table-waiters';
+
+    function useWaiter() {
+      let myOtherWaiter = buildWaiter('the second');
+    }
+  `,
+    `
+    import { buildWaiter } from 'ember-test-waiters';
+
+    function useWaiter() {
+      let myWaiter = somethingElse.buildWaiter('waiterName');
+    }
+  `,
+    `
+    import { buildWaiter } from 'ember-test-waiters';
+
+    function useWaiter() {
+      let myWaiter = buildWaiter.somethingElse('waiterName');
+    }
+  `,
+  ],
+
+  invalid: [
+    {
+      code: `
+      import { buildWaiter } from 'ember-test-waiters';
+
+      function useWaiter() {
+          let myOtherWaiter = buildWaiter('the second');
+      }
+      `,
+      output: null,
+      errors: [{ message: MODULE_SCOPE_ERROR_MESSAGE }],
+    },
+    {
+      code: `
+      import { buildWaiter as b } from 'ember-test-waiters';
+
+      function useWaiter() {
+          let myOtherWaiter = b('the second');
+      }
+      `,
+      output: null,
+      errors: [{ message: MODULE_SCOPE_ERROR_MESSAGE }],
+    },
+    {
+      code: `
+      import { buildWaiter } from 'ember-test-waiters';
+
+      const someFunction = () => {
+        buildWaiter('waiterName');
+      };
+      `,
+      output: null,
+      errors: [{ message: DIRECT_ASSIGNMENT_ERROR_MESSAGE }],
+    },
+  ],
+});