Repository: ember-cli/eslint-plugin-ember
Pull request #534 (2019-10-09 16:56:24)

Add `onlyThisContexts` option to `no-arrow-function-computed-properties` rule
arrow functions only affect the `this`, so if you have computed properties without a `this`, such as when primitives or empty objects are used as the value, the arrow function usage is totally valid :)

This PR skips the lint rule when this is not present, otherwise it behaves as it did before.


Patch:
diff --git a/docs/rules/no-arrow-function-computed-properties.md b/docs/rules/no-arrow-function-computed-properties.md
index 49285418..b7851ec3 100644
--- a/docs/rules/no-arrow-function-computed-properties.md
+++ b/docs/rules/no-arrow-function-computed-properties.md
@@ -32,6 +32,12 @@ const Person = EmberObject.extend({
 });
 ```
 
+## Configuration
+
+This rule takes an optional object containing:
+
+* `boolean` -- `onlyThisContexts` -- whether the rule should allow or disallow computed properties where the arrow function body does not contain a `this` reference (default: `false`)
+
 ## References
 
 * [Arrow function spec](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)
diff --git a/lib/rules/no-arrow-function-computed-properties.js b/lib/rules/no-arrow-function-computed-properties.js
index aaf40283..539b31d0 100644
--- a/lib/rules/no-arrow-function-computed-properties.js
+++ b/lib/rules/no-arrow-function-computed-properties.js
@@ -17,13 +17,33 @@ module.exports = {
   },
 
   create(context) {
+    const options = context.options[0] || {};
+    const onlyThisContexts = options.onlyThisContexts || false;
+
+    let isThisPresent = false;
+
     return {
-      CallExpression(node) {
-        if (
+      ThisExpression() {
+        isThisPresent = true;
+      },
+      CallExpression() {
+        isThisPresent = false;
+      },
+      'CallExpression:exit'(node) {
+        const isComputedArrow =
           emberUtils.isComputedProp(node) &&
           node.arguments.length > 0 &&
-          types.isArrowFunctionExpression(node.arguments[node.arguments.length - 1])
-        ) {
+          types.isArrowFunctionExpression(node.arguments[node.arguments.length - 1]);
+
+        if (!isComputedArrow) {
+          return;
+        }
+
+        if (onlyThisContexts) {
+          if (isThisPresent) {
+            context.report(node.arguments[node.arguments.length - 1], ERROR_MESSAGE);
+          }
+        } else {
           context.report(node.arguments[node.arguments.length - 1], ERROR_MESSAGE);
         }
       },


Test patch:
diff --git a/tests/lib/rules/no-arrow-function-computed-properties.js b/tests/lib/rules/no-arrow-function-computed-properties.js
index e457a148..1af47dbd 100644
--- a/tests/lib/rules/no-arrow-function-computed-properties.js
+++ b/tests/lib/rules/no-arrow-function-computed-properties.js
@@ -21,18 +21,41 @@ ruleTester.run('no-arrow-function-computed-properties', rule, {
   valid: [
     'computed()',
     'computed(function() { return 123; })',
-    "computed('prop', function() { return this.prop; })",
-    "computed('prop', function() { return this.prop; }).volatile()",
-    "computed.map('products', function(product) { return someFunction(product); })",
+    'computed("prop", function() { return this.prop; })',
+    'computed("prop", function() { return this.prop; }).volatile()',
+    'computed.map("products", function(product) { return someFunction(product); })',
     'other(() => {})',
     'other.computed(() => {})',
+    {
+      code: `computed('prop', function() { return this.prop; });`,
+      options: [{ onlyThisContexts: true }],
+    },
+    {
+      code: `computed('prop', function() { return this.prop; }).volatile();`,
+      options: [{ onlyThisContexts: true }],
+    },
+    {
+      code: `computed(() => { return 123; });`,
+      options: [{ onlyThisContexts: true }],
+    },
+    {
+      code: `computed(() => { return "string stuff"; });`,
+      options: [{ onlyThisContexts: true }],
+    },
+    {
+      code: `computed(() => []);`,
+      options: [{ onlyThisContexts: true }],
+    },
+    {
+      code: `computed.map('products', product => { return someFunction(product); });`,
+      options: [{ onlyThisContexts: true }],
+    },
   ],
   invalid: [
     {
       code: 'computed(() => { return 123; })',
       errors: [{ message: ERROR_MESSAGE, type: 'ArrowFunctionExpression' }],
     },
-
     {
       code: "computed('prop', () => { return this.prop; })",
       errors: [{ message: ERROR_MESSAGE, type: 'ArrowFunctionExpression' }],
@@ -42,10 +65,24 @@ ruleTester.run('no-arrow-function-computed-properties', rule, {
       code: "computed('prop', () => { return this.prop; }).volatile()",
       errors: [{ message: ERROR_MESSAGE, type: 'ArrowFunctionExpression' }],
     },
-
     {
       code: "computed.map('products', product => { return someFunction(product); })",
       errors: [{ message: ERROR_MESSAGE, type: 'ArrowFunctionExpression' }],
     },
+    {
+      code: 'computed(() => { return 123; })',
+      errors: [],
+      options: [{ onlyThisContexts: true }],
+    },
+    {
+      code: "computed('prop', () => { return this.prop; })",
+      errors: [{ message: ERROR_MESSAGE, type: 'ArrowFunctionExpression' }],
+      options: [{ onlyThisContexts: true }],
+    },
+    {
+      code: "computed('prop', () => { return this.prop; }).volatile()",
+      errors: [{ message: ERROR_MESSAGE, type: 'ArrowFunctionExpression' }],
+      options: [{ onlyThisContexts: true }],
+    },
   ],
 });