Repository: ember-cli/eslint-plugin-ember
Pull request #562 (2019-11-01 16:06:53)

Add new rule `no-test-module-for`
:-1: `moduleFor`*


Patch:
diff --git a/README.md b/README.md
index 10afcad7..cdcc43e0 100644
--- a/README.md
+++ b/README.md
@@ -164,6 +164,7 @@ The `--fix` option on the command line automatically fixes problems reported by
 |  | [no-pause-test](./docs/rules/no-pause-test.md) | Disallow use of `pauseTest` helper in tests. |
 |  | [no-test-and-then](./docs/rules/no-test-and-then.md) | Disallow use of `andThen` test wait helper. |
 |  | [no-test-import-export](./docs/rules/no-test-import-export.md) | Disallow importing of "-test.js" in a test file and exporting from a test file. |
+|  | [no-test-module-for](./docs/rules/no-test-module-for.md) | Disallow use of moduleFor, moduleForComponent, etc |
 
 
 ### Stylistic Issues
diff --git a/lib/index.js b/lib/index.js
index 18c5fcc6..3cd6b868 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -41,6 +41,7 @@ module.exports = {
     'no-side-effects': require('./rules/no-side-effects'),
     'no-test-and-then': require('./rules/no-test-and-then'),
     'no-test-import-export': require('./rules/no-test-import-export'),
+    'no-test-module-for': require('./rules/no-test-module-for'),
     'no-unnecessary-index-route': require('./rules/no-unnecessary-index-route'),
     'no-unnecessary-route-path-option': require('./rules/no-unnecessary-route-path-option'),
     'no-unnecessary-service-injection-argument': require('./rules/no-unnecessary-service-injection-argument'),
diff --git a/lib/recommended-rules.js b/lib/recommended-rules.js
index 3d339ec1..84333b78 100644
--- a/lib/recommended-rules.js
+++ b/lib/recommended-rules.js
@@ -44,6 +44,7 @@ module.exports = {
   "ember/no-side-effects": "error",
   "ember/no-test-and-then": "off",
   "ember/no-test-import-export": "off",
+  "ember/no-test-module-for": "off",
   "ember/no-unnecessary-index-route": "off",
   "ember/no-unnecessary-route-path-option": "error",
   "ember/no-unnecessary-service-injection-argument": "off",
diff --git a/lib/utils/types.js b/lib/utils/types.js
index 61d6e753..5ef41a24 100644
--- a/lib/utils/types.js
+++ b/lib/utils/types.js
@@ -11,6 +11,7 @@ module.exports = {
   isConciseArrowFunctionWithCallExpression,
   isConditionalExpression,
   isExpressionStatement,
+  isFunctionDeclaration,
   isFunctionExpression,
   isIdentifier,
   isImportDeclaration,
@@ -139,6 +140,16 @@ function isExpressionStatement(node) {
   return node !== undefined && node.type === 'ExpressionStatement';
 }
 
+/**
+ * Check whether or not a node is a FunctionDeclaration
+ *
+ * @param {Object} node The node to check.
+ * @returns {boolean} Whether or not the node is a FunctionDeclaration
+ */
+function isFunctionDeclaration(node) {
+  return node !== undefined && node.type === 'FunctionDeclaration';
+}
+
 /**
  * Check whether or not a node is an FunctionExpression.
  *


Test patch:
diff --git a/docs/rules/no-test-module-for.md b/docs/rules/no-test-module-for.md
new file mode 100644
index 00000000..5a2ae691
--- /dev/null
+++ b/docs/rules/no-test-module-for.md
@@ -0,0 +1,63 @@
+# Use `module` instead of `moduleFor` (no-test-module-for)
+
+`moduleForComponent`, `moduleFor`, `moduleForAcceptance`, etc have been deprecated and there are codemods to help migrate.
+
+## Rule Details
+
+Examples of **incorrect** code for this rule:
+
+```js
+moduleFor('Test Name');
+```
+
+Examples of **correct** code for this rule:
+
+```js
+module('Test Name', function(hooks) {
+
+});
+```
+
+## Migration
+
+A short guide for how each of the legacy APIs converts to the new APIs:
+
+* `moduleFor`, `moduleForModel`
+
+    ```ts
+    import { module, test } from 'qunit';
+    import { setupTest } from 'ember-qunit';
+
+    module('...', function(hooks) {
+      setupTest(hooks);
+    });
+    ```
+
+* `moduleForComponent`
+
+    ```ts
+    import { module, test } from 'qunit';
+    import { setupRenderingTest } from 'ember-qunit';
+
+    module('...', function(hooks) {
+      setupRenderingTest(hooks);
+    });
+    ```
+
+* `moduleForAcceptance`
+
+    ```ts
+    import { module, test } from 'qunit';
+    import { setupApplicationTest } from 'ember-qunit';
+
+    module('...', function(hooks) {
+      setupApplicationTest(hooks);
+    });
+    ```
+
+## References
+
+* [moduleFor* deprecation notice from ember-qunit 4.5.0](https://github.com/emberjs/ember-qunit/blob/master/CHANGELOG.md#rocket-enhancement-1)
+
+* Codemod for automated upgrade of tests: [ember-qunit-codemod](https://github.com/ember-codemods/ember-qunit-codemod)
+
diff --git a/lib/rules/no-test-module-for.js b/lib/rules/no-test-module-for.js
new file mode 100644
index 00000000..bbc94ee6
--- /dev/null
+++ b/lib/rules/no-test-module-for.js
@@ -0,0 +1,83 @@
+'use strict';
+
+const assert = require('assert');
+
+const types = require('../utils/types');
+const emberUtils = require('../utils/ember');
+
+const ERROR_MESSAGE = 'moduleFor* apis are are not allowed. Use `module` instead of `moduleFor`';
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow use of moduleFor, moduleForComponent, etc',
+      category: 'Testing',
+      recommended: false,
+    },
+    fixable: null,
+  },
+
+  ERROR_MESSAGE,
+
+  create(context) {
+    const filename = context.getFilename();
+    const isTestFile =
+      emberUtils.isTestFile(filename) ||
+      (filename.includes('tests') && filename.includes('helpers'));
+
+    if (!isTestFile) {
+      return {};
+    }
+
+    return {
+      FunctionDeclaration(node) {
+        if (isModuleFor(context, node)) {
+          context.report({
+            node,
+            message: ERROR_MESSAGE,
+          });
+        }
+      },
+      CallExpression(node) {
+        if (isModuleFor(context, node)) {
+          context.report({
+            node,
+            message: ERROR_MESSAGE,
+          });
+        }
+      },
+    };
+  },
+};
+
+function isModuleFor(_context, node) {
+  assert(types.isCallExpression(node) || types.isFunctionDeclaration(node));
+
+  const { callee, id } = node;
+  const identifier = callee || id;
+
+  if (!identifier || !types.isIdentifier(identifier)) {
+    return false;
+  }
+
+  // catches everything from legacy ember-qunit
+  //  - moduleForComponent
+  //  - moduleForModel
+  //  - moduleFor
+  //
+  // this lint will error on the definition of moduleForAcceptance, likely in
+  // tests/helpers/start-app.js
+  // moduleForAcceptance was the default name from the ember blueprint
+  //
+  // in case someone renames a moduleFor to something else like,
+  // - moduleForUtil
+  // - moduleForRoute
+  // the entire pattern is not allowed.
+  const isBespokeInvocation = identifier.name.startsWith('moduleFor');
+
+  return isBespokeInvocation;
+}
diff --git a/tests/lib/rules/no-test-module-for.js b/tests/lib/rules/no-test-module-for.js
new file mode 100644
index 00000000..0a72496b
--- /dev/null
+++ b/tests/lib/rules/no-test-module-for.js
@@ -0,0 +1,70 @@
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/no-test-module-for');
+const RuleTester = require('eslint').RuleTester;
+
+const { ERROR_MESSAGE } = rule;
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({
+  parserOptions: {
+    ecmaVersion: 6,
+    sourceType: 'module',
+  },
+});
+
+const TEST_FILE_NAME = 'some-test.js';
+
+ruleTester.run('no-test-module-for', rule, {
+  valid: [
+    {
+      filename: TEST_FILE_NAME,
+      code: 'module()',
+    },
+    {
+      filename: 'not-a-test-file.js',
+      code: 'moduleFor()',
+    },
+    {
+      filename: 'tests/helpers/something.js',
+      code: 'export default function dosomething() {}',
+    },
+  ],
+  invalid: [
+    {
+      filename: TEST_FILE_NAME,
+      code: 'moduleFor()',
+      output: null,
+      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
+    },
+    {
+      filename: TEST_FILE_NAME,
+      code: 'moduleForComponent()',
+      output: null,
+      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
+    },
+    {
+      filename: TEST_FILE_NAME,
+      code: 'moduleForAcceptance()',
+      output: null,
+      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
+    },
+    {
+      filename: TEST_FILE_NAME,
+      code: 'moduleForBespoke()',
+      output: null,
+      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
+    },
+    {
+      filename: 'tests/helpers/module-for-acceptance',
+      code: 'export default function moduleForAcceptance() {}',
+      output: null,
+      errors: [{ message: ERROR_MESSAGE, type: 'FunctionDeclaration' }],
+    },
+  ],
+});