Repository: ember-cli/eslint-plugin-ember
Pull request #372 (2019-01-10 06:33:47)

Add new 'no-unnecessary-index-route'  rule
The `index` route (for the `/` path) is automatically provided at every level of nesting and does not need to be defined in the router.

### Rule Details

Examples of **incorrect** code for this rule:

```js
this.route('index');
```

```js
this.route('index', { path: '/' });
```

Examples of **correct** code for this rule:

```js
this.route('blog-posts');
```



bmish: Hey @rwjblue can you take a look? I'm hoping to get this in to unblock other work I'd like to do. (also just rebased on the latest master).

Patch:
diff --git a/README.md b/README.md
index 14c90916..076b2514 100644
--- a/README.md
+++ b/README.md
@@ -96,6 +96,7 @@ The `--fix` option on the command line automatically fixes problems reported by
 | :white_check_mark::wrench: | [no-old-shims](./docs/rules/no-old-shims.md) | Prevents usage of old shims for modules |
 | :white_check_mark: | [no-on-calls-in-components](./docs/rules/no-on-calls-in-components.md) | Prevents usage of `on` to call lifecycle hooks in components |
 | :white_check_mark: | [no-restricted-resolver-tests](./docs/rules/no-restricted-resolver-tests.md) | Prevents the use of patterns that use the restricted resolver in tests. |
+|  | [no-unnecessary-index-route](./docs/rules/no-unnecessary-index-route.md) | Disallow unnecessary `index` route definition |
 | :wrench: | [no-unnecessary-route-path-option](./docs/rules/no-unnecessary-route-path-option.md) | Disallow unnecessary route `path` option |
 | :wrench: | [no-unnecessary-service-injection-argument](./docs/rules/no-unnecessary-service-injection-argument.md) | Disallow unnecessary argument when injecting service |
 | :wrench: | [use-ember-get-and-set](./docs/rules/use-ember-get-and-set.md) | Enforces usage of Ember.get and Ember.set |
diff --git a/docs/rules/no-unnecessary-index-route.md b/docs/rules/no-unnecessary-index-route.md
new file mode 100644
index 00000000..4d77d422
--- /dev/null
+++ b/docs/rules/no-unnecessary-index-route.md
@@ -0,0 +1,33 @@
+## Disallow unnecessary `index` route definition
+
+### Rule name: `no-unnecessary-index-route`
+
+The `index` route (for the `/` path) is automatically provided at every level of nesting and does not need to be defined in the router.
+
+### Rule Details
+
+Examples of **incorrect** code for this rule:
+
+```js
+this.route('index');
+```
+
+```js
+this.route('index', { path: '/' });
+```
+
+Examples of **correct** code for this rule:
+
+```js
+this.route('blog-posts');
+```
+
+### References
+
+* [Ember Routing Guide](https://guides.emberjs.com/release/routing/)
+
+### Related Rules
+
+* [no-capital-letters-in-routes](no-capital-letters-in-routes.md)
+* [no-unnecessary-route-path-option](no-unnecessary-route-path-option.md)
+* [routes-segments-snake-case](routes-segments-snake-case.md)
diff --git a/docs/rules/no-unnecessary-route-path-option.md b/docs/rules/no-unnecessary-route-path-option.md
index a543a0a2..068d0a57 100644
--- a/docs/rules/no-unnecessary-route-path-option.md
+++ b/docs/rules/no-unnecessary-route-path-option.md
@@ -29,4 +29,5 @@ this.route('blog-posts', { path: '/blog' });
 ### Related Rules
 
 * [no-capital-letters-in-routes](no-capital-letters-in-routes.md)
+* [no-unnecessary-index-route](no-unnecessary-index-route.md)
 * [routes-segments-snake-case](routes-segments-snake-case.md)
diff --git a/lib/index.js b/lib/index.js
index 54252130..9337dce1 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -32,6 +32,7 @@ module.exports = {
     'no-side-effects': require('./rules/no-side-effects'),
     'no-test-and-then': require('./rules/no-test-and-then'),
     'no-test-import-export': require('./rules/no-test-import-export'),
+    'no-unnecessary-index-route': require('./rules/no-unnecessary-index-route'),
     'no-unnecessary-route-path-option': require('./rules/no-unnecessary-route-path-option'),
     'no-unnecessary-service-injection-argument': require('./rules/no-unnecessary-service-injection-argument'),
     'order-in-components': require('./rules/order-in-components'),
diff --git a/lib/recommended-rules.js b/lib/recommended-rules.js
index f2a7276d..b80d614a 100644
--- a/lib/recommended-rules.js
+++ b/lib/recommended-rules.js
@@ -34,6 +34,7 @@ module.exports = {
   "ember/no-side-effects": "error",
   "ember/no-test-and-then": "off",
   "ember/no-test-import-export": "off",
+  "ember/no-unnecessary-index-route": "off",
   "ember/no-unnecessary-route-path-option": "off",
   "ember/no-unnecessary-service-injection-argument": "off",
   "ember/order-in-components": "off",
diff --git a/lib/rules/no-unnecessary-index-route.js b/lib/rules/no-unnecessary-index-route.js
new file mode 100644
index 00000000..327754bd
--- /dev/null
+++ b/lib/rules/no-unnecessary-index-route.js
@@ -0,0 +1,40 @@
+'use strict';
+
+const emberUtils = require('../utils/ember');
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+const ERROR_MESSAGE = 'The `index` route is automatically provided and does not need to be defined.';
+
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Disallow unnecessary `index` route definition',
+      category: 'Best Practices',
+      recommended: false
+    },
+    fixable: null,
+    ERROR_MESSAGE
+  },
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        if (!emberUtils.isRoute(node)) {
+          return;
+        }
+
+        if (node.arguments[0].value !== 'index') {
+          return;
+        }
+
+        context.report({
+          node,
+          message: ERROR_MESSAGE
+        });
+      }
+    };
+  }
+};


