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

Issue:
Template literals not recognized by order-in-* rules.
When enforcing component order I have a set of defaults
```
group: null,
sizeLimit: 1048576, // 1Mb
calendarSort: Object.freeze(['name']),
uploadEndpoint: `${ENV.cosConfig.api_endpoint}/upload`,

allCalendarsSelected: computed('sortedCalendars.[]', 'group.calendar_ids.[]', function () {
  return this.get('sortedCalendars.length') === this.get('group.calendar_ids.length');
}),
```

having something like that tells me that the property complains with this error
![screen shot 2018-10-17 at 9 40 33 am](https://user-images.githubusercontent.com/2464593/47094407-c444ce80-d1f0-11e8-889f-e4f6542c5e0b.png)


Pull request:
Update `order-in-*` rules to consider template literals as properties
Fixes #304 

Patch:
diff --git a/README.md b/README.md
index 0417286f..519e0429 100644
--- a/README.md
+++ b/README.md
@@ -67,7 +67,6 @@ Each rule has emojis denoting what configuration it belongs to and/or a :wrench:
 | :white_check_mark: | [closure-actions](./docs/rules/closure-actions.md) | enforce usage of closure actions |
 |  | [named-functions-in-promises](./docs/rules/named-functions-in-promises.md) | enforce usage of named functions in promises |
 | :white_check_mark: | [new-module-imports](./docs/rules/new-module-imports.md) | enforce using "New Module Imports" from Ember RFC #176 |
-|  | [no-controllers](./docs/rules/no-controllers.md) | disallow non-essential controllers |
 | :white_check_mark: | [no-function-prototype-extensions](./docs/rules/no-function-prototype-extensions.md) | disallow usage of Ember's `function` prototype extensions |
 | :car: | [no-get-with-default](./docs/rules/no-get-with-default.md) | disallow usage of the Ember's `getWithDefault` function |
 | :car::wrench: | [no-get](./docs/rules/no-get.md) | require using ES5 getters instead of Ember's `get` / `getProperties` functions |
diff --git a/docs/rules/no-controllers.md b/docs/rules/no-controllers.md
deleted file mode 100644
index 8960b4bf..00000000
--- a/docs/rules/no-controllers.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# no-controllers
-
-Some people may prefer to avoid the use of controllers in their applications, typically in favor of components which can be more portable and easier to test.
-
-This rule disallows controller usage, except when the controller is used to define `queryParams`.
-
-## Examples
-
-Examples of **incorrect** code for this rule:
-
-```js
-import Controller from '@ember/controller';
-export default class ArticlesController extends Controller {
-  // Controller disallowed since it doesn't use `queryParams`.
-  ...
-}
-```
-
-Examples of **correct** code for this rule:
-
-```js
-import Controller from '@ember/controller';
-export default class ArticlesController extends Controller {
-  queryParams = ['category']; // Controller allowed for defining `queryParams`.
-  @tracked category = null;
-  ...
-}
-```
-
-Note that this rule supports both classic and native class syntax.
-
-## Further Reading
-
-- [Guide on controllers](https://guides.emberjs.com/release/routing/controllers/)
-- [Guide on `queryParameters`](https://guides.emberjs.com/release/routing/query-params/)
diff --git a/lib/index.js b/lib/index.js
index dd348d6d..1f2163b2 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -20,7 +20,6 @@ module.exports = {
     'no-classic-classes': require('./rules/no-classic-classes'),
     'no-classic-components': require('./rules/no-classic-components'),
     'no-computed-properties-in-native-classes': require('./rules/no-computed-properties-in-native-classes'),
-    'no-controllers': require('./rules/no-controllers'),
     'no-deeply-nested-dependent-keys-with-each': require('./rules/no-deeply-nested-dependent-keys-with-each'),
     'no-duplicate-dependent-keys': require('./rules/no-duplicate-dependent-keys'),
     'no-ember-super-in-es-classes': require('./rules/no-ember-super-in-es-classes'),
diff --git a/lib/recommended-rules.js b/lib/recommended-rules.js
index 1b717845..5640f977 100644
--- a/lib/recommended-rules.js
+++ b/lib/recommended-rules.js
@@ -23,7 +23,6 @@ module.exports = {
   "ember/no-classic-classes": "off",
   "ember/no-classic-components": "off",
   "ember/no-computed-properties-in-native-classes": "off",
-  "ember/no-controllers": "off",
   "ember/no-deeply-nested-dependent-keys-with-each": "error",
   "ember/no-duplicate-dependent-keys": "error",
   "ember/no-ember-super-in-es-classes": "error",
diff --git a/lib/rules/no-classic-components.js b/lib/rules/no-classic-components.js
index 3224b808..b08d21be 100644
--- a/lib/rules/no-classic-components.js
+++ b/lib/rules/no-classic-components.js
@@ -1,8 +1,5 @@
 'use strict';
 
-const types = require('../utils/types');
-const assert = require('assert');
-
 const ERROR_MESSAGE =
   'Use Glimmer components(@glimmer/component) instead of classic components(@ember/component)';
 
@@ -30,18 +27,12 @@ module.exports = {
 
     return {
       ImportDeclaration(node) {
-        if (node.source.value === '@ember/component') {
-          const importDefaultSpecifier = getImportDefaultSpecifier(node);
-          if (importDefaultSpecifier) {
-            report(importDefaultSpecifier);
-          }
+        const importSource = node.source.value;
+
+        if (importSource === '@ember/component') {
+          report(node);
         }
       },
     };
   },
 };
-
-function getImportDefaultSpecifier(node) {
-  assert(types.isImportDeclaration(node));
-  return node.specifiers.find(specifier => types.isImportDefaultSpecifier(specifier));
-}
diff --git a/lib/rules/no-controllers.js b/lib/rules/no-controllers.js
deleted file mode 100644
index 638ea8ab..00000000
--- a/lib/rules/no-controllers.js
+++ /dev/null
@@ -1,67 +0,0 @@
-const ember = require('../utils/ember');
-const types = require('../utils/types');
-const assert = require('assert');
-
-const ERROR_MESSAGE = 'Avoid using controllers except for specifying `queryParams`';
-
-module.exports = {
-  ERROR_MESSAGE,
-  meta: {
-    type: 'suggestion',
-    docs: {
-      description: 'disallow non-essential controllers',
-      category: 'Best Practices',
-      recommended: false,
-      url:
-        'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-controllers.md',
-    },
-    fixable: null,
-    schema: [],
-  },
-
-  create: context => {
-    return {
-      ClassDeclaration(node) {
-        if (
-          ember.isEmberController(context, node) &&
-          (node.body.body.length === 0 || !classDeclarationHasProperty(node, 'queryParams'))
-        ) {
-          context.report(node, ERROR_MESSAGE);
-        }
-      },
-
-      CallExpression(node) {
-        if (
-          ember.isEmberController(context, node) &&
-          (node.arguments.length === 0 || !callExpressionClassHasProperty(node, 'queryParams'))
-        ) {
-          context.report(node, ERROR_MESSAGE);
-        }
-      },
-    };
-  },
-};
-
-function classDeclarationHasProperty(classDeclaration, propertyName) {
-  assert(types.isClassDeclaration(classDeclaration));
-  return (
-    classDeclaration.body.body.length >= 1 &&
-    classDeclaration.body.body.some(
-      item =>
-        types.isClassProperty(item) &&
-        types.isIdentifier(item.key) &&
-        item.key.name === propertyName
-    )
-  );
-}
-
-function callExpressionClassHasProperty(callExpression, propertyName) {
-  assert(types.isCallExpression(callExpression));
-  return (
-    callExpression.arguments.length >= 1 &&
-    types.isObjectExpression(callExpression.arguments[callExpression.arguments.length - 1]) &&
-    callExpression.arguments[callExpression.arguments.length - 1].properties.some(
-      prop => types.isIdentifier(prop.key) && prop.key.name === propertyName
-    )
-  );
-}
diff --git a/lib/utils/ember.js b/lib/utils/ember.js
index 754ba9b8..2bdf3344 100644
--- a/lib/utils/ember.js
+++ b/lib/utils/ember.js
@@ -278,6 +278,7 @@ function isCustomProp(property) {
 
   return (
     types.isLiteral(value) ||
+    types.isTemplateLiteral(value) ||
     types.isIdentifier(value) ||
     types.isArrayExpression(value) ||
     types.isUnaryExpression(value) ||
diff --git a/lib/utils/types.js b/lib/utils/types.js
index 07c31e14..30886530 100644
--- a/lib/utils/types.js
+++ b/lib/utils/types.js
@@ -18,7 +18,6 @@ module.exports = {
   isFunctionExpression,
   isIdentifier,
   isImportDeclaration,
-  isImportDefaultSpecifier,
   isLiteral,
   isLogicalExpression,
   isMemberExpression,
@@ -226,16 +225,6 @@ function isImportDeclaration(node) {
   return node !== undefined && node.type === 'ImportDeclaration';
 }
 
-/**
- * Check whether or not a node is an ImportDefaultSpecifier.
- *
- * @param {Object} node The node to check.
- * @returns {boolean} Whether or not the node is an ImportDefaultSpecifier.
- */
-function isImportDefaultSpecifier(node) {
-  return node !== undefined && node.type === 'ImportDefaultSpecifier';
-}
-
 /**
  * Check whether or not a node is an Literal.
  *


Test patch:
diff --git a/tests/lib/rules/no-classic-components.js b/tests/lib/rules/no-classic-components.js
index 0eaa2533..9625cb98 100644
--- a/tests/lib/rules/no-classic-components.js
+++ b/tests/lib/rules/no-classic-components.js
@@ -19,11 +19,7 @@ const parserOptions = {
 const ruleTester = new RuleTester({ parserOptions });
 
 ruleTester.run('no-classic-components', rule, {
-  valid: [
-    "import Component from '@glimmer/component';",
-    "import { setComponentTemplate } from '@ember/component';",
-    "import { helper } from '@ember/component/helper';",
-  ],
+  valid: ["import Component from '@glimmer/component';"],
 
   invalid: [
     {
@@ -32,7 +28,7 @@ ruleTester.run('no-classic-components', rule, {
       errors: [
         {
           message: ERROR_MESSAGE,
-          type: 'ImportDefaultSpecifier',
+          type: 'ImportDeclaration',
         },
       ],
     },
diff --git a/tests/lib/rules/no-controllers.js b/tests/lib/rules/no-controllers.js
deleted file mode 100644
index 3eeeb49c..00000000
--- a/tests/lib/rules/no-controllers.js
+++ /dev/null
@@ -1,117 +0,0 @@
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const rule = require('../../../lib/rules/no-controllers');
-const RuleTester = require('eslint').RuleTester;
-
-const { ERROR_MESSAGE } = rule;
-
-//------------------------------------------------------------------------------
-// Tests
-//------------------------------------------------------------------------------
-
-const ruleTester = new RuleTester({
-  parser: require.resolve('babel-eslint'),
-  parserOptions: { ecmaVersion: 6, sourceType: 'module' },
-});
-ruleTester.run('no-controllers', rule, {
-  valid: [
-    `
-      import Controller from '@ember/controller';
-      export default Controller.extend({
-        someFunction() {},
-        query: null,
-        sortType: null,
-        sortOrder: null,
-        queryParams: ['query', 'sortType', 'sortOrder']
-      });
-    `,
-    `
-      import Controller from '@ember/controller';
-      export default class ArticlesController extends Controller {
-        get filteredArticles() {}
-        @tracked category = null;
-        queryParams = ['category'];
-      }
-    `,
-  ],
-
-  invalid: [
-    // ***************
-    // Legacy classes:
-    // ***************
-
-    {
-      code: `
-        import Controller from '@ember/controller';
-        export default Controller.extend();
-      `,
-      output: null,
-      errors: [{ type: 'CallExpression', message: ERROR_MESSAGE }],
-    },
-    {
-      code: `
-        import Controller from '@ember/controller';
-        export default Controller.extend(SomeMixin);
-      `,
-      output: null,
-      errors: [{ type: 'CallExpression', message: ERROR_MESSAGE }],
-    },
-    {
-      code: `
-        import Controller from '@ember/controller';
-        export default Controller.extend({});
-      `,
-      output: null,
-      errors: [{ type: 'CallExpression', message: ERROR_MESSAGE }],
-    },
-    {
-      code: `
-        import Controller from '@ember/controller';
-        export default Controller.extend({
-          randomProperty: true,
-          randomFunction() {}
-        });
-      `,
-      output: null,
-      errors: [{ type: 'CallExpression', message: ERROR_MESSAGE }],
-    },
-    {
-      // With mixin.
-      code: `
-        import Controller from '@ember/controller';
-        export default Controller.extend(SomeMixin, {
-          randomProperty: true,
-          randomFunction() {}
-        });
-      `,
-      output: null,
-      errors: [{ type: 'CallExpression', message: ERROR_MESSAGE }],
-    },
-
-    // ***************
-    // Native classes:
-    // ***************
-
-    {
-      code: `
-        import Controller from '@ember/controller';
-        export default class ArticlesController extends Controller {}
-      `,
-      output: null,
-      errors: [{ type: 'ClassDeclaration', message: ERROR_MESSAGE }],
-    },
-    {
-      code: `
-        import Controller from '@ember/controller';
-        export default class ArticlesController extends Controller {
-          randomProperty = true;
-          get filteredArticles() {}
-        }
-      `,
-      output: null,
-      errors: [{ type: 'ClassDeclaration', message: ERROR_MESSAGE }],
-    },
-  ],
-});
diff --git a/tests/lib/rules/order-in-components.js b/tests/lib/rules/order-in-components.js
index a7997bf7..4f185321 100644
--- a/tests/lib/rules/order-in-components.js
+++ b/tests/lib/rules/order-in-components.js
@@ -26,6 +26,16 @@ eslintTester.run('order-in-components', rule, {
         levelOfHappiness: computed("attitude", "health", () => {
         }),
 
+        actions: {}
+      });`,
+    `export default Component.extend({
+        role: ${`${'sloth'}`},
+
+        vehicle: alias("car"),
+
+        levelOfHappiness: computed("attitude", "health", () => {
+        }),
+
         actions: {}
       });`,
     `export default Component.extend({
@@ -345,6 +355,33 @@ eslintTester.run('order-in-components', rule, {
         },
       ],
     },
+    {
+      code: `export default Component.extend({
+        actions: {},
+
+        role: ${`${'sloth'}`},
+
+        vehicle: alias("car"),
+
+        levelOfHappiness: computed("attitude", "health", () => {
+        })
+      });`,
+      errors: [
+        {
+          message: 'The "role" property should be above the actions hash on line 2',
+          line: 4,
+        },
+        {
+          message: 'The "vehicle" single-line function should be above the actions hash on line 2',
+          line: 6,
+        },
+        {
+          message:
+            'The "levelOfHappiness" multi-line function should be above the actions hash on line 2',
+          line: 8,
+        },
+      ],
+    },
     {
       code: `export default Component.extend({
         vehicle: alias("car"),
diff --git a/tests/lib/utils/ember-test.js b/tests/lib/utils/ember-test.js
index 1c4bc014..2a416fe3 100644
--- a/tests/lib/utils/ember-test.js
+++ b/tests/lib/utils/ember-test.js
@@ -834,6 +834,9 @@ describe('isCustomProp', () => {
     node = getProperty("test = { test: 'someLiteral' }");
     expect(emberUtils.isCustomProp(node)).toBeTruthy();
 
+    node = getProperty("test = { test: `foo${'bar'}` }");
+    expect(emberUtils.isCustomProp(node)).toBeTruthy();
+
     node = getProperty('test = { test: someIdentifier }');
     expect(emberUtils.isCustomProp(node)).toBeTruthy();
 
diff --git a/tests/lib/utils/property-order-test.js b/tests/lib/utils/property-order-test.js
index c1d5a898..87099722 100644
--- a/tests/lib/utils/property-order-test.js
+++ b/tests/lib/utils/property-order-test.js
@@ -188,6 +188,16 @@ describe('determinePropertyType', () => {
       expect(propertyOrder.determinePropertyType(node, 'component')).toStrictEqual('property');
     });
 
+    it('should determine template literals as properties', () => {
+      const context = new FauxContext(
+        `export default Component.extend({
+          foo: ${`foo${"bar"}`}
+        });`
+      );
+      const node = context.ast.body[0].declaration.arguments[0].properties[0];
+      expect(propertyOrder.determinePropertyType(node, 'component')).toStrictEqual('property');
+    });
+
     it('should determine empty methods', () => {
       const context = new FauxContext(
         `export default Component.extend({