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

Issue:
New Octane Rule: `no-computed-properties-in-native-classes`
Pulled from https://github.com/ember-cli/eslint-plugin-ember/issues/533 which proposes a new set of rules for Octane related best practices

Disallow usage of Computed Properties in native JS classes. `@tracked` and getters should be used instead.

```js
// ### Good
import { tracked } from '@glimmer/tracking';
export default class MyComponent extend Component {
  ...
}

import { computed } from '@ember/object';
import { and, or, alias } from '@ember/object/computed';
export default class Component.extends({
  ...
});

// ### Bad
import { computed } from '@ember/object';
import { and, or, alias } from '@ember/object/computed';
export default class MyComponent extend Component {
  ...
}
```

If anyone would like to implement this please comment that you'll take it and work away.

Pull request:
Add new `no-computed-properties-in-native-classes` rule
Fixes #544 

Introduces a rule to prevent usage of Computed Properties in Native JS Classes.

Examples of **incorrect** code for this rule:

```js
import { and, or, alias } from '@ember/object/computed';
export default class MyComponent extends Component {
}
```

```js
import { computed } from '@ember/object';
export default class MyComponent extends Component {
}
```

Examples of **correct** code for this rule:

```js
import { computed } from '@ember/object';
import Component from '@ember/component';
export default Ember.Component.extend({});
```

```js
import { alias, or, and } from '@ember/object/computed';
import Component from '@ember/component';
export default Ember.Component.extend({});
```

```js
import { tracked } from '@glimmer/tracking';
import Component from '@ember/component';
export default class MyComponent extends Component {}
```

Patch:
diff --git a/README.md b/README.md
index 042a368a..c16eda0a 100644
--- a/README.md
+++ b/README.md
@@ -138,6 +138,13 @@ The `--fix` option on the command line automatically fixes problems reported by
 | :white_check_mark: | [routes-segments-snake-case](./docs/rules/routes-segments-snake-case.md) | Enforces usage of snake_cased dynamic segments in routes |
 
 
+### Ember Octane
+
+|    | Rule ID | Description |
+|:---|:--------|:------------|
+|  | [no-computed-properties-in-native-classes](./docs/rules/no-computed-properties-in-native-classes.md) | Disallows using computed properties in native classes |
+
+
 ### Ember Data
 
 |    | Rule ID | Description |