Test patch:
diff --git a/tests/lib/rules/no-unnecessary-index-route.js b/tests/lib/rules/no-unnecessary-index-route.js
new file mode 100644
index 00000000..f49479f3
--- /dev/null
+++ b/tests/lib/rules/no-unnecessary-index-route.js
@@ -0,0 +1,61 @@
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/no-unnecessary-index-route');
+const RuleTester = require('eslint').RuleTester;
+
+const { ERROR_MESSAGE } = rule;
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester();
+ruleTester.run('no-unnecessary-index-route', rule, {
+  valid: [
+    'this.route("blog");',
+    'this.route("blog", function() {});',
+    'this.route("blog", { path: "" });',
+    'this.route("blog", { path: "/" });',
+    'this.route("blog", { path: "/:blog_id" });',
+    'this.route("blog", { path: "/*path" });',
+    'this.route("blog", { path: "blog-posts" });',
+    'this.route("blog", { path: "blog-posts" }, function() {});',
+
+    // Not Ember's route function:
+    'test();',
+    "test('index');",
+    "test('index', { path: '/' });",
+    "this.test('index');",
+    "this.test('index', { path: '/' });",
+    "MyClass.route('index');",
+    "MyClass.route('index', { path: '/' });",
+    "route.unrelatedFunction('index');",
+    "route.unrelatedFunction('index', { path: '/' });",
+    "this.route.unrelatedFunction('index');",
+    "this.route.unrelatedFunction('index', { path: '/' });"
+  ],
+  invalid: [
+    {
+      code: 'this.route("index");',
+      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }]
+    },
+    {
+      code: 'this.route("index", { path: "" });',
+      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }]
+    },
+    {
+      code: 'this.route("index", { path: "/" });',
+      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }]
+    },
+    {
+      code: 'this.route("index", { path: "/index" });',
+      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }]
+    },
+    {
+      code: 'this.route("index", { path: "/" }, function() {});',
+      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }]
+    },
+  ]
+});
diff --git a/tests/lib/rules/no-unnecessary-route-path-option.js b/tests/lib/rules/no-unnecessary-route-path-option.js
index d9788445..b6c498bb 100644
--- a/tests/lib/rules/no-unnecessary-route-path-option.js
+++ b/tests/lib/rules/no-unnecessary-route-path-option.js
@@ -16,6 +16,7 @@ ruleTester.run('no-unnecessary-route-path-option', rule, {
   valid: [
     'this.route("blog");',
     'this.route("blog", function() {});',
+    'this.route("blog", { path: "" });',
     'this.route("blog", { path: "/" });',
     'this.route("blog", { path: "blog-posts" });',
     'this.route("blog", { path: "blog-posts" }, function() {});',