Repository: ember-cli/eslint-plugin-ember (js)

Issue:
require-computed-property-dependencies false positive
Using string interpolation like so:

```javascript
computed(`${dependentKey}.@each.${propertyKey}`, function() {
```

Causes `require-computed-property-dependencies` to be triggered which is wrong.

P.S. Actually it is not "wrong" but I guess outputting the warning in such a case is pointless - obviously dynamic stuff is happening and the linter can not check it.

Pull request description:
Add `allowDynamicKeys` option (default true) to `require-computed-property-dependencies` rule
Fixes #475. CC: @boris-petrov

Also fixes handling of dynamic keys in the rule's autofixer.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f395865..6fcae4df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,3 @@
-## v6.9.1 (2019-08-14)
-
-#### :bug: Bug Fix
-* [#472](https://github.com/ember-cli/eslint-plugin-ember/pull/472) Improve handling of nested keys inside braces for `require-computed-property-dependencies` rule ([@bmish](https://github.com/bmish))
-* [#471](https://github.com/ember-cli/eslint-plugin-ember/pull/471) Improve detection of missing dependencies in `require-computed-property-dependencies` rule ([@bmish](https://github.com/bmish))
-
-#### Committers: 1
-- Bryan Mishkin ([@bmish](https://github.com/bmish))
-
 ## v6.9.0 (2019-08-12)
 
 #### :rocket: Enhancement
diff --git a/docs/rules/require-computed-property-dependencies.md b/docs/rules/require-computed-property-dependencies.md
index 6fccec44..af5e64b8 100644
--- a/docs/rules/require-computed-property-dependencies.md
+++ b/docs/rules/require-computed-property-dependencies.md
@@ -40,6 +40,12 @@ export default EmberObject.extend({
 });
 ```
 
+## Configuration
+
+This rule takes an optional object containing:
+
+* `boolean` -- `allowDynamicKeys` -- whether the rule should allow or disallow dynamic (variable / non-string) dependency keys in computed properties (default `true`)
+
 ## References
 
 * [Guide](https://guides.emberjs.com/release/object-model/computed-properties/) for computed properties
diff --git a/lib/rules/require-computed-property-dependencies.js b/lib/rules/require-computed-property-dependencies.js
index 36e62b2e..06ef1588 100644
--- a/lib/rules/require-computed-property-dependencies.js
+++ b/lib/rules/require-computed-property-dependencies.js
@@ -290,6 +290,17 @@ module.exports = {
     },
 
     fixable: 'code',
+
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          allowDynamicKeys: {
+            type: 'boolean',
+          },
+        },
+      },
+    ],
   },
 
   ERROR_MESSAGE_NON_STRING_VALUE,
@@ -300,12 +311,14 @@ module.exports = {
         if (isEmberComputed(node.callee) && node.arguments.length >= 1) {
           const declaredDependencies = parseComputedDependencies(node.arguments);
 
-          declaredDependencies.dynamicKeys.forEach(key => {
-            context.report({
-              node: key,
-              message: ERROR_MESSAGE_NON_STRING_VALUE,
+          if (context.options[0] && !context.options[0].allowDynamicKeys) {
+            declaredDependencies.dynamicKeys.forEach(key => {
+              context.report({
+                node: key,
+                message: ERROR_MESSAGE_NON_STRING_VALUE,
+              });
             });
-          });
+          }
 
           const computedPropertyFunction = node.arguments[node.arguments.length - 1];
 
@@ -347,10 +360,20 @@ module.exports = {
                 ', '
               )}`,
               fix(fixer) {
-                const missingDependenciesAsArguments = collapseKeys(
+                const sourceCode = context.getSourceCode();
+
+                const missingDependenciesAsArgumentsForDynamicKeys = declaredDependencies.dynamicKeys.map(
+                  dynamicKey => sourceCode.getText(dynamicKey)
+                );
+                const missingDependenciesAsArgumentsForStringKeys = collapseKeys(
                   removeRedundantKeys([...undeclaredKeys, ...expandedDeclaredKeys])
                 );
 
+                const missingDependenciesAsArguments = [
+                  ...missingDependenciesAsArgumentsForDynamicKeys,
+                  ...missingDependenciesAsArgumentsForStringKeys,
+                ].join(', ');
+
                 if (node.arguments.length > 1) {
                   const firstDependency = node.arguments[0];
                   const lastDependency = node.arguments[node.arguments.length - 2];
@@ -379,7 +402,7 @@ module.exports = {
  *
  * Example:
  * Input: ["foo.bar", "foo.baz", "quux.[]"]
- * Output: "'foo.{bar,baz}', 'quux.[]'"
+ * Output: ["foo.{bar,baz}", "quux.[]"]
  *
  * @param {Array<string>} keys
  * @returns string
@@ -412,10 +435,7 @@ function collapseKeys(keys) {
     return `${parent}.${children[0]}`;
   });
 
-  return [...bareKeys, ...joined]
-    .sort()
-    .map(key => `'${key}'`)
-    .join(', ');
+  return [...bareKeys, ...joined].sort().map(key => `'${key}'`);
 }
 
 /**
diff --git a/package.json b/package.json
index 3a8aaacc..43aeba5f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint-plugin-ember",
-  "version": "6.9.1",
+  "version": "6.9.0",
   "description": "Eslint plugin for Ember.js apps",
   "main": "lib/index.js",
   "files": [


Test patch:
diff --git a/tests/lib/rules/require-computed-property-dependencies.js b/tests/lib/rules/require-computed-property-dependencies.js
index 077bb318..35e0db63 100644
--- a/tests/lib/rules/require-computed-property-dependencies.js
+++ b/tests/lib/rules/require-computed-property-dependencies.js
@@ -98,15 +98,28 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     `
       Ember.computed.someMacro('test')
     `,
+    // Dynamic key:
+    `
+      Ember.computed(dynamic, function() {});
+    `,
+    // Dynamic key:
+    {
+      code: `
+        Ember.computed(...PROPERTIES, function() {});
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
     // Incorrect usage that should be ignored:
     `
       Ember.computed(123)
     `,
   ],
   invalid: [
+    // Dynamic key:
     {
       code: 'Ember.computed(dynamic, function() {});',
       output: null,
+      options: [{ allowDynamicKeys: false }],
       errors: [
         {
           message: ERROR_MESSAGE_NON_STRING_VALUE,
@@ -114,11 +127,51 @@ ruleTester.run('require-computed-property-dependencies', rule, {
         },
       ],
     },
+    // Dynamic keys:
     {
       code: 'Ember.computed(...PROPERTIES, function() {});',
       output: null,
       parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      options: [{ allowDynamicKeys: false }],
+      errors: [
+        {
+          message: ERROR_MESSAGE_NON_STRING_VALUE,
+          type: 'SpreadElement',
+        },
+      ],
+    },
+    // Dynamic key with missing dependency:
+    {
+      code: 'Ember.computed(dynamic, function() { return this.undeclared; });',
+      output: "Ember.computed(dynamic, 'undeclared', function() { return this.undeclared; });",
+      options: [{ allowDynamicKeys: false }],
+      errors: [
+        {
+          message: 'Use of undeclared dependencies in computed property: undeclared',
+          type: 'CallExpression',
+        },
+        {
+          message: ERROR_MESSAGE_NON_STRING_VALUE,
+          type: 'Identifier',
+        },
+      ],
+    },
+    // Multiple dynamic (identifier and spread) keys with missing dependency:
+    {
+      code: 'Ember.computed(dynamic, ...moreDynamic, function() { return this.undeclared; });',
+      output:
+        "Ember.computed(dynamic, ...moreDynamic, 'undeclared', function() { return this.undeclared; });",
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      options: [{ allowDynamicKeys: false }],
       errors: [
+        {
+          message: 'Use of undeclared dependencies in computed property: undeclared',
+          type: 'CallExpression',
+        },
+        {
+          message: ERROR_MESSAGE_NON_STRING_VALUE,
+          type: 'Identifier',
+        },
         {
           message: ERROR_MESSAGE_NON_STRING_VALUE,
           type: 'SpreadElement',