Repository: ember-cli/eslint-plugin-ember
Pull request #167 (2017-11-13 15:08:05)

Added Yarn Install
Thought it would look nice if there was info on how to install this with yarn.
On many libraries/addons this is usually included


Patch:
diff --git a/README.md b/README.md
index 1e5ee1c0..4988957f 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,10 @@
 
 ### 1. Install plugin
 
+```shell
+  yarn add --dev eslint-plugin-ember
+```
+Or
 ```shell
   npm install --save-dev eslint-plugin-ember
 ```
@@ -91,6 +95,13 @@ The `--fix` option on the command line automatically fixes problems reported by
 | :white_check_mark: | [no-on-calls-in-components](./docs/rules/no-on-calls-in-components.md) | Prevents usage of `on` calls in components |
 
 
+### Ember Object
+
+|    | Rule ID | Description |
+|:---|:--------|:------------|
+|  | [avoid-leaking-state-in-ember-objects](./docs/rules/avoid-leaking-state-in-ember-objects.md) | Avoids state leakage |
+
+
 ### General
 
 |    | Rule ID | Description |
@@ -142,6 +153,15 @@ The `--fix` option on the command line automatically fixes problems reported by
 | :white_check_mark: | [order-in-models](./docs/rules/order-in-models.md) | Enforces proper order of properties in models |
 | :white_check_mark: | [order-in-routes](./docs/rules/order-in-routes.md) | Enforces proper order of properties in routes |
 
+### Deprecated
+
+> :warning: We're going to remove deprecated rules in the next major release. Please migrate to successor/new rules.
+
+| Rule ID | Replaced by |
+|:--------|:------------|
+| [avoid-leaking-state-in-components](./docs/rules/avoid-leaking-state-in-components.md) | [avoid-leaking-state-in-ember-objects](./docs/rules/avoid-leaking-state-in-ember-objects.md) |
+| [local-modules](./docs/rules/local-modules.md) | [new-module-imports](./docs/rules/new-module-imports.md) |
+
 <!--RULES_TABLE_END-->
 
 For the simplified list of rules, [go here](./lib/recommended-rules.js).
