Repository: ember-cli/eslint-plugin-ember
Pull request #907 (2020-08-07 16:40:49)

Check imports in `require-computed-property-dependencies` rule
Checks what name `Ember` and `computed` are imported under when detecting computed properties, instead of just assuming that they are imported under the standard names.

CC: @mongoose700 

Helps with #590.


Patch:
diff --git a/lib/rules/require-computed-property-dependencies.js b/lib/rules/require-computed-property-dependencies.js
index 33164e1e..ef3218a4 100644
--- a/lib/rules/require-computed-property-dependencies.js
+++ b/lib/rules/require-computed-property-dependencies.js
@@ -9,12 +9,13 @@ const utils = require('../utils/utils');
 const propertyGetterUtils = require('../utils/property-getter');
 const computedPropertyDependentKeyUtils = require('../utils/computed-property-dependent-keys');
 const assert = require('assert');
+const { getImportIdentifier } = require('../utils/import');
 
 /**
- * Checks whether the node is an identifier and optionally, its name.
+ * Checks whether the node is an identifier with the given name.
  *
  * @param {ASTNode} node
- * @param {string=} name
+ * @param {string} name
  * @returns {boolean}
  */
 function isIdentifier(node, name) {
@@ -22,11 +23,7 @@ function isIdentifier(node, name) {
     return false;
   }
 
-  if (name) {
-    return node.name === name;
-  }
-
-  return true;
+  return node.name === name;
 }
 
 /**
@@ -39,10 +36,6 @@ function isIdentifier(node, name) {
  * @returns {boolean}
  */
 function isMemberExpression(node, objectName, propertyName) {
-  if (!objectName && !propertyName) {
-    return node && types.isMemberExpression(node);
-  }
-
   return (
     node &&
     types.isMemberExpression(node) &&
@@ -56,10 +49,15 @@ function isMemberExpression(node, objectName, propertyName) {
 
 /**
  * @param {ASTNode} node
+ * @param {string} importedEmberName
+ * @param {string} importedComputedName
  * @returns {boolean}
  */
-function isEmberComputed(node) {
-  return isIdentifier(node, 'computed') || isMemberExpression(node, 'Ember', 'computed');
+function isEmberComputed(node, importedEmberName, importedComputedName) {
+  return (
+    isIdentifier(node, importedComputedName) ||
+    isMemberExpression(node, importedEmberName, 'computed')
+  );
 }
 
 /**
@@ -119,15 +117,16 @@ function parseComputedDependencies(args) {
  * or `this.get(…)`.
  *
  * @param {ASTNode} node
+ * @param {string} importedEmberName
  * @returns {Array<ASTNode>}
  */
-function findEmberGetCalls(node) {
+function findEmberGetCalls(node, importedEmberName) {
   const results = [];
 
   new Traverser().traverse(node, {
     enter(child) {
       if (types.isCallExpression(child)) {
-        const dependency = extractEmberGetDependencies(child);
+        const dependency = extractEmberGetDependencies(child, importedEmberName);
 
         if (dependency.length > 0) {
           results.push(child);
@@ -212,9 +211,10 @@ function getArrayOrRest(args) {
  * Extracts all static property keys used in the various forms of `Ember.get`.
  *
  * @param {ASTNode} call
+ * @param {string} importedEmberName
  * @returns {Array<string>}
  */
-function extractEmberGetDependencies(call) {
+function extractEmberGetDependencies(call, importedEmberName) {
   if (
     isMemberExpression(call.callee, 'this', 'get') ||
     isMemberExpression(call.callee, 'this', 'getWithDefault')
@@ -225,8 +225,8 @@ function extractEmberGetDependencies(call) {
       return [firstArg.value];
     }
   } else if (
-    isMemberExpression(call.callee, 'Ember', 'get') ||
-    isMemberExpression(call.callee, 'Ember', 'getWithDefault')
+    isMemberExpression(call.callee, importedEmberName, 'get') ||
+    isMemberExpression(call.callee, importedEmberName, 'getWithDefault')
   ) {
     const firstArg = call.arguments[0];
     const secondArgument = call.arguments[1];
@@ -238,7 +238,7 @@ function extractEmberGetDependencies(call) {
     return getArrayOrRest(call.arguments)
       .filter(types.isStringLiteral)
       .map((arg) => arg.value);
-  } else if (isMemberExpression(call.callee, 'Ember', 'getProperties')) {
+  } else if (isMemberExpression(call.callee, importedEmberName, 'getProperties')) {
     const firstArg = call.arguments[0];
     const rest = call.arguments.slice(1);
 
@@ -311,7 +311,10 @@ module.exports = {
 
     let serviceNames = [];
 
-    function checkComputedDependencies(node, nodeArguments) {
+    let importedEmberName;
+    let importedComputedName;
+
+    function checkComputedDependencies(node, nodeArguments, importedEmberName) {
       const declaredDependencies = parseComputedDependencies(nodeArguments);
 
       if (!allowDynamicKeys) {
@@ -328,14 +331,12 @@ module.exports = {
       );
 
       const usedKeys1 = javascriptUtils.flatMap(
-        findEmberGetCalls(computedPropertyFunctionBody),
-        extractEmberGetDependencies
+        findEmberGetCalls(computedPropertyFunctionBody, importedEmberName),
+        (node) => extractEmberGetDependencies(node, importedEmberName)
       );
       const usedKeys2 = javascriptUtils.flatMap(
         findThisGetCalls(computedPropertyFunctionBody),
-        (node) => {
-          return extractThisGetDependencies(node, context);
-        }
+        (node) => extractThisGetDependencies(node, context)
       );
       const usedKeys = [...usedKeys1, ...usedKeys2];
 
@@ -451,15 +452,25 @@ module.exports = {
         serviceNames = requireServiceNames ? [] : findInjectedServiceNames(node);
       },
 
+      ImportDeclaration(node) {
+        if (node.source.value === 'ember') {
+          importedEmberName = importedEmberName || getImportIdentifier(node, 'ember');
+        }
+        if (node.source.value === '@ember/object') {
+          importedComputedName =
+            importedComputedName || getImportIdentifier(node, '@ember/object', 'computed');
+        }
+      },
+
       Identifier(node) {
-        if (isEmberComputed(node)) {
-          checkComputedDependencies(node, []);
+        if (isEmberComputed(node, importedEmberName, importedComputedName)) {
+          checkComputedDependencies(node, [], importedEmberName);
         }
       },
 
       CallExpression(node) {
-        if (isEmberComputed(node.callee)) {
-          checkComputedDependencies(node, node.arguments);
+        if (isEmberComputed(node.callee, importedEmberName, importedComputedName)) {
+          checkComputedDependencies(node, node.arguments, importedEmberName);
         }
       },
     };


Test patch:
diff --git a/tests/lib/rules/require-computed-property-dependencies.js b/tests/lib/rules/require-computed-property-dependencies.js
index 8e5e1bb0..076ecd82 100644
--- a/tests/lib/rules/require-computed-property-dependencies.js
+++ b/tests/lib/rules/require-computed-property-dependencies.js
@@ -15,63 +15,68 @@ const ruleTester = new RuleTester({
 });
 ruleTester.run('require-computed-property-dependencies', rule, {
   valid: [
-    'Ember.computed();',
-    'Ember.computed(function() {});',
-    "Ember.computed('unused', function() {});",
+    "import Ember from 'ember'; Ember.computed();",
+    "import Ember from 'ember'; Ember.computed(function() {});",
+    "import Ember from 'ember'; Ember.computed('unused', function() {});",
     // Volatile:
-    "Ember.computed('name', function() { return this.get('name'); }).volatile()",
+    "import Ember from 'ember'; Ember.computed('name', function() { return this.get('name'); }).volatile()",
     // ES5 getter usage:
-    "Ember.computed('name', function() { return this.name; });",
-    "Ember.computed('name', function() { return this.get('name'); });",
+    "import Ember from 'ember'; Ember.computed('name', function() { return this.name; });",
+    "import Ember from 'ember'; Ember.computed('name', function() { return this.get('name'); });",
     // String concatenation in dependent key:
-    " Ember.computed('na' + 'me', function() { return this.get('name'); });",
+    "import Ember from 'ember';  Ember.computed('na' + 'me', function() { return this.get('name'); });",
     // Optional chaining:
-    'Ember.computed(function() { return this?.someFunction(); });',
-    "Ember.computed('x.y', function() { return this?.x?.y });",
+    "import Ember from 'ember'; Ember.computed(function() { return this?.someFunction(); });",
+    "import Ember from 'ember'; Ember.computed('x.y', function() { return this?.x?.y });",
     // Without `Ember.`:
-    "computed('name', function() {return this.get('name');});",
+    "import { computed } from '@ember/object'; computed('name', function() {return this.get('name');});",
     `
+      import Ember from 'ember';
       Ember.computed('list.@each.foo', function() {
         return this.get('list').map(function(item) {
           return item.get('foo');
         });
       });
     `,
-    "Ember.computed('list.[]', function() { return this.get('list.length');});",
-    "Ember.computed('deeper.than.needed.but.okay', function() { return this.get('deeper'); });",
-    "Ember.computed('array.[]', function() { return this.get('array.firstObject'); });",
-    "Ember.computed('array.[]', function() { return this.get('array.lastObject'); });",
-    "Ember.computed('foo.{bar,baz}', function() { return this.get('foo.bar') + this.get('foo.baz'); });",
-    "Ember.computed('foo.@each.{bar,baz}', function() { return this.get('foo').mapBy('bar') + this.get('foo').mapBy('bar'); });",
+    "import Ember from 'ember'; Ember.computed('list.[]', function() { return this.get('list.length');});",
+    "import Ember from 'ember'; Ember.computed('deeper.than.needed.but.okay', function() { return this.get('deeper'); });",
+    "import Ember from 'ember'; Ember.computed('array.[]', function() { return this.get('array.firstObject'); });",
+    "import Ember from 'ember'; Ember.computed('array.[]', function() { return this.get('array.lastObject'); });",
+    "import Ember from 'ember'; Ember.computed('foo.{bar,baz}', function() { return this.get('foo.bar') + this.get('foo.baz'); });",
+    "import Ember from 'ember'; Ember.computed('foo.@each.{bar,baz}', function() { return this.get('foo').mapBy('bar') + this.get('foo').mapBy('bar'); });",
     // Array inside braces:
     `
+      import Ember from 'ember';
       Ember.computed('article.{comments.[],title}', function() {
         return this.article.title + someFunction(this.article.comments.mapBy(function(comment) { return comment.author; }));
       });
     `,
     // Nesting inside braces:
     `
+      import Ember from 'ember';
       Ember.computed('article.{comments.innerProperty,title}', function() {
         return this.article.title + someFunction(this.article.comments.innerProperty);
       });
     `,
     // Braces but no nesting:
     `
+      import Ember from 'ember';
       Ember.computed('{foo,bar}', function() {
         return this.get('foo') + this.get('bar');
       });
     `,
     // Computed macro that should be ignored:
-    'Ember.computed.someMacro(function() { return this.x; })',
-    "Ember.computed.someMacro('test')",
+    "import Ember from 'ember'; Ember.computed.someMacro(function() { return this.x; })",
+    "import Ember from 'ember'; Ember.computed.someMacro('test')",
     // Dynamic key:
-    'Ember.computed(dynamic, function() {});',
+    "import Ember from 'ember'; Ember.computed(dynamic, function() {});",
     // Dynamic key:
-    'Ember.computed(...PROPERTIES, function() {});',
+    "import Ember from 'ember'; Ember.computed(...PROPERTIES, function() {});",
     // Incorrect usage that should be ignored:
-    'Ember.computed(123)',
+    "import Ember from 'ember'; Ember.computed(123)",
     // Should ignore injected service names:
     `
+      import Ember from 'ember';
       import Component from '@ember/component';
       import { inject as service } from '@ember/service';
       Component.extend({
@@ -85,11 +90,12 @@ ruleTester.run('require-computed-property-dependencies', rule, {
       });
     `,
     // Should ignore the left side of an assignment.
-    "Ember.computed('right', function() { this.left = this.right; })",
+    "import Ember from 'ember'; Ember.computed('right', function() { this.left = this.right; })",
     // Should ignore the left side of an assignment with nested path.
-    "Ember.computed('right', function() { this.left1.left2 = this.right; })",
+    "import Ember from 'ember'; Ember.computed('right', function() { this.left1.left2 = this.right; })",
     // Explicit getter function:
     `
+      import { computed } from '@ember/object';
       computed('firstName', 'lastName', {
         get() {
           return this.firstName + ' ' + this.lastName;
@@ -99,6 +105,7 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     `,
     // Decorator:
     `
+      import { computed } from '@ember/object';
       class Test {
         @computed('first', 'last')
         get fullName() { return this.first + ' ' + this.last; }
@@ -106,6 +113,8 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     `,
     // Decorator:
     `
+      import { computed } from '@ember/object';
+      import { inject as service } from '@ember/service';
       class Test {
         @service i18n; // Service names not required as dependent keys by default.
         @computed('first', 'last')
@@ -116,7 +125,7 @@ ruleTester.run('require-computed-property-dependencies', rule, {
   invalid: [
     // Dynamic key:
     {
-      code: 'Ember.computed(dynamic, function() {});',
+      code: "import Ember from 'ember'; Ember.computed(dynamic, function() {});",
       output: null,
       options: [{ allowDynamicKeys: false }],
       errors: [
@@ -128,7 +137,7 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     },
     // Dynamic keys:
     {
-      code: 'Ember.computed(...PROPERTIES, function() {});',
+      code: "import Ember from 'ember'; Ember.computed(...PROPERTIES, function() {});",
       output: null,
       options: [{ allowDynamicKeys: false }],
       errors: [
@@ -140,8 +149,10 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     },
     // Dynamic key with missing dependency:
     {
-      code: 'Ember.computed(dynamic, function() { return this.undeclared; });',
-      output: "Ember.computed(dynamic, 'undeclared', function() { return this.undeclared; });",
+      code:
+        "import Ember from 'ember'; Ember.computed(dynamic, function() { return this.undeclared; });",
+      output:
+        "import Ember from 'ember'; Ember.computed(dynamic, 'undeclared', function() { return this.undeclared; });",
       options: [{ allowDynamicKeys: false }],
       errors: [
         {
@@ -156,9 +167,10 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     },
     // Multiple dynamic (identifier and spread) keys with missing dependency:
     {
-      code: 'Ember.computed(dynamic, ...moreDynamic, function() { return this.undeclared; });',
+      code:
+        "import Ember from 'ember'; Ember.computed(dynamic, ...moreDynamic, function() { return this.undeclared; });",
       output:
-        "Ember.computed(dynamic, ...moreDynamic, 'undeclared', function() { return this.undeclared; });",
+        "import Ember from 'ember'; Ember.computed(dynamic, ...moreDynamic, 'undeclared', function() { return this.undeclared; });",
       options: [{ allowDynamicKeys: false }],
       errors: [
         {
@@ -177,11 +189,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     },
     {
       code: `
+        import Ember from 'ember';
         Ember.computed(function() {
           return Ember.get(this, 'undeclared');
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('undeclared', function() {
           return Ember.get(this, 'undeclared');
         });
@@ -195,11 +209,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     },
     {
       code: `
+        import Ember from 'ember';
         Ember.computed(function() {
           return this.get('undeclared') + this.get('undeclared2');
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('undeclared', 'undeclared2', function() {
           return this.get('undeclared') + this.get('undeclared2');
         });
@@ -214,11 +230,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     // Volatile:
     {
       code: `
+        import Ember from 'ember';
         Ember.computed(function() {
           return this.get('undeclared');
         }).volatile();
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('undeclared', function() {
           return this.get('undeclared');
         }).volatile();
@@ -233,11 +251,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     // ES5 getter usage:
     {
       code: `
+        import Ember from 'ember';
         Ember.computed(function() {
           return this.undeclared + this.undeclared2;
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('undeclared', 'undeclared2', function() {
           return this.undeclared + this.undeclared2;
         });
@@ -252,11 +272,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     // ES5 getter usage inside function call:
     {
       code: `
+        import Ember from 'ember';
         Ember.computed(function() {
           return someFunction(this.undeclared) + some.thing(this.undeclared2) + some(this.undeclared3).thing;
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('undeclared', 'undeclared2', 'undeclared3', function() {
           return someFunction(this.undeclared) + some.thing(this.undeclared2) + some(this.undeclared3).thing;
         });
@@ -272,11 +294,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     // ES5 getter usage inside array index:
     {
       code: `
+        import Ember from 'ember';
         Ember.computed(function() {
           return someArray[this.undeclared];
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('undeclared', function() {
           return someArray[this.undeclared];
         });
@@ -291,11 +315,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     // ES5 getter usage with nesting:
     {
       code: `
+        import Ember from 'ember';
         Ember.computed(function() {
           return this.a.b.c;
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('a.b.c', function() {
           return this.a.b.c;
         });
@@ -311,11 +337,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     // TODO: an improvement would be to detect the missing `someArray.[]` dependency key.
     {
       code: `
+        import Ember from 'ember';
         Ember.computed(function() {
           return this.someArray[123];
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('someArray', function() {
           return this.someArray[123];
         });
@@ -330,11 +358,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     // ES5 getter usage with array/object access:
     {
       code: `
+        import Ember from 'ember';
         Ember.computed(function() {
           return this.someArrayOrObject[index];
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('someArrayOrObject', function() {
           return this.someArrayOrObject[index];
         });
@@ -349,11 +379,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     // With function calls on properties.
     {
       code: `
+        import Ember from 'ember';
         Ember.computed(function() {
           return this.get('service1').someFunction() + this.service2.someFunction();
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('service1', 'service2', function() {
           return this.get('service1').someFunction() + this.service2.someFunction();
         });
@@ -368,11 +400,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     {
       // Without `Ember.`:
       code: `
+        import { computed } from '@ember/object';
         computed(function() {
           return this.get('undeclared') + this.get('undeclared2');
         });
       `,
       output: `
+        import { computed } from '@ember/object';
         computed('undeclared', 'undeclared2', function() {
           return this.get('undeclared') + this.get('undeclared2');
         });
@@ -386,11 +420,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     },
     {
       code: `
+        import Ember from 'ember';
         Ember.computed(function() {
           return this.get('undeclared') + this.get('undeclared');
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('undeclared', function() {
           return this.get('undeclared') + this.get('undeclared');
         });
@@ -404,11 +440,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     },
     {
       code: `
+        import Ember from 'ember';
         Ember.computed(function() {
           return this.get('foo.bar.baz');
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('foo.bar.baz', function() {
           return this.get('foo.bar.baz');
         });
@@ -423,11 +461,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     // Ensure that the redundant key (`foo`) is removed.
     {
       code: `
+        import Ember from 'ember';
         Ember.computed('foo', function() {
           return this.get('foo.bar.baz');
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('foo.bar.baz', function() {
           return this.get('foo.bar.baz');
         });
@@ -441,11 +481,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     },
     {
       code: `
+        import Ember from 'ember';
         Ember.computed(function() {
           return this.getProperties('a', dynamic, 'b', 'c');
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('a', 'b', 'c', function() {
           return this.getProperties('a', dynamic, 'b', 'c');
         });
@@ -459,11 +501,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     },
     {
       code: `
+        import Ember from 'ember';
         Ember.computed(function() {
           return this.getProperties(['a', dynamic, 'b', 'c']);
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('a', 'b', 'c', function() {
           return this.getProperties(['a', dynamic, 'b', 'c']);
         });
@@ -477,11 +521,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     },
     {
       code: `
+        import Ember from 'ember';
         Ember.computed(function() {
           return Ember.getProperties(this, ['a', dynamic, 'b', 'c']);
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('a', 'b', 'c', function() {
           return Ember.getProperties(this, ['a', dynamic, 'b', 'c']);
         });
@@ -495,11 +541,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     },
     {
       code: `
+        import Ember from 'ember';
         Ember.computed(function() {
           return this.getWithDefault('maybe', {});
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('maybe', function() {
           return this.getWithDefault('maybe', {});
         });
@@ -513,11 +561,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     },
     {
       code: `
+        import Ember from 'ember';
         Ember.computed(function() {
           return Ember.getWithDefault(this, 'maybe', {});
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('maybe', function() {
           return Ember.getWithDefault(this, 'maybe', {});
         });
@@ -531,11 +581,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     },
     {
       code: `
+        import Ember from 'ember';
         Ember.computed('constructor.bar', function() {
           return this.get('constructor.bar') + this.get('constructor.baz');
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('constructor.{bar,baz}', function() {
           return this.get('constructor.bar') + this.get('constructor.baz');
         });
@@ -550,11 +602,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     // Ensure that the fixer removes the redundant `quux.length` dependency.
     {
       code: `
+        import Ember from 'ember';
         Ember.computed('foo.bar', 'quux.[]', 'quux.length', function() {
           return this.get('foo.bar') + this.get('foo.baz') + this.get('quux.firstObject.test');
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('foo.{bar,baz}', 'quux.[]', 'quux.firstObject.test', function() {
           return this.get('foo.bar') + this.get('foo.baz') + this.get('quux.firstObject.test');
         });
@@ -570,11 +624,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     // TODO: this should actually be a valid test case because the property added by the fixer (`quux.firstObject.myProp`) is redundant.
     {
       code: `
+        import Ember from 'ember';
         Ember.computed('quux.@each.myProp', function() {
           return this.get('quux.firstObject.myProp');
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('quux.@each.myProp', 'quux.firstObject.myProp', function() {
           return this.get('quux.firstObject.myProp');
         });
@@ -589,12 +645,14 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     // Gracefully handles nesting/array inside braces:
     {
       code: `
+        import Ember from 'ember';
         Ember.computed('article.{comments.[],title,first.second}', function() {
           return this.article.title + this.missingProp + someFunction(this.article.comments) + this.article.first.second;
         });
       `,
       // TODO: an improvement would be to use braces here: 'article.{comments.[],first.second,title}'
       output: `
+        import Ember from 'ember';
         Ember.computed('article.comments.[]', 'article.first.second', 'article.title', 'missingProp', function() {
           return this.article.title + this.missingProp + someFunction(this.article.comments) + this.article.first.second;
         });
@@ -609,11 +667,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     {
       // don't expand @each.{foo,bar}
       code: `
+        import Ember from 'ember';
         Ember.computed('foo.@each.{bar,baz}', function() {
           return this.get('foo').mapBy('bar') + this.get('quux');
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('foo.@each.{bar,baz}', 'quux', function() {
           return this.get('foo').mapBy('bar') + this.get('quux');
         });
@@ -628,6 +688,7 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     {
       // Catch missing injected service name with `requireServiceNames` enabled:
       code: `
+        import Ember from 'ember';
         import Component from '@ember/component';
         import { inject as service } from '@ember/service';
         Component.extend({
@@ -640,6 +701,7 @@ ruleTester.run('require-computed-property-dependencies', rule, {
       `,
       options: [{ requireServiceNames: true }],
       output: `
+        import Ember from 'ember';
         import Component from '@ember/component';
         import { inject as service } from '@ember/service';
         Component.extend({
@@ -660,11 +722,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     {
       // Should ignore the left side of an assignment but not the right side.
       code: `
+        import Ember from 'ember';
         Ember.computed(function() {
           this.left = this.right;
         })
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('right', function() {
           this.left = this.right;
         })
@@ -679,6 +743,7 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     {
       // Should not ignore properties inside injected service:
       code: `
+        import Ember from 'ember';
         import Component from '@ember/component';
         import { inject as service } from '@ember/service';
         Component.extend({
@@ -689,6 +754,7 @@ ruleTester.run('require-computed-property-dependencies', rule, {
         });
       `,
       output: `
+        import Ember from 'ember';
         import Component from '@ember/component';
         import { inject as service } from '@ember/service';
         Component.extend({
@@ -707,12 +773,14 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     },
     {
       code: `
+        import Ember from 'ember';
         Ember.computed(function() {
           return this.some.very.long.
             multi.line.property.name;
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('some.very.long.multi.line.property.name', function() {
           return this.some.very.long.
             multi.line.property.name;
@@ -729,11 +797,13 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     {
       // String concatenation in dependent key:
       code: `
+        import Ember from 'ember';
         Ember.computed('na' + 'me', function() {
           return this.undeclared + this.name;
         });
       `,
       output: `
+        import Ember from 'ember';
         Ember.computed('name', 'undeclared', function() {
           return this.undeclared + this.name;
         });
@@ -748,6 +818,7 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     {
       // Explicit getter function:
       code: `
+        import { computed } from '@ember/object';
         computed('firstName', {
           get() {
             return this.firstName + ' ' + this.lastName;
@@ -756,6 +827,7 @@ ruleTester.run('require-computed-property-dependencies', rule, {
         })
       `,
       output: `
+        import { computed } from '@ember/object';
         computed('firstName', 'lastName', {
           get() {
             return this.firstName + ' ' + this.lastName;
@@ -773,6 +845,7 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     // Decorator with getter inside object parameter:
     {
       code: `
+        import { computed } from '@ember/object';
         class Test {
           @computed('firstName', {
             get() {
@@ -784,6 +857,7 @@ ruleTester.run('require-computed-property-dependencies', rule, {
         }
       `,
       output: `
+        import { computed } from '@ember/object';
         class Test {
           @computed('firstName', 'lastName', {
             get() {
@@ -804,12 +878,14 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     // Decorator with no parens:
     {
       code: `
+        import { computed } from '@ember/object';
         class Test {
           @computed
           get someProp() { return this.undeclared; }
         }
       `,
       output: `
+        import { computed } from '@ember/object';
         class Test {
           @computed('undeclared')
           get someProp() { return this.undeclared; }
@@ -825,12 +901,14 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     // Decorator with no args:
     {
       code: `
+        import { computed } from '@ember/object';
         class Test {
           @computed()
           get someProp() { return this.undeclared; }
         }
       `,
       output: `
+        import { computed } from '@ember/object';
         class Test {
           @computed('undeclared')
           get someProp() { return this.undeclared; }
@@ -846,12 +924,14 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     // Decorator with arg:
     {
       code: `
+        import { computed } from '@ember/object';
         class Test {
           @computed('first')
           get fullName() { return this.first + ' ' + this.last; }
         }
       `,
       output: `
+        import { computed } from '@ember/object';
         class Test {
           @computed('first', 'last')
           get fullName() { return this.first + ' ' + this.last; }
@@ -867,12 +947,14 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     // Decorator with two arg:
     {
       code: `
+        import { computed } from '@ember/object';
         class Test {
           @computed('first', 'last')
           get fullName() { return this.first + ' ' + this.last + ' ' + this.undeclared; }
         }
       `,
       output: `
+        import { computed } from '@ember/object';
         class Test {
           @computed('first', 'last', 'undeclared')
           get fullName() { return this.first + ' ' + this.last + ' ' + this.undeclared; }
@@ -888,8 +970,10 @@ ruleTester.run('require-computed-property-dependencies', rule, {
 
     {
       // Optional chaining:
-      code: 'computed(function() { return this.x?.y?.z; })',
-      output: "computed('x.y.z', function() { return this.x?.y?.z; })",
+      code:
+        "import { computed } from '@ember/object'; computed(function() { return this.x?.y?.z; })",
+      output:
+        "import { computed } from '@ember/object'; computed('x.y.z', function() { return this.x?.y?.z; })",
       errors: [
         {
           message: 'Use of undeclared dependencies in computed property: x.y.z',
@@ -899,8 +983,10 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     },
     {
       // Optional chaining plus overlap with non-optional-chaining:
-      code: 'computed(function() { return this.x?.y?.z + this.x.y.foo; })',
-      output: "computed('x.y.{foo,z}', function() { return this.x?.y?.z + this.x.y.foo; })",
+      code:
+        "import { computed } from '@ember/object'; computed(function() { return this.x?.y?.z + this.x.y.foo; })",
+      output:
+        "import { computed } from '@ember/object'; computed('x.y.{foo,z}', function() { return this.x?.y?.z + this.x.y.foo; })",
       errors: [
         {
           message: 'Use of undeclared dependencies in computed property: x.y.foo, x.y.z',
@@ -910,8 +996,10 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     },
     {
       // Optional chaining with function call:
-      code: 'computed(function() { return this.x?.y?.someFunction(); })',
-      output: "computed('x.y', function() { return this.x?.y?.someFunction(); })",
+      code:
+        "import { computed } from '@ember/object'; computed(function() { return this.x?.y?.someFunction(); })",
+      output:
+        "import { computed } from '@ember/object'; computed('x.y', function() { return this.x?.y?.someFunction(); })",
       errors: [
         {
           message: 'Use of undeclared dependencies in computed property: x.y',
@@ -921,9 +1009,10 @@ ruleTester.run('require-computed-property-dependencies', rule, {
     },
     {
       // Optional chaining with array/object access:
-      code: 'computed(function() { return this.x?.someArrayOrObject[index]; })',
+      code:
+        "import { computed } from '@ember/object'; computed(function() { return this.x?.someArrayOrObject[index]; })",
       output:
-        "computed('x.someArrayOrObject', function() { return this.x?.someArrayOrObject[index]; })",
+        "import { computed } from '@ember/object'; computed('x.someArrayOrObject', function() { return this.x?.someArrayOrObject[index]; })",
       errors: [
         {
           message: 'Use of undeclared dependencies in computed property: x.someArrayOrObject',
@@ -931,5 +1020,28 @@ ruleTester.run('require-computed-property-dependencies', rule, {
         },
       ],
     },
+    {
+      // Renamed Ember import:
+      code: "import E from 'ember'; E.computed(function() { return this.foo; });",
+      output: "import E from 'ember'; E.computed('foo', function() { return this.foo; });",
+      errors: [
+        {
+          message: 'Use of undeclared dependencies in computed property: foo',
+          type: 'CallExpression',
+        },
+      ],
+    },
+    {
+      // Renamed computed import:
+      code: "import { computed as c } from '@ember/object'; c(function() { return this.foo; });",
+      output:
+        "import { computed as c } from '@ember/object'; c('foo', function() { return this.foo; });",
+      errors: [
+        {
+          message: 'Use of undeclared dependencies in computed property: foo',
+          type: 'CallExpression',
+        },
+      ],
+    },
   ],
 });