Repository: ember-cli/eslint-plugin-ember
Pull request #253 (2018-05-08 23:20:15)

`avoid-using-needs` rule
This rule discourages the following pattern:

```javascript
const ContextDetailsController = Controller.extend({
  needs: ['foobar', 'barbaz']
});
// or
Controller.reopen({
  needs: ['foobar', 'barbaz']
});
// or
Controller.reopenClass({
  needs: ['foobar', 'barbaz']
});
```

closes https://github.com/ember-cli/eslint-plugin-ember/issues/252


Patch:
diff --git a/README.md b/README.md
index 9c37ac2a..58e8a738 100644
--- a/README.md
+++ b/README.md
@@ -117,6 +117,7 @@ The `--fix` option on the command line automatically fixes problems reported by
 |    | Rule ID | Description |
 |:---|:--------|:------------|
 | :white_check_mark: | [avoid-leaking-state-in-ember-objects](./docs/rules/avoid-leaking-state-in-ember-objects.md) | Avoids state leakage |
+|  | [avoid-using-needs-in-controllers](./docs/rules/avoid-using-needs-in-controllers.md) | Avoids using needs in controllers |
 
 
 ### Testing
diff --git a/lib/recommended-rules.js b/lib/recommended-rules.js
index 92e74b3a..fa8452f4 100644
--- a/lib/recommended-rules.js
+++ b/lib/recommended-rules.js
@@ -8,6 +8,7 @@ module.exports = {
   "ember/alias-model-in-controller": "off",
   "ember/avoid-leaking-state-in-components": "off",
   "ember/avoid-leaking-state-in-ember-objects": "error",
+  "ember/avoid-using-needs-in-controllers": "off",
   "ember/closure-actions": "error",
   "ember/jquery-ember-run": "error",
   "ember/local-modules": "off",
diff --git a/lib/rules/avoid-using-needs-in-controllers.js b/lib/rules/avoid-using-needs-in-controllers.js
new file mode 100644
index 00000000..50e674cf
--- /dev/null
+++ b/lib/rules/avoid-using-needs-in-controllers.js
@@ -0,0 +1,42 @@
+'use strict';
+
+const ember = require('../utils/ember');
+
+//------------------------------------------------------------------------------
+// Ember object rule - Avoid using needs in controllers
+//------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Avoids using needs in controllers',
+      category: 'Ember Object',
+      recommended: false
+    },
+    fixable: null, // or "code" or "whitespace"
+  },
+
+  create(context) {
+    const report = function (node) {
+      const message = '`needs` API has been deprecated, `Ember.inject.controller` should be used instead';
+      context.report(node, message);
+    };
+    const filePath = context.getFilename();
+
+    return {
+      CallExpression(node) {
+        const isReopenNode = ember.isReopenObject(node) || ember.isReopenClassObject(node);
+
+        if (!ember.isEmberController(node, filePath) && !isReopenNode) return;
+
+        const properties = ember.getModuleProperties(node);
+
+        properties.forEach((property) => {
+          if (property.key.name === 'needs') {
+            report(property);
+          }
+        });
+      },
+    };
+  },
+};
diff --git a/lib/utils/ember.js b/lib/utils/ember.js
index 007266dc..fd0f8cbc 100644
--- a/lib/utils/ember.js
+++ b/lib/utils/ember.js
@@ -46,6 +46,7 @@ module.exports = {
   isEmberObject,
 
   isReopenObject,
+  isReopenClassObject,
 };
 
 // Private
@@ -127,6 +128,10 @@ function isEmberObject(node) {
   );
 }
 
+function isReopenClassObject(node) {
+  return node.callee.property && node.callee.property.name === 'reopenClass';
+}
+
 function isReopenObject(node) {
   return node.callee.property && node.callee.property.name === 'reopen';
 }


Test patch:
diff --git a/tests/lib/rules/avoid-using-needs-in-controllers.js b/tests/lib/rules/avoid-using-needs-in-controllers.js
new file mode 100644
index 00000000..5fdb7d64
--- /dev/null
+++ b/tests/lib/rules/avoid-using-needs-in-controllers.js
@@ -0,0 +1,93 @@
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/avoid-using-needs-in-controllers');
+const RuleTester = require('eslint').RuleTester;
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const eslintTester = new RuleTester({
+  parserOptions: {
+    ecmaVersion: 6,
+    sourceType: 'module'
+  }
+});
+eslintTester.run('avoid-using-needs-in-controllers', rule, {
+  valid: [
+    {
+      code: 'export default Controller.extend();',
+    },
+    {
+      code: 'export default FooController.extend();',
+    },
+    {
+      code: 'Controller.reopen();',
+    },
+    {
+      code: 'FooController.reopen();',
+    },
+    {
+      code: 'Controller.reopenClass();',
+    },
+    {
+      code: 'FooController.reopenClass();',
+    }
+  ],
+  invalid: [
+    {
+      code: 'export default Controller.extend({ needs: [] });',
+      errors: [{
+        message: '`needs` API has been deprecated, `Ember.inject.controller` should be used instead',
+      }],
+    },
+    {
+      code: 'Controller.reopenClass({ needs: [] });',
+      errors: [{
+        message: '`needs` API has been deprecated, `Ember.inject.controller` should be used instead',
+      }],
+    },
+    {
+      code: 'Controller.reopen({ needs: [] });',
+      errors: [{
+        message: '`needs` API has been deprecated, `Ember.inject.controller` should be used instead',
+      }],
+    },
+    {
+      code: "export default Controller['extend']({ needs: [] });",
+      errors: [{
+        message: '`needs` API has been deprecated, `Ember.inject.controller` should be used instead',
+      }],
+    },
+    {
+      filename: 'example-app/controllers/some-controller.js',
+      code: 'export default FooController.extend({ needs: [] });',
+      errors: [{
+        message: '`needs` API has been deprecated, `Ember.inject.controller` should be used instead',
+      }],
+    },
+    {
+      filename: 'example-app/controllers/some-controller.js',
+      code: 'FooController.reopenClass({ needs: [] });',
+      errors: [{
+        message: '`needs` API has been deprecated, `Ember.inject.controller` should be used instead',
+      }],
+    },
+    {
+      filename: 'example-app/controllers/some-controller.js',
+      code: 'FooController.reopen({ needs: [] });',
+      errors: [{
+        message: '`needs` API has been deprecated, `Ember.inject.controller` should be used instead',
+      }],
+    },
+    {
+      filename: 'example-app/controllers/some-controller.js',
+      code: "export default FooController['extend']({ needs: [] });",
+      errors: [{
+        message: '`needs` API has been deprecated, `Ember.inject.controller` should be used instead',
+      }],
+    },
+  ]
+});