@@ -172,6 +192,7 @@ If you have any suggestions, ideas or problems feel free to add new [issue](http
 - [Alex LaFroscia](https://github.com/alexlafroscia)
 - [Tobias Bieniek](https://github.com/Turbo87)
 - [Robert Wagner](https://github.com/rwwagner90)
+- [Jacek Bandura](https://github.com/jbandura)
 
 ##  🙌 Credits
 - [DockYard team](http://github.com/DockYard) - for great inspiration with their [styleguide](https://github.com/DockYard/styleguides/blob/master/engineering/ember.md)
diff --git a/docs/rules/use-ember-get-and-set.md b/docs/rules/use-ember-get-and-set.md
index 4af18886..3e95f2e9 100644
--- a/docs/rules/use-ember-get-and-set.md
+++ b/docs/rules/use-ember-get-and-set.md
@@ -2,6 +2,18 @@
 
 ### Rule name: `use-ember-get-and-set`
 
+#### Configuration
+
+```
+ember/use-ember-get-and-set: [2, {
+  ignoreThisExpressions: false,
+}]
+```
+
+Setting `ignoreThisExpressions` to `true` allows use of `this.get()` and `this.set()` where you will generally know if `this` is an `Ember.Object`.
+
+#### Description
+
 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.
diff --git a/lib/recommended-rules.js b/lib/recommended-rules.js
index 7ea5084c..1c203297 100644
--- a/lib/recommended-rules.js
+++ b/lib/recommended-rules.js
@@ -7,6 +7,7 @@
 module.exports = {
   "ember/alias-model-in-controller": "error",
   "ember/avoid-leaking-state-in-components": "error",
+  "ember/avoid-leaking-state-in-ember-objects": "off",
   "ember/closure-actions": "error",
   "ember/jquery-ember-run": "error",
   "ember/local-modules": "off",
diff --git a/lib/rules/avoid-leaking-state-in-components.js b/lib/rules/avoid-leaking-state-in-components.js
index 662b92cb..e49ea7f3 100644
--- a/lib/rules/avoid-leaking-state-in-components.js
+++ b/lib/rules/avoid-leaking-state-in-components.js
@@ -12,9 +12,11 @@ module.exports = {
     docs: {
       description: 'Avoids state leakage',
       category: 'Components',
-      recommended: true
+      recommended: true,
+      replacedBy: ['avoid-leaking-state-in-ember-objects'],
     },
     fixable: null, // or "code" or "whitespace"
+    deprecated: true,
     schema: [{
       type: 'array',
       items: { type: 'string' },
diff --git a/lib/rules/avoid-leaking-state-in-ember-objects.js b/lib/rules/avoid-leaking-state-in-ember-objects.js
index 13c0be38..e39f60c6 100644
--- a/lib/rules/avoid-leaking-state-in-ember-objects.js
+++ b/lib/rules/avoid-leaking-state-in-ember-objects.js
@@ -23,7 +23,9 @@ const isAllowed = function (property) {
     utils.isCallExpression(value) ||
     utils.isBinaryExpression(value) ||
     utils.isTemplateLiteral(value) ||
-    utils.isTaggedTemplateExpression(value);
+    utils.isTaggedTemplateExpression(value) ||
+    utils.isMemberExpression(value) ||
+    utils.isUnaryExpression(value);
 };
 
 
@@ -37,7 +39,7 @@ module.exports = {
     docs: {
       description: 'Avoids state leakage',
       category: 'Ember Object',
-      recommended: true
+      recommended: false
     },
     fixable: null, // or "code" or "whitespace"
     schema: [{
diff --git a/lib/rules/local-modules.js b/lib/rules/local-modules.js
index 0f2680dc..8cf8c5eb 100644
--- a/lib/rules/local-modules.js
+++ b/lib/rules/local-modules.js
@@ -11,7 +11,8 @@ module.exports = {
     docs: {
       description: 'Enforces usage of local modules',
       category: 'General',
-      recommended: false
+      recommended: false,
+      replacedBy: ['new-module-imports'],
     },
     deprecated: true,
     fixable: null, // or "code" or "whitespace"
diff --git a/lib/rules/no-capital-letters-in-routes.js b/lib/rules/no-capital-letters-in-routes.js
index 99f981e5..52d26de9 100644
--- a/lib/rules/no-capital-letters-in-routes.js
+++ b/lib/rules/no-capital-letters-in-routes.js
@@ -22,7 +22,13 @@ module.exports = {
 
     return {
       CallExpression(node) {
-        if (!ember.isRoute(node) || !node.arguments[0]) return;
+        if (
+          !ember.isRoute(node) ||
+          !node.arguments[0] ||
+          (node.arguments[0] && node.arguments[0].type === 'Identifier')
+        ) {
+          return;
+        }
 
         const routeName = node.arguments[0].value;
         const hasAnyUppercaseLetter = Boolean(routeName.match('[A-Z]'));
diff --git a/lib/rules/use-ember-get-and-set.js b/lib/rules/use-ember-get-and-set.js
index 10fb48f1..66f151d6 100644
--- a/lib/rules/use-ember-get-and-set.js
+++ b/lib/rules/use-ember-get-and-set.js
@@ -28,6 +28,7 @@ module.exports = {
     const localModulesPresent = {};
     const sourceCode = context.getSourceCode();
     const filename = context.getFilename();
+    const options = context.options[0] || {};
     const message = 'Use get/set';
 
     function isFileInDirectory(dirname) {
@@ -117,6 +118,10 @@ module.exports = {
         ) {
           return;
         }
+        // Skip calls made on this
+        if (options.ignoreThisExpressions && isThisExpression(callee.object)) {
+          return;
+        }
         // Skip calls made on Ember methods
         if (isIdentifier(callee.object) && callee.object.name === emberImportAliasName) {
           return;
diff --git a/lib/utils/ember.js b/lib/utils/ember.js
index 888a23ed..13d5c649 100644
--- a/lib/utils/ember.js
+++ b/lib/utils/ember.js
@@ -262,6 +262,7 @@ function isRouteCustomFunction(property) {
 
 function isRouteProperty(name) {
   return [
+    'init',
     'actions',
     'concatenatedProperties',
     'controller',
@@ -282,6 +283,7 @@ function isRouteDefaultProp(property) {
 
 function isControllerProperty(name) {
   return [
+    'init',
     'actions',
     'concatenatedProperties',
     'isDestroyed',
diff --git a/package.json b/package.json
index a0825a3d..b446a0d7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint-plugin-ember",
-  "version": "4.6.1",
+  "version": "4.6.2",
   "description": "Eslint plugin for Ember.js apps",
   "main": "lib/index.js",
   "directories": {
diff --git a/scripts/update-rules.js b/scripts/update-rules.js
index dfaa32cd..ae39f3c1 100644
--- a/scripts/update-rules.js
+++ b/scripts/update-rules.js
@@ -45,7 +45,7 @@ const categories = rules
     return arr;
   }, []);
 
-const rulesTableContent = categories.map(category => `
+let rulesTableContent = categories.map(category => `
 ### ${category}
 
 |    | Rule ID | Description |
@@ -65,6 +65,27 @@ ${
 }
 `).join('\n');
 
+rulesTableContent += `
+### Deprecated
+
+> :warning: We're going to remove deprecated rules in the next major release. Please migrate to successor/new rules.
+
+| Rule ID | Replaced by |
+|:--------|:------------|
+${
+  rules
+    .filter(entry => entry[1].meta.deprecated)
+    .map((entry) => {
+      const name = entry[0];
+      const meta = entry[1].meta;
+      const link = `[${name}](./docs/rules/${name}.md)`;
+      const replacedBy = (meta.docs.replacedBy || []).map(id => `[${id}](./docs/rules/${id}.md)`).join(', ') || '(no replacement)';
+      return `| ${link} | ${replacedBy} |`;
+    })
+    .join('\n')
+}
+`;
+
 const recommendedRules = rules.reduce((obj, entry) => {
   const name = `ember/${entry[0]}`;
   const recommended = entry[1].meta.docs.recommended;


Test patch:
diff --git a/tests/__snapshots__/recommended.js.snap b/tests/__snapshots__/recommended.js.snap
index 287e4fe1..051cf650 100644
--- a/tests/__snapshots__/recommended.js.snap
+++ b/tests/__snapshots__/recommended.js.snap
@@ -4,7 +4,6 @@ exports[`recommended rules 1`] = `
 Array [
   "alias-model-in-controller",
   "avoid-leaking-state-in-components",
-  "avoid-leaking-state-in-ember-objects",
   "closure-actions",
   "jquery-ember-run",
   "named-functions-in-promises",
diff --git a/tests/lib/rules/avoid-leaking-state-in-ember-objects.js b/tests/lib/rules/avoid-leaking-state-in-ember-objects.js
index 7c90d8d1..d6097fe1 100644
--- a/tests/lib/rules/avoid-leaking-state-in-ember-objects.js
+++ b/tests/lib/rules/avoid-leaking-state-in-ember-objects.js
@@ -94,7 +94,16 @@ eslintTester.run('avoid-leaking-state-in-ember-objects', rule, {
     },
     {
       code: 'export default Foo.extend({ test: `lorem ipsum` });'
-    }
+    },
+    {
+      code: 'export default Foo.extend({ fullName: abc.dgc });'
+    },
+    {
+      code: 'export default Foo.extend({ foo: abc.something() });'
+    },
+    {
+      code: 'export default Foo.extend({ foo: !true });'
+    },
   ],
   invalid: [
     {
diff --git a/tests/lib/rules/no-capital-letters-in-routes.js b/tests/lib/rules/no-capital-letters-in-routes.js
index a8162384..79c25dc1 100644
--- a/tests/lib/rules/no-capital-letters-in-routes.js
+++ b/tests/lib/rules/no-capital-letters-in-routes.js
@@ -17,6 +17,12 @@ eslintTester.run('no-capital-letters-in-routes', rule, {
   valid: [
     'this.route("sign-in");',
     'this.route("news", { path: "/:news_id" });',
+    {
+      code: `
+        const routeName="about";
+        this.route(routeName);`,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' }
+    }
   ],
 
   invalid: [{
diff --git a/tests/lib/rules/order-in-controllers.js b/tests/lib/rules/order-in-controllers.js
index 4e02c12e..6f1c3e83 100644
--- a/tests/lib/rules/order-in-controllers.js
+++ b/tests/lib/rules/order-in-controllers.js
@@ -91,6 +91,43 @@ eslintTester.run('order-in-controllers', rule, {
         ],
       }],
     },
+    {
+      code: `
+        export default Controller.extend({
+          foo: service(),
+          init() {
+            this._super(...arguments);
+          },
+          actions: {
+            onKeyPress: function (event) {}
+          }
+        });
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' }
+    },
+    {
+      code: `
+        export default Controller.extend({
+          foo: service(),
+          init() {
+            this._super(...arguments);
+          },
+          customFoo() {}
+        });
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' }
+    },
+    {
+      code: `
+        export default Controller.extend({
+          foo: service(),
+          init() {
+            this._super(...arguments);
+          }
+        });
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' }
+    }
   ],
   invalid: [
     {
@@ -223,5 +260,54 @@ eslintTester.run('order-in-controllers', rule, {
         line: 3,
       }],
     },
+    {
+      code: `
+        export default Controller.extend({
+          foo: service(),
+          actions: {
+            onKeyPress: function (event) {}
+          },
+          init() {
+            this._super(...arguments);
+          }
+        });
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      errors: [{
+        message: 'The inherited "init" property should be above the actions hash on line 4',
+        line: 7
+      }]
+    },
+    {
+      code: `
+        export default Controller.extend({
+          foo: service(),
+          customFoo() {},
+          init() {
+            this._super(...arguments);
+          }
+        });
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      errors: [{
+        message: 'The inherited "init" property should be above the "customFoo" empty method on line 4',
+        line: 5
+      }]
+    },
+    {
+      code: `
+        export default Controller.extend({
+          init() {
+            this._super(...arguments);
+          },
+          foo: service()
+        });
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      errors: [{
+        message: 'The "foo" service injection should be above the inherited "init" property on line 3',
+        line: 6
+      }]
+    }
   ],
 });
diff --git a/tests/lib/rules/order-in-routes.js b/tests/lib/rules/order-in-routes.js
index 57338bbd..0deca818 100644
--- a/tests/lib/rules/order-in-routes.js
+++ b/tests/lib/rules/order-in-routes.js
@@ -73,9 +73,9 @@ eslintTester.run('order-in-routes', rule, {
     },
     {
       code: `export default Route.extend({
+        init() {},
         model() {},
         render() {},
-        init() {}
       });`,
       parserOptions: { ecmaVersion: 6, sourceType: 'module' }
     },
@@ -150,7 +150,31 @@ eslintTester.run('order-in-routes', rule, {
           'model'
         ]
       }]
-    }
+    },
+    {
+      code: `
+        export default Route.extend({
+          foo: service(),
+          init() {
+            this._super(...arguments);
+          },
+          actions: {}
+        });
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' }
+    },
+    {
+      code: `
+        export default Route.extend({
+          foo: service(),
+          init() {
+            this._super(...arguments);
+          },
+          customFoo() {}
+        });
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' }
+    },
   ],
   invalid: [
     {
@@ -361,6 +385,53 @@ eslintTester.run('order-in-routes', rule, {
         message: 'The "test" property should be above the "model" hook on line 2',
         line: 3
       }]
-    }
+    },
+    {
+      code: `
+        export default Route.extend({
+          foo: service(),
+          actions: {},
+          init() {
+            this._super(...arguments);
+          }
+        });
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      errors: [{
+        message: 'The inherited "init" property should be above the actions hash on line 4',
+        line: 5
+      }]
+    },
+    {
+      code: `
+        export default Route.extend({
+          foo: service(),
+          customFoo() {},
+          init() {
+            this._super(...arguments);
+          },
+        });
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      errors: [{
+        message: 'The inherited "init" property should be above the "customFoo" empty method on line 4',
+        line: 5
+      }]
+    },
+    {
+      code: `
+        export default Route.extend({
+          init() {
+            this._super(...arguments);
+          },
+          foo: service()
+        });
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      errors: [{
+        message: 'The "foo" service injection should be above the inherited "init" property on line 3',
+        line: 6
+      }]
+    },
   ]
 });
diff --git a/tests/lib/rules/use-ember-get-and-set.js b/tests/lib/rules/use-ember-get-and-set.js
index 6120042d..af1ad6e7 100644
--- a/tests/lib/rules/use-ember-get-and-set.js
+++ b/tests/lib/rules/use-ember-get-and-set.js
@@ -1,3 +1,5 @@
+const path = require('path');
+
 // ------------------------------------------------------------------------------
 // Requirements
 // ------------------------------------------------------------------------------
@@ -24,15 +26,15 @@ eslintTester.run('use-ember-get-and-set', rule, {
     'getWithDefault(controller, "test", "default")',
     {
       code: 'this.get("myProperty")',
-      filename: 'app/tests/unit/components/component-test.js',
+      filename: path.join('app', 'tests', 'unit', 'components', 'component-test.js'),
     },
     {
       code: 'this.set("myProperty", "value")',
-      filename: 'app/tests/unit/components/component-test.js',
+      filename: path.join('app', 'tests', 'unit', 'components', 'component-test.js'),
     },
     {
       code: 'this.get("/resources")',
-      filename: 'app/mirage/config.js',
+      filename: path.join('app', 'mirage', 'config.js'),
     },
     {
       code: 'import Ember from "ember"; Ember.get(this, "test")',