Repository: ember-cli/eslint-plugin-ember
Pull request #115 (2017-08-02 01:54:16)

Add `fix` for `use-ember-get-and-set`
Continuation of #55. This allows `this.get()` and `this.set()` in the `tests` directory, and it will completely skip over the `mirage` directory.

sudowork: @SaladFork @michalsnik @tylerturdenpants Here's the PR continuing from #55. Since I don't have write access to the original PR, I opened a new one with the commits rebased.
kevinkucharczyk: We're still a little worried about the `fix` potentially breaking a build. When you run the fix and you don't have `set`/`get` imported, your app won't build anymore.
[https://github.com/ember-cli/eslint-plugin-ember/pull/55#issuecomment-299720671](A suggestion) was to replace `this.set` with `Ember.set` etc., to only trigger a different lint error instead of breaking the app.
However, that might not work in all cases: with the new module imports, we would be able to, for example, `import Component from '@ember/component';` without ever needing to import `Ember` itself. Thus, replacing `this` with `Ember` would still break builds.
In the end, if we don't come up with a solution for this, maybe it would still be okay to get this merged but supply a warning that this fix might potentially break your app.
sudowork: Definitely a valid concern.

Here are a few enhancements I think I could make:
- Don't autofix (still report) if Ember is not imported. This would reduce what is fixable to avoid the import issue you described.
- Check if the method under question is declared in an upper scope. If not, then use `Ember.get` instead of `get` for example.

What are your thoughts.
sudowork: @kevinkucharczyk I updated the PR with some new behaviors to prevent code breakages:
- Don't attempt to fix the code if `Ember` is not imported
- When fixing the code, avoid breakages by using the local module if present. Otherwise, use the `Ember.get` form.
  - A new traversal over `VariableDeclarators` in the module scope is used to collect any local modules that were destructured. This only checks for the object destructuring form of `const { get } = Ember;`. It is also alias-aware: `const { get: emberGet } = Ember; this.get('foo');` => `const { get: emberGet } = Ember; emberGet(this, 'foo');`. It will not detect `const get = Ember.get`.
  - During fixing, if no local module is present, then default to using `Ember.get` (also alias aware).
kevinkucharczyk: Looks pretty good! One more thing though: I think it would be good to document the new behaviour in `use-ember-get-and-set.md`, so that users won't have to read through the source code in order to understand what the rule does and how the fix works.
sudowork: Documented the `--fix` behavior.
kevinkucharczyk: Please also add a note saying that the linter skips mirage and tests :)
sudowork: Done!

Patch:
diff --git a/README.md b/README.md
index bf87e68e..6179dadc 100644
--- a/README.md
+++ b/README.md
@@ -95,6 +95,7 @@ The `--fix` option on the command line automatically fixes problems reported by
 | :white_check_mark: | [jquery-ember-run](./docs/rules/jquery-ember-run.md) | Prevents usage of jQuery without Ember Run Loop |
 |  | [local-modules](./docs/rules/local-modules.md) | Enforces usage of local modules |
 | :white_check_mark: | [named-functions-in-promises](./docs/rules/named-functions-in-promises.md) | Enforces usage of named functions in promises |
+|  | [new-module-imports](./docs/rules/new-module-imports.md) |  Use "New Module Imports" from Ember RFC #176 |
 |  | [no-attrs-in-components](./docs/rules/no-attrs-in-components.md) | Disallow usage of this.attrs in components |
 | :white_check_mark: | [no-function-prototype-extensions](./docs/rules/no-function-prototype-extensions.md) | Prevents usage of Ember's `function` prototype extensions |
 |  | [no-global-jquery](./docs/rules/no-global-jquery.md) | Prevents usage of global jQuery object |
@@ -102,7 +103,7 @@ The `--fix` option on the command line automatically fixes problems reported by
 | :wrench: | [no-old-shims](./docs/rules/no-old-shims.md) | Prevents usage of old shims for modules |
 | :white_check_mark: | [no-side-effects](./docs/rules/no-side-effects.md) | Warns about unexpected side effects in computed properties |
 | :white_check_mark: | [use-brace-expansion](./docs/rules/use-brace-expansion.md) | Enforces usage of brace expansion |
-| :white_check_mark: | [use-ember-get-and-set](./docs/rules/use-ember-get-and-set.md) | Enforces usage of Ember.get and Ember.set |
+| :white_check_mark: :wrench: | [use-ember-get-and-set](./docs/rules/use-ember-get-and-set.md) | Enforces usage of Ember.get and Ember.set |
 
 
 ### Routing
