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

Issue:
`no-shadow-route-definition` false positive
Hey @raido, your change hasn't been released yet, but I noticed it is causing a bunch of routes in my application to get incorrectly flagged.

Here's a test case with the issue. Would you be able to fix this? Otherwise I'll need to revert this fix before the next release.

```js
this.route('show', { path: '/:marketingEmailId' });
this.route('edit', { path: '/:marketingEmailId/edit' });
```

_Originally posted by @bmish in https://github.com/ember-cli/eslint-plugin-ember/pull/1132#issuecomment-816254737_

Pull request description:
Fix false positive in same level routes but nested paths in `no-shadow-route-definition` rule
With recent effort (https://github.com/ember-cli/eslint-plugin-ember/pull/1132) to make no-shadow-route-definition rule graceful upon unexpected routes setup, we introduced regression with nested paths via configuration.

This PR fixes introduced regression in PR https://github.com/ember-cli/eslint-plugin-ember/pull/1132.

Closes #1134.

Patch:
diff --git a/lib/rules/no-shadow-route-definition.js b/lib/rules/no-shadow-route-definition.js
index ca853532..7f330c1d 100644
--- a/lib/rules/no-shadow-route-definition.js
+++ b/lib/rules/no-shadow-route-definition.js
@@ -22,7 +22,7 @@ function buildSourceLocationString(routeInfo) {
 }
 
 function buildRouteErrorInfo(routeInfo) {
-  return `"${routeInfo.name}" (${routeInfo.fullPath}, ${buildSourceLocationString(routeInfo)})`;
+  return `"${routeInfo.name}" ("${routeInfo.fullPath}", ${buildSourceLocationString(routeInfo)})`;
 }
 
 function isNestedRouteWithSamePath(routeInfo) {
@@ -100,13 +100,13 @@ module.exports = {
 
 function getRouteInfo(node) {
   const basePath = getRouteBasePath(node);
-  if (basePath.stringValue === null) {
+  if (basePath.normalizedPath === null) {
     return null;
   }
 
   const parentRoutes = getParentRoutesPaths(node);
   const notSupportedParentRoutePathArguments = parentRoutes.find((routePathInfo) => {
-    return routePathInfo.stringValue === null;
+    return routePathInfo.normalizedPath === null;
   });
 
   if (notSupportedParentRoutePathArguments) {
@@ -145,13 +145,15 @@ function getRouteBasePath(node) {
     routePathInfo = getNodeValue(pathOptionNode.value);
   }
 
-  if (
-    isString(routePathInfo.stringValue) &&
-    !routePathInfo.stringValue.startsWith(URL_CHUNK_SEPARATOR)
-  ) {
-    routePathInfo.stringValue = `/${routePathInfo.stringValue}`;
+  let path = routePathInfo.stringValue;
+  if (isString(path) && !path.startsWith(URL_CHUNK_SEPARATOR)) {
+    path = `/${routePathInfo.stringValue.trim()}`;
   }
-  return routePathInfo;
+  return {
+    type: routePathInfo.type,
+    normalizedPath: path,
+    rawPath: routePathInfo.stringValue,
+  };
 }
 
 function getParentRoutesPaths(node) {
@@ -192,8 +194,7 @@ function trimRootLevelNestedRoutes(routesPath) {
 function convertPathToGenericForMatching(routePathInfos) {
   return routePathInfos
     .map((routePathInfo) => {
-      const trimmedValue = routePathInfo.stringValue.trim();
-      const convertedPathForMatchingChunks = trimmedValue
+      const convertedPathForMatchingChunks = routePathInfo.normalizedPath
         .split(URL_CHUNK_SEPARATOR)
         .map((pathChunk) => {
           if (routePathInfo.type === SOURCE_PATH_VALUE_TYPE.STATIC) {
@@ -217,7 +218,7 @@ function convertPathToGenericForMatching(routePathInfos) {
 function convertPathForDisplay(routePathInfos) {
   return routePathInfos
     .map((routePathInfo) => {
-      return routePathInfo.stringValue.trim();
+      return routePathInfo.rawPath;
     })
     .join('');
 }


Test patch:
diff --git a/tests/lib/rules/no-shadow-route-definition.js b/tests/lib/rules/no-shadow-route-definition.js
index 9c07811f..c16eed5f 100644
--- a/tests/lib/rules/no-shadow-route-definition.js
+++ b/tests/lib/rules/no-shadow-route-definition.js
@@ -146,6 +146,82 @@ ruleTester.run('no-shadow-route-definition', rule, {
     },
   ],
   invalid: [
+    {
+      code: `
+        this.route('post', { path: '' });
+        this.route('edit', { path: '/' });
+      `,
+      output: null,
+      errors: [
+        {
+          message: buildErrorMessage({
+            leftRoute: {
+              name: 'edit',
+              fullPath: '/',
+              source: {
+                loc: {
+                  start: {
+                    line: 3,
+                    column: 8,
+                  },
+                },
+              },
+            },
+            rightRoute: {
+              name: 'post',
+              fullPath: '',
+              source: {
+                loc: {
+                  start: {
+                    line: 2,
+                    column: 8,
+                  },
+                },
+              },
+            },
+          }),
+          type: 'CallExpression',
+        },
+      ],
+    },
+    {
+      code: `
+        this.route('post', { path: ' ' });
+        this.route('edit', { path: ' / ' });
+      `,
+      output: null,
+      errors: [
+        {
+          message: buildErrorMessage({
+            leftRoute: {
+              name: 'edit',
+              fullPath: ' / ',
+              source: {
+                loc: {
+                  start: {
+                    line: 3,
+                    column: 8,
+                  },
+                },
+              },
+            },
+            rightRoute: {
+              name: 'post',
+              fullPath: ' ',
+              source: {
+                loc: {
+                  start: {
+                    line: 2,
+                    column: 8,
+                  },
+                },
+              },
+            },
+          }),
+          type: 'CallExpression',
+        },
+      ],
+    },
     {
       code: `
         this.route("blog");
@@ -157,7 +233,7 @@ ruleTester.run('no-shadow-route-definition', rule, {
           message: buildErrorMessage({
             leftRoute: {
               name: 'blog',
-              fullPath: '/blog',
+              fullPath: 'blog',
               source: {
                 loc: {
                   start: {
@@ -169,7 +245,7 @@ ruleTester.run('no-shadow-route-definition', rule, {
             },
             rightRoute: {
               name: 'blog',
-              fullPath: '/blog',
+              fullPath: 'blog',
               source: {
                 loc: {
                   start: {
@@ -197,7 +273,7 @@ ruleTester.run('no-shadow-route-definition', rule, {
           message: buildErrorMessage({
             leftRoute: {
               name: 'post',
-              fullPath: '/post',
+              fullPath: 'post',
               source: {
                 loc: {
                   start: {
@@ -239,7 +315,7 @@ ruleTester.run('no-shadow-route-definition', rule, {
           message: buildErrorMessage({
             leftRoute: {
               name: 'post',
-              fullPath: '/post',
+              fullPath: 'post',
               source: {
                 loc: {
                   start: {
@@ -281,7 +357,7 @@ ruleTester.run('no-shadow-route-definition', rule, {
           message: buildErrorMessage({
             leftRoute: {
               name: 'post',
-              fullPath: '/post',
+              fullPath: 'post',
               source: {
                 loc: {
                   start: {
@@ -446,7 +522,7 @@ ruleTester.run('no-shadow-route-definition', rule, {
             },
             rightRoute: {
               name: 'blog',
-              fullPath: '/*blog',
+              fullPath: '*blog',
               source: {
                 loc: {
                   start: {
@@ -501,6 +577,46 @@ ruleTester.run('no-shadow-route-definition', rule, {
         },
       ],
     },
+    {
+      code: `
+        this.route("main", { path: " / " }, function() {
+          this.route("blog", { path: " *blog " });
+        });
+        this.route("blog", { path: "/*blog" });
+      `,
+      output: null,
+      errors: [
+        {
+          message: buildErrorMessage({
+            leftRoute: {
+              name: 'blog',
+              fullPath: '/*blog',
+              source: {
+                loc: {
+                  start: {
+                    line: 5,
+                    column: 8,
+                  },
+                },
+              },
+            },
+            rightRoute: {
+              name: 'blog',
+              fullPath: ' /  *blog ',
+              source: {
+                loc: {
+                  start: {
+                    line: 3,
+                    column: 10,
+                  },
+                },
+              },
+            },
+          }),
+          type: 'CallExpression',
+        },
+      ],
+    },
     {
       code: `
         this.route("main", { path: "/" }, function() {
@@ -564,7 +680,7 @@ ruleTester.run('no-shadow-route-definition', rule, {
             },
             rightRoute: {
               name: 'blog',
-              fullPath: '/:blog',
+              fullPath: ':blog',
               source: {
                 loc: {
                   start: {
@@ -602,7 +718,7 @@ ruleTester.run('no-shadow-route-definition', rule, {
             },
             rightRoute: {
               name: 'blog',
-              fullPath: '/:blog',
+              fullPath: ':blog',
               source: {
                 loc: {
                   start: {
@@ -708,7 +824,7 @@ ruleTester.run('no-shadow-route-definition', rule, {
           message: buildErrorMessage({
             leftRoute: {
               name: 'second',
-              fullPath: '/main/',
+              fullPath: 'main/',
               source: {
                 loc: {
                   start: {
@@ -720,7 +836,7 @@ ruleTester.run('no-shadow-route-definition', rule, {
             },
             rightRoute: {
               name: 'first',
-              fullPath: '/main/',
+              fullPath: 'main/',
               source: {
                 loc: {
                   start: {
@@ -748,7 +864,7 @@ ruleTester.run('no-shadow-route-definition', rule, {
           message: buildErrorMessage({
             leftRoute: {
               name: 'second',
-              fullPath: '/someVariable/',
+              fullPath: 'someVariable/',
               source: {
                 loc: {
                   start: {
@@ -760,7 +876,7 @@ ruleTester.run('no-shadow-route-definition', rule, {
             },
             rightRoute: {
               name: 'first',
-              fullPath: '/someVariable/',
+              fullPath: 'someVariable/',
               source: {
                 loc: {
                   start: {
@@ -786,7 +902,7 @@ ruleTester.run('no-shadow-route-definition', rule, {
           message: buildErrorMessage({
             leftRoute: {
               name: 'someVariable',
-              fullPath: '/someVariable',
+              fullPath: 'someVariable',
               source: {
                 loc: {
                   start: {
@@ -798,7 +914,7 @@ ruleTester.run('no-shadow-route-definition', rule, {
             },
             rightRoute: {
               name: 'someVariable',
-              fullPath: '/someVariable',
+              fullPath: 'someVariable',
               source: {
                 loc: {
                   start: {
@@ -824,7 +940,7 @@ ruleTester.run('no-shadow-route-definition', rule, {
           message: buildErrorMessage({
             leftRoute: {
               name: 'second',
-              fullPath: '/someVariable',
+              fullPath: 'someVariable',
               source: {
                 loc: {
                   start: {
@@ -836,7 +952,7 @@ ruleTester.run('no-shadow-route-definition', rule, {
             },
             rightRoute: {
               name: 'first',
-              fullPath: '/someVariable',
+              fullPath: 'someVariable',
               source: {
                 loc: {
                   start: {
@@ -862,7 +978,7 @@ ruleTester.run('no-shadow-route-definition', rule, {
           message: buildErrorMessage({
             leftRoute: {
               name: 'null',
-              fullPath: '/someVariable',
+              fullPath: 'someVariable',
               source: {
                 loc: {
                   start: {
@@ -874,7 +990,7 @@ ruleTester.run('no-shadow-route-definition', rule, {
             },
             rightRoute: {
               name: 'null',
-              fullPath: '/someVariable',
+              fullPath: 'someVariable',
               source: {
                 loc: {
                   start: {
@@ -922,7 +1038,7 @@ describe('no-shadow-route-definition', () => {
     });
 
     expect(message).toStrictEqual(
-      'Route "second" (main/, 4L:10C) is shadowing route "first" (main/, 3L:10C)'
+      'Route "second" ("main/", 4L:10C) is shadowing route "first" ("main/", 3L:10C)'
     );
   });
 });