Repository: ember-cli/eslint-plugin-ember
Pull request #908 (2020-08-07 17:18:30)

Check imported get/getProperties/getWithDefault functions for missing dependencies in `require-computed-property-dependencies` rule
Previously, we only checked `this.get(...)` and `Ember.get(...)`, but not the imported version `import { get } from '@ember/object'; get(...)` of these functions, when searching for missing dependent keys in computed properties.

CC: @mongoose700 


Patch:
diff --git a/lib/rules/require-computed-property-dependencies.js b/lib/rules/require-computed-property-dependencies.js
index ef3218a4..e557240a 100644
--- a/lib/rules/require-computed-property-dependencies.js
+++ b/lib/rules/require-computed-property-dependencies.js
@@ -117,16 +117,16 @@ function parseComputedDependencies(args) {
  * or `this.get(…)`.
  *
  * @param {ASTNode} node
- * @param {string} importedEmberName
+ * @param {object} importedNames
  * @returns {Array<ASTNode>}
  */
-function findEmberGetCalls(node, importedEmberName) {
+function findEmberGetCalls(node, importedNames) {
   const results = [];
 
   new Traverser().traverse(node, {
     enter(child) {
       if (types.isCallExpression(child)) {
-        const dependency = extractEmberGetDependencies(child, importedEmberName);
+        const dependency = extractEmberGetDependencies(child, importedNames);
 
         if (dependency.length > 0) {
           results.push(child);
@@ -211,10 +211,13 @@ function getArrayOrRest(args) {
  * Extracts all static property keys used in the various forms of `Ember.get`.
  *
  * @param {ASTNode} call
- * @param {string} importedEmberName
+ * @param {object} importedNames
  * @returns {Array<string>}
  */
-function extractEmberGetDependencies(call, importedEmberName) {
+function extractEmberGetDependencies(
+  call,
+  { importedEmberName, importedGetName, importedGetPropertiesName, importedGetWithDefaultName }
+) {
   if (
     isMemberExpression(call.callee, 'this', 'get') ||
     isMemberExpression(call.callee, 'this', 'getWithDefault')
@@ -226,7 +229,9 @@ function extractEmberGetDependencies(call, importedEmberName) {
     }
   } else if (
     isMemberExpression(call.callee, importedEmberName, 'get') ||
-    isMemberExpression(call.callee, importedEmberName, 'getWithDefault')
+    isMemberExpression(call.callee, importedEmberName, 'getWithDefault') ||
+    isIdentifier(call.callee, importedGetName) ||
+    isIdentifier(call.callee, importedGetWithDefaultName)
   ) {
     const firstArg = call.arguments[0];
     const secondArgument = call.arguments[1];
@@ -238,7 +243,10 @@ function extractEmberGetDependencies(call, importedEmberName) {
     return getArrayOrRest(call.arguments)
       .filter(types.isStringLiteral)
       .map((arg) => arg.value);
-  } else if (isMemberExpression(call.callee, importedEmberName, 'getProperties')) {
+  } else if (
+    isMemberExpression(call.callee, importedEmberName, 'getProperties') ||
+    isIdentifier(call.callee, importedGetPropertiesName)
+  ) {
     const firstArg = call.arguments[0];
     const rest = call.arguments.slice(1);
 
@@ -313,8 +321,11 @@ module.exports = {
 
     let importedEmberName;
     let importedComputedName;
+    let importedGetName;
+    let importedGetPropertiesName;
+    let importedGetWithDefaultName;
 
-    function checkComputedDependencies(node, nodeArguments, importedEmberName) {
+    function checkComputedDependencies(node, nodeArguments, importedNames) {
       const declaredDependencies = parseComputedDependencies(nodeArguments);
 
       if (!allowDynamicKeys) {
@@ -331,8 +342,8 @@ module.exports = {
       );
 
       const usedKeys1 = javascriptUtils.flatMap(
-        findEmberGetCalls(computedPropertyFunctionBody, importedEmberName),
-        (node) => extractEmberGetDependencies(node, importedEmberName)
+        findEmberGetCalls(computedPropertyFunctionBody, importedNames),
+        (node) => extractEmberGetDependencies(node, importedNames)
       );
       const usedKeys2 = javascriptUtils.flatMap(
         findThisGetCalls(computedPropertyFunctionBody),
@@ -459,18 +470,35 @@ module.exports = {
         if (node.source.value === '@ember/object') {
           importedComputedName =
             importedComputedName || getImportIdentifier(node, '@ember/object', 'computed');
+          importedGetName = importedGetName || getImportIdentifier(node, '@ember/object', 'get');
+          importedGetPropertiesName =
+            importedGetPropertiesName ||
+            getImportIdentifier(node, '@ember/object', 'getProperties');
+          importedGetWithDefaultName =
+            importedGetWithDefaultName ||
+            getImportIdentifier(node, '@ember/object', 'getWithDefault');
         }
       },
 
       Identifier(node) {
         if (isEmberComputed(node, importedEmberName, importedComputedName)) {
-          checkComputedDependencies(node, [], importedEmberName);
+          checkComputedDependencies(node, [], {
+            importedEmberName,
+            importedGetName,
+            importedGetPropertiesName,
+            importedGetWithDefaultName,
+          });
         }
       },
 
       CallExpression(node) {
         if (isEmberComputed(node.callee, importedEmberName, importedComputedName)) {
-          checkComputedDependencies(node, node.arguments, importedEmberName);
+          checkComputedDependencies(node, node.arguments, {
+            importedEmberName,
+            importedGetName,
+            importedGetPropertiesName,
+            importedGetWithDefaultName,
+          });
         }
       },
     };


Test patch:
diff --git a/tests/lib/rules/require-computed-property-dependencies.js b/tests/lib/rules/require-computed-property-dependencies.js
index 076ecd82..f5f6aa07 100644
--- a/tests/lib/rules/require-computed-property-dependencies.js
+++ b/tests/lib/rules/require-computed-property-dependencies.js
@@ -1043,5 +1043,44 @@ ruleTester.run('require-computed-property-dependencies', rule, {
         },
       ],
     },
+    {
+      // Renamed get import:
+      code:
+        "import { computed, get as g } from '@ember/object'; computed(function() { return g(this, 'foo'); });",
+      output:
+        "import { computed, get as g } from '@ember/object'; computed('foo', function() { return g(this, 'foo'); });",
+      errors: [
+        {
+          message: 'Use of undeclared dependencies in computed property: foo',
+          type: 'CallExpression',
+        },
+      ],
+    },
+    {
+      // Renamed getProperties import:
+      code:
+        "import { computed, getProperties as gp } from '@ember/object'; computed(function() { return gp(this, 'foo'); });",
+      output:
+        "import { computed, getProperties as gp } from '@ember/object'; computed('foo', function() { return gp(this, 'foo'); });",
+      errors: [
+        {
+          message: 'Use of undeclared dependencies in computed property: foo',
+          type: 'CallExpression',
+        },
+      ],
+    },
+    {
+      // Renamed getWithDefault import:
+      code:
+        "import { computed, getWithDefault as gwd } from '@ember/object'; computed(function() { return gwd(this, 'foo', 'bar'); });",
+      output:
+        "import { computed, getWithDefault as gwd } from '@ember/object'; computed('foo', function() { return gwd(this, 'foo', 'bar'); });",
+      errors: [
+        {
+          message: 'Use of undeclared dependencies in computed property: foo',
+          type: 'CallExpression',
+        },
+      ],
+    },
   ],
 });