From 298a1c7c8d66baf74d461c689d1cd0255a4afd4e Mon Sep 17 00:00:00 2001 From: Bryan Mishkin <698306+bmish@users.noreply.github.com> Date: Fri, 7 Aug 2020 12:17:14 -0400 Subject: [PATCH] fix: check imports in require-computed-property-dependencies rule --- .../require-computed-property-dependencies.js | 69 ++++--- .../require-computed-property-dependencies.js | 186 ++++++++++++++---- 2 files changed, 189 insertions(+), 66 deletions(-) diff --git a/lib/rules/require-computed-property-dependencies.js b/lib/rules/require-computed-property-dependencies.js index 33164e1ed0..ef3218a4c4 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} */ -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} */ -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); } }, }; diff --git a/tests/lib/rules/require-computed-property-dependencies.js b/tests/lib/rules/require-computed-property-dependencies.js index 8e5e1bb017..076ecd82e9 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', + }, + ], + }, ], });