diff --git a/docs/rules/new-module-imports.md b/docs/rules/new-module-imports.md
new file mode 100644
index 00000000..c6c93ddd
--- /dev/null
+++ b/docs/rules/new-module-imports.md
@@ -0,0 +1,23 @@
+## Use "New Module Imports" from Ember RFC #176
+
+### Rule name: `new-module-imports`
+
+[RFC #176](https://github.com/emberjs/rfcs/pull/176) introduced as new public
+API for Ember.js based on ES6 module imports.
+
+If you use `ember-cli-babel` with version `6.6.0` or above you can start using
+the "New Module Imports" instead of the `Ember` global directly. This will
+enable us to build better tree shaking feature into Ember CLI.
+
+```javascript
+// GOOD
+import Component from '@ember/component'
+import EmberObject, { computed } from '@ember/object'
+import Service, { inject } from '@ember/service'
+
+// BAD
+Ember.Component.extend({});
+Ember.Object.extend({});
+Ember.computed(function() {});
+Ember.inject.service('foo');
+```
diff --git a/docs/rules/use-ember-get-and-set.md b/docs/rules/use-ember-get-and-set.md
index f3fb6b62..4af18886 100644
--- a/docs/rules/use-ember-get-and-set.md
+++ b/docs/rules/use-ember-get-and-set.md
@@ -4,6 +4,13 @@
 
 This way you don't have to worry whether the object that you're trying to access is an `Ember.Object` or not. It also solves the problem of trying to wrap every object in `Ember.Object` in order to be able to use things like `getWithDefault`.
 
+Ember tests use `this.get()` and `this.set()` as test helpers, so uses of them in the `tests` directory will not be reported.
+In addition, all files in the `mirage` directory will be excluded from this rule.
+
+This rule can be used with `eslint --fix` to automatically fix some occurrences.
+To be auto-fixed, `Ember` must be imported.
+Ideally, you should also be using [local modules](./local-modules.md); otherwise, the fixed code will look like `Ember.get(this, fooProperty')` instead of `get(this, 'fooProperty')`.
+
 ```javascript
 // Bad
 this.get('fooProperty');
diff --git a/lib/recommended-rules.js b/lib/recommended-rules.js
index d03abe55..473319cf 100644
--- a/lib/recommended-rules.js
+++ b/lib/recommended-rules.js
@@ -10,6 +10,7 @@ module.exports = {
   "ember/jquery-ember-run": "error",
   "ember/local-modules": "off",
   "ember/named-functions-in-promises": "error",
+  "ember/new-module-imports": "off",
   "ember/no-attrs-in-components": "off",
   "ember/no-attrs-snapshot": "error",
   "ember/no-capital-letters-in-routes": "error",
diff --git a/lib/rules/new-module-imports.js b/lib/rules/new-module-imports.js
new file mode 100644
index 00000000..d55f5240
--- /dev/null
+++ b/lib/rules/new-module-imports.js
@@ -0,0 +1,66 @@
+'use strict';
+
+const GLOBALS = require('ember-rfc176-data/globals.json');
+
+//------------------------------------------------------------------------------
+// General rule - Use "New Module Imports" from Ember RFC #176
+//------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    docs: {
+      description: ' Use "New Module Imports" from Ember RFC #176',
+      category: 'General',
+      recommended: false
+    },
+  },
+
+  create(context) {
+    return {
+      'MemberExpression > Identifier[name=Ember]': function (node) {
+        // filter out "foo.Ember"
+        if (node.parent.object !== node) return;
+
+        // build an array of full expression names
+        // e.g. [Ember.computed, Ember.computed.or]
+        let fullName = 'Ember';
+        const fullNames = [];
+        let parentNode = node.parent;
+        while (parentNode.type === 'MemberExpression') {
+          fullName += `.${parentNode.property.name}`;
+          fullNames.push(fullName);
+          parentNode = parentNode.parent;
+        }
+
+        fullNames.reverse();
+
+        // find a matching expression starting at the end
+        for (let i = 0; i < fullNames.length; i++) {
+          fullName = fullNames[i];
+
+          const key = fullName.replace(/^Ember\./, '');
+          const match = GLOBALS[key];
+          if (match) {
+            // build replacement import for the warning message
+            let importSpecifier;
+            if (match[1]) {
+              importSpecifier = match[2] ? `{ ${match[1]} as ${match[2]} }` : `{ ${match[1]} }`;
+            } else {
+              importSpecifier = match[2] || key;
+            }
+
+            const replacement = `import ${importSpecifier} from '${match[0]}';`;
+
+            const message = `Use  ${replacement}  instead of using  ${fullName}`;
+
+            // report the issue to ESLint
+            context.report(node, message);
+
+            // exit the loop after the first match was found
+            break;
+          }
+        }
+      }
+    };
+  }
+};
diff --git a/lib/rules/use-ember-get-and-set.js b/lib/rules/use-ember-get-and-set.js
index e227b546..73db0f49 100644
--- a/lib/rules/use-ember-get-and-set.js
+++ b/lib/rules/use-ember-get-and-set.js
@@ -1,7 +1,13 @@
 'use strict';
 
+const path = require('path');
+const ember = require('../utils/ember');
 const utils = require('../utils/utils');
 
+const collectObjectPatternBindings = utils.collectObjectPatternBindings;
+const isIdentifier = utils.isIdentifier;
+const isThisExpression = utils.isThisExpression;
+
 //------------------------------------------------------------------------------
 // General - use get and set
 //------------------------------------------------------------------------------
@@ -13,17 +19,51 @@ module.exports = {
       category: 'General',
       recommended: true
     },
-    fixable: null, // or "code" or "whitespace"
+    fixable: 'code',
   },
 
   create(context) {
+    let emberImportAliasName;
+    // Populated during VariableDeclarator traversal
+    const localModulesPresent = {};
+    const sourceCode = context.getSourceCode();
+    const filename = context.getFilename();
     const message = 'Use get/set';
 
-    const report = function (node) {
-      context.report(node, message);
-    };
+    function isFileInDirectory(dirname) {
+      const pathParts = filename.split(path.sep);
+      return pathParts.indexOf(dirname) > -1;
+    }
+
+    function report(node) {
+      context.report({
+        node: node.callee.property,
+        message,
+        fix(fixer) {
+          if (!emberImportAliasName) {
+            return null;
+          }
+          // this.set('foo', 3);
+          // └┬─┘ └┬┘ └───┬──┘
+          //  │    │      └─────── args
+          //  │    └────────────── method
+          //  └─────────────────── subject
+
+          const subject = sourceCode.getText(node.callee.object);
+          const method = sourceCode.getText(node.callee.property);
+          const args = node.arguments.map(a => sourceCode.getText(a)).join(', ');
+
+          const localModule = localModulesPresent[method];
+          const replacementMethod = localModule || `${emberImportAliasName}.${method}`;
+
+          const fixedSource = `${replacementMethod}(${subject}, ${args})`;
 
-    const avoidedProperties = [
+          return fixer.replaceText(node, fixedSource);
+        }
+      });
+    }
+
+    const avoidedMethods = [
       'get',
       'set',
       'getProperties',
@@ -31,15 +71,57 @@ module.exports = {
       'getWithDefault',
     ];
 
+    const testMethodsToSkip = [
+      'get',
+      'set',
+    ];
+
+    const directoriesToSkipCompletely = [
+      'mirage',
+    ];
+
+    if (directoriesToSkipCompletely.some(dir => isFileInDirectory(dir))) {
+      return {};
+    }
+
     return {
-      MemberExpression(node) {
+      ImportDeclaration(node) {
+        emberImportAliasName = ember.getEmberImportAliasName(node);
+      },
+
+      VariableDeclarator(node) {
+        const isEmberImported = !!emberImportAliasName;
+        const isModuleScope = context.getScope().type === 'module';
+        if (isEmberImported && isModuleScope) {
+          // Populate localModulesPresent as a mapping of (avoided method -> local module alias)
+          avoidedMethods.forEach((methodName) => {
+            const destructuredAssignment = collectObjectPatternBindings(node, {
+              [emberImportAliasName]: [methodName]
+            }).pop();
+            if (destructuredAssignment) {
+              localModulesPresent[methodName] = destructuredAssignment;
+            }
+          });
+        }
+      },
+
+      CallExpression(node) {
+        const callee = node.callee;
+        const method = callee.property;
+
+        // Skip this.get() and this.set() in tests/
         if (
-          utils.isIdentifier(node.property) &&
-          avoidedProperties.indexOf(node.property.name) > -1
+          isFileInDirectory('tests') &&
+          isThisExpression(callee.object) &&
+          isIdentifier(method) && testMethodsToSkip.indexOf(method.name) > -1
         ) {
-          report(node.property);
+          return;
         }
-      },
+
+        if (isIdentifier(method) && avoidedMethods.indexOf(method.name) > -1) {
+          report(node);
+        }
+      }
     };
   }
 };


Test patch:
diff --git a/tests/lib/rules/new-module-imports.js b/tests/lib/rules/new-module-imports.js
new file mode 100644
index 00000000..3d10c47e
--- /dev/null
+++ b/tests/lib/rules/new-module-imports.js
@@ -0,0 +1,56 @@
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/new-module-imports');
+const RuleTester = require('eslint').RuleTester;
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const eslintTester = new RuleTester();
+eslintTester.run('new-module-imports', rule, {
+  valid: [
+    { code: 'Ember.onerror = function() {};' },
+    { code: 'Ember.MODEL_FACTORY_INJECTIONS = true;' },
+    { code: 'console.log(Ember.VERSION);' },
+    { code: 'if (Ember.testing) {}' },
+  ],
+  invalid: [
+    {
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: 'export default Ember.Service;',
+      errors: [{ message: 'Use  import Service from \'@ember/service\';  instead of using  Ember.Service' }],
+    },
+    {
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: 'export default Ember.Service.extend({});',
+      errors: [{ message: 'Use  import Service from \'@ember/service\';  instead of using  Ember.Service' }],
+    },
+    {
+      code: 'Ember.computed();',
+      errors: [{ message: 'Use  import { computed } from \'@ember/object\';  instead of using  Ember.computed' }],
+    },
+    {
+      code: 'Ember.computed.not();',
+      errors: [{ message: 'Use  import { not } from \'@ember/object/computed\';  instead of using  Ember.computed.not' }],
+    },
+    {
+      code: 'Ember.inject.service(\'foo\');',
+      errors: [{ message: 'Use  import { inject } from \'@ember/service\';  instead of using  Ember.inject.service' }],
+    },
+    {
+      code: 'var Router = Ember.Router.extend({});',
+      errors: [{ message: 'Use  import Router from \'@ember/routing/router\';  instead of using  Ember.Router' }],
+    },
+    {
+      code: 'Ember.$(\'.foo\')',
+      errors: [{ message: 'Use  import $ from \'jquery\';  instead of using  Ember.$' }],
+    },
+    {
+      code: 'new Ember.RSVP.Promise();',
+      errors: [{ message: 'Use  import RSVP from \'rsvp\';  instead of using  Ember.RSVP' }],
+    },
+  ],
+});
diff --git a/tests/lib/rules/use-ember-get-and-set.js b/tests/lib/rules/use-ember-get-and-set.js
index c9862c78..177bff62 100644
--- a/tests/lib/rules/use-ember-get-and-set.js
+++ b/tests/lib/rules/use-ember-get-and-set.js
@@ -22,91 +22,337 @@ eslintTester.run('use-ember-get-and-set', rule, {
     'setProperties(controller, {name: "Jon", email: "jon@snow.com"})',
     'getWithDefault(this, "test", "default")',
     'getWithDefault(controller, "test", "default")',
+    {
+      code: 'this.get("myProperty")',
+      filename: 'app/tests/unit/components/component-test.js',
+    },
+    {
+      code: 'this.set("myProperty", "value")',
+      filename: 'app/tests/unit/components/component-test.js',
+    },
+    {
+      code: 'this.get("/resources")',
+      filename: 'app/mirage/config.js',
+    },
   ],
   invalid: [
+    // Non-fixable errors
     {
       code: 'this.get("test")',
-      errors: [{
-        message: 'Use get/set',
-      }],
+      output: 'this.get("test")',
+      errors: [{ message: 'Use get/set' }],
     },
     {
       code: 'controller.get("test")',
-      errors: [{
-        message: 'Use get/set',
-      }],
+      output: 'controller.get("test")',
+      errors: [{ message: 'Use get/set' }],
     },
     {
       code: 'model.get("test")',
-      errors: [{
-        message: 'Use get/set',
-      }],
+      output: 'model.get("test")',
+      errors: [{ message: 'Use get/set' }],
+    },
+    {
+      code: 'this.foo.get("test")',
+      output: 'this.foo.get("test")',
+      errors: [{ message: 'Use get/set' }],
     },
     {
       code: 'this.getWithDefault("test", "default")',
-      errors: [{
-        message: 'Use get/set',
-      }],
+      output: 'this.getWithDefault("test", "default")',
+      errors: [{ message: 'Use get/set' }],
     },
     {
       code: 'controller.getWithDefault("test", "default")',
-      errors: [{
-        message: 'Use get/set',
-      }],
+      output: 'controller.getWithDefault("test", "default")',
+      errors: [{ message: 'Use get/set' }],
     },
     {
       code: 'this.set("test", "value")',
-      errors: [{
-        message: 'Use get/set',
-      }],
+      output: 'this.set("test", "value")',
+      errors: [{ message: 'Use get/set' }],
     },
     {
       code: 'controller.set("test", "value")',
-      errors: [{
-        message: 'Use get/set',
-      }],
+      output: 'controller.set("test", "value")',
+      errors: [{ message: 'Use get/set' }],
     },
     {
       code: 'model.set("test", "value")',
-      errors: [{
-        message: 'Use get/set',
-      }],
+      output: 'model.set("test", "value")',
+      errors: [{ message: 'Use get/set' }],
     },
     {
       code: 'this.getProperties("test", "test2")',
-      errors: [{
-        message: 'Use get/set',
-      }],
+      output: 'this.getProperties("test", "test2")',
+      errors: [{ message: 'Use get/set' }],
     },
     {
       code: 'controller.getProperties("test", "test2")',
-      errors: [{
-        message: 'Use get/set',
-      }],
+      output: 'controller.getProperties("test", "test2")',
+      errors: [{ message: 'Use get/set' }],
     },
     {
       code: 'model.getProperties("test", "test2")',
-      errors: [{
-        message: 'Use get/set',
-      }],
+      output: 'model.getProperties("test", "test2")',
+      errors: [{ message: 'Use get/set' }],
     },
     {
       code: 'this.setProperties({test: "value"})',
-      errors: [{
-        message: 'Use get/set',
-      }],
+      output: 'this.setProperties({test: "value"})',
+      errors: [{ message: 'Use get/set' }],
     },
     {
       code: 'controller.setProperties({test: "value"})',
-      errors: [{
-        message: 'Use get/set',
-      }],
+      output: 'controller.setProperties({test: "value"})',
+      errors: [{ message: 'Use get/set' }],
     },
     {
       code: 'model.setProperties({test: "value"})',
-      errors: [{
-        message: 'Use get/set',
-      }],
+      output: 'model.setProperties({test: "value"})',
+      errors: [{ message: 'Use get/set' }],
+    },
+    {
+      code: 'controller.getProperties("test", "test2")',
+      filename: 'app/tests/unit/controllers/controller-test.js',
+      output: 'controller.getProperties("test", "test2")',
+      errors: [{ message: 'Use get/set' }],
+    },
+    {
+      code: 'controller.setProperties({test: "value"})',
+      filename: 'app/tests/unit/controllers/controller-test.js',
+      output: 'controller.setProperties({test: "value"})',
+      errors: [{ message: 'Use get/set' }],
+    },
+    {
+      code: 'controller.getWithDefault("test", "default")',
+      filename: 'app/tests/unit/controllers/controller-test.js',
+      output: 'controller.getWithDefault("test", "default")',
+      errors: [{ message: 'Use get/set' }],
+    },
+    // Fixable errors using local modules
+    {
+      code: 'import Ember from "ember"; const { get } = Ember; this.get("test")',
+      output: 'import Ember from "ember"; const { get } = Ember; get(this, "test")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; const { get } = Ember; controller.get("test")',
+      output: 'import Ember from "ember"; const { get } = Ember; get(controller, "test")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; const { get } = Ember; model.get("test")',
+      output: 'import Ember from "ember"; const { get } = Ember; get(model, "test")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; const { get } = Ember; this.foo.get("test")',
+      output: 'import Ember from "ember"; const { get } = Ember; get(this.foo, "test")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; const { getWithDefault } = Ember; this.getWithDefault("test", "default")',
+      output: 'import Ember from "ember"; const { getWithDefault } = Ember; getWithDefault(this, "test", "default")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; const { getWithDefault } = Ember; controller.getWithDefault("test", "default")',
+      output: 'import Ember from "ember"; const { getWithDefault } = Ember; getWithDefault(controller, "test", "default")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; const { set } = Ember; this.set("test", "value")',
+      output: 'import Ember from "ember"; const { set } = Ember; set(this, "test", "value")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; const { set } = Ember; controller.set("test", "value")',
+      output: 'import Ember from "ember"; const { set } = Ember; set(controller, "test", "value")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; const { set } = Ember; model.set("test", "value")',
+      output: 'import Ember from "ember"; const { set } = Ember; set(model, "test", "value")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; const { getProperties } = Ember; this.getProperties("test", "test2")',
+      output: 'import Ember from "ember"; const { getProperties } = Ember; getProperties(this, "test", "test2")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; const { getProperties } = Ember; controller.getProperties("test", "test2")',
+      output: 'import Ember from "ember"; const { getProperties } = Ember; getProperties(controller, "test", "test2")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; const { getProperties } = Ember; model.getProperties("test", "test2")',
+      output: 'import Ember from "ember"; const { getProperties } = Ember; getProperties(model, "test", "test2")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; const { setProperties } = Ember; this.setProperties({test: "value"})',
+      output: 'import Ember from "ember"; const { setProperties } = Ember; setProperties(this, {test: "value"})',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; const { setProperties } = Ember; controller.setProperties({test: "value"})',
+      output: 'import Ember from "ember"; const { setProperties } = Ember; setProperties(controller, {test: "value"})',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; const { setProperties } = Ember; model.setProperties({test: "value"})',
+      output: 'import Ember from "ember"; const { setProperties } = Ember; setProperties(model, {test: "value"})',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; const { getProperties } = Ember; controller.getProperties("test", "test2")',
+      filename: 'app/tests/unit/controllers/controller-test.js',
+      output: 'import Ember from "ember"; const { getProperties } = Ember; getProperties(controller, "test", "test2")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; const { setProperties } = Ember; controller.setProperties({test: "value"})',
+      filename: 'app/tests/unit/controllers/controller-test.js',
+      output: 'import Ember from "ember"; const { setProperties } = Ember; setProperties(controller, {test: "value"})',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; const { getWithDefault } = Ember; controller.getWithDefault("test", "default")',
+      filename: 'app/tests/unit/controllers/controller-test.js',
+      output: 'import Ember from "ember"; const { getWithDefault } = Ember; getWithDefault(controller, "test", "default")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    // Fixable errors using method on Ember
+    {
+      code: 'import Ember from "ember"; this.get("test")',
+      output: 'import Ember from "ember"; Ember.get(this, "test")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; controller.get("test")',
+      output: 'import Ember from "ember"; Ember.get(controller, "test")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; model.get("test")',
+      output: 'import Ember from "ember"; Ember.get(model, "test")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; this.foo.get("test")',
+      output: 'import Ember from "ember"; Ember.get(this.foo, "test")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; this.getWithDefault("test", "default")',
+      output: 'import Ember from "ember"; Ember.getWithDefault(this, "test", "default")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; controller.getWithDefault("test", "default")',
+      output: 'import Ember from "ember"; Ember.getWithDefault(controller, "test", "default")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; this.set("test", "value")',
+      output: 'import Ember from "ember"; Ember.set(this, "test", "value")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; controller.set("test", "value")',
+      output: 'import Ember from "ember"; Ember.set(controller, "test", "value")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; model.set("test", "value")',
+      output: 'import Ember from "ember"; Ember.set(model, "test", "value")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; this.getProperties("test", "test2")',
+      output: 'import Ember from "ember"; Ember.getProperties(this, "test", "test2")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; controller.getProperties("test", "test2")',
+      output: 'import Ember from "ember"; Ember.getProperties(controller, "test", "test2")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; model.getProperties("test", "test2")',
+      output: 'import Ember from "ember"; Ember.getProperties(model, "test", "test2")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; this.setProperties({test: "value"})',
+      output: 'import Ember from "ember"; Ember.setProperties(this, {test: "value"})',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; controller.setProperties({test: "value"})',
+      output: 'import Ember from "ember"; Ember.setProperties(controller, {test: "value"})',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; model.setProperties({test: "value"})',
+      output: 'import Ember from "ember"; Ember.setProperties(model, {test: "value"})',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; controller.getProperties("test", "test2")',
+      filename: 'app/tests/unit/controllers/controller-test.js',
+      output: 'import Ember from "ember"; Ember.getProperties(controller, "test", "test2")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; controller.setProperties({test: "value"})',
+      filename: 'app/tests/unit/controllers/controller-test.js',
+      output: 'import Ember from "ember"; Ember.setProperties(controller, {test: "value"})',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+    },
+    {
+      code: 'import Ember from "ember"; controller.getWithDefault("test", "default")',
+      filename: 'app/tests/unit/controllers/controller-test.js',
+      output: 'import Ember from "ember"; Ember.getWithDefault(controller, "test", "default")',
+      errors: [{ message: 'Use get/set' }],
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
     },
   ],
 });