diff --git a/docs/rules/no-computed-properties-in-native-classes.md b/docs/rules/no-computed-properties-in-native-classes.md
new file mode 100644
index 00000000..6c76293d
--- /dev/null
+++ b/docs/rules/no-computed-properties-in-native-classes.md
@@ -0,0 +1,56 @@
+# no-computed-properties-in-native-classes
+
+ Since the beginning of Ember's existence, Computed Properties (CPs) have been used to accomplish reactivity in the framework. With Ember Octane, new features were introduced including Glimmer components, native JavaScript classes and Tracked Properties. With Ember Octane's new programming model, CPs are no longer needed. If using native JavaScript classes, Tracked Properties should be used instead as they give us the same benefit of CPs but with less boilerplate and more flexibility.
+
+## Rule Details
+
+This rule disallows using computed properties with native classes.
+
+
+## Examples
+
+Examples of **incorrect** code for this rule:
+
+```js
+import { and, or, alias } from '@ember/object/computed';
+
+export default class MyComponent extends Component {
+
+}
+```
+
+```js
+import { computed } from '@ember/object';
+
+export default class MyComponent extends Component {
+
+}
+```
+
+Examples of **correct** code for this rule:
+
+```js
+import { computed } from '@ember/object';
+import Component from '@ember/component';
+
+export default Ember.Component.extend({});
+```
+
+```js
+import { alias, or, and } from '@ember/object/computed';
+import Component from '@ember/component';
+
+export default Ember.Component.extend({});
+```
+
+```js
+import { tracked } from '@glimmer/tracking';
+import Component from '@ember/component';
+
+export default class MyComponent extends Component {}
+```
+
+## References
+
+* [Ember Guides: Tracked Properties](https://octane-guides-preview.emberjs.com/release/state-management/tracked-properties/)
+* [Tracked Properties Deep Dive](https://www.pzuraq.com/coming-soon-in-ember-octane-part-3-tracked-properties/)
diff --git a/lib/index.js b/lib/index.js
index 9be8cd40..1f90b5ea 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -16,6 +16,7 @@ module.exports = {
     'no-attrs-in-components': require('./rules/no-attrs-in-components'),
     'no-attrs-snapshot': require('./rules/no-attrs-snapshot'),
     'no-capital-letters-in-routes': require('./rules/no-capital-letters-in-routes'),
+    'no-computed-properties-in-native-classes': require('./rules/no-computed-properties-in-native-classes'),
     '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 28ba1950..f261a08d 100644
--- a/lib/recommended-rules.js
+++ b/lib/recommended-rules.js
@@ -19,6 +19,7 @@ module.exports = {
   "ember/no-attrs-in-components": "error",
   "ember/no-attrs-snapshot": "error",
   "ember/no-capital-letters-in-routes": "error",
+  "ember/no-computed-properties-in-native-classes": "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-computed-properties-in-native-classes.js b/lib/rules/no-computed-properties-in-native-classes.js
new file mode 100644
index 00000000..6a6dcf32
--- /dev/null
+++ b/lib/rules/no-computed-properties-in-native-classes.js
@@ -0,0 +1,59 @@
+'use strict';
+
+const utils = require('../utils/utils');
+
+const ERROR_MESSAGE = `Don't use computed properties with native classes. Use getters or @tracked properties instead.`;
+
+/**
+ * Locates all the ImportDeclaration with computed properties.
+ * @param {Node[]} nodeBody Array of nodes.
+ * @returns {Node[]} Array of nodes with .
+ */
+function findComputedNodes(nodeBody) {
+  const importDeclarations = utils.findNodes(nodeBody, 'ImportDeclaration');
+  return importDeclarations.filter(node => {
+    return (
+      node.source.value === '@ember/object/computed' ||
+      (node.source.value === '@ember/object' &&
+        node.specifiers.some(s => s.local.name === 'computed'))
+    );
+  });
+}
+
+//----------------------------------------------
+// General rule - Do not use computed properties in native classes
+//----------------------------------------------
+
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallows using computed properties in native classes',
+      category: 'Ember Octane',
+      recommended: false,
+      url:
+        'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/no-computed-properties-in-native-classes.md',
+    },
+    fixable: null, // or "code" or "whitespace"
+  },
+
+  create(context) {
+    let computedNodes = [];
+
+    const report = function(node) {
+      context.report(node, ERROR_MESSAGE);
+    };
+
+    return {
+      Program(node) {
+        computedNodes = findComputedNodes(node.body);
+      },
+      ClassDeclaration() {
+        computedNodes.forEach(importNode => {
+          report(importNode);
+        });
+      },
+    };
+  },
+
+  ERROR_MESSAGE,
+};


Test patch:
diff --git a/tests/lib/rules/no-computed-properties-in-native-classes.js b/tests/lib/rules/no-computed-properties-in-native-classes.js
new file mode 100644
index 00000000..bbedcbc5
--- /dev/null
+++ b/tests/lib/rules/no-computed-properties-in-native-classes.js
@@ -0,0 +1,65 @@
+'use strict';
+
+const rule = require('../../../lib/rules/no-computed-properties-in-native-classes');
+const RuleTester = require('eslint').RuleTester;
+
+const { ERROR_MESSAGE } = rule;
+const ruleTester = new RuleTester({
+  parserOptions: {
+    ecmaVersion: 6,
+    sourceType: 'module',
+  },
+});
+
+ruleTester.run('no-computed-properties-in-native-classes', rule, {
+  valid: [
+    `
+      import { computed } from '@ember/object';
+      import Component from '@ember/component';
+
+      export default Ember.Component.extend({});
+    `,
+    `
+      import { alias, or, and } from '@ember/object/computed';
+      import Component from '@ember/component';
+
+      export default Ember.Component.extend({});
+    `,
+    `
+      import { tracked } from '@glimmer/tracking';
+      import Component from '@ember/component';
+
+      export default class MyComponent extends Component {}
+    `,
+    `
+      import { randomThing } from '@ember/object';
+      import Component from '@ember/component';
+
+      export default class MyComponent extends Component {}
+    `,
+  ],
+  invalid: [
+    {
+      code: `
+      import { computed } from '@ember/object';
+      
+      export default class MyComponent extends Component {
+      
+      }
+      `,
+      output: null,
+      errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],
+    },
+    {
+      code: `
+      import { and, or, alias } from '@ember/object/computed';
+      
+      export default class MyComponent extends Component {
+      
+      }
+      `,
+      output: null,
+      errors: [{ message: ERROR_MESSAGE, type: 'ImportDeclaration' }],
+    },
+  ],
+});