diff --git a/packages/core-api/src/routing/hooks.test.tsx b/packages/core-api/src/routing/hooks.test.tsx index 6c38a6e18a..c2b8679e5f 100644 --- a/packages/core-api/src/routing/hooks.test.tsx +++ b/packages/core-api/src/routing/hooks.test.tsx @@ -25,7 +25,7 @@ import { } from '../extensions/traversal'; import { createPlugin } from '../plugin'; import { routeCollector, routeParentCollector } from './collectors'; -import { useRouteRef, RoutingProvider } from './hooks'; +import { useRouteRef, RoutingProvider, validateRoutes } from './hooks'; import { createRouteRef } from './RouteRef'; import { RouteRef } from './types'; @@ -177,4 +177,29 @@ describe('discovery', () => { rendered.getByText('Path at inside: /foo/blob/baz'), ).toBeInTheDocument(); }); + + it('should handle relative routing of parameterized routes with duplicate param names', () => { + const root = ( + + + + + + + + ); + + const { routes, routeParents } = traverseElementTree({ + root, + discoverers: [childDiscoverer, routeElementDiscoverer], + collectors: { + routes: routeCollector, + routeParents: routeParentCollector, + }, + }); + + expect(() => validateRoutes(routes, routeParents)).toThrow( + 'Parameter :id is duplicated in path /foo/:id/bar/:id', + ); + }); }); diff --git a/packages/core-api/src/routing/hooks.tsx b/packages/core-api/src/routing/hooks.tsx index 821fd6f9de..0c36928f10 100644 --- a/packages/core-api/src/routing/hooks.tsx +++ b/packages/core-api/src/routing/hooks.tsx @@ -75,3 +75,42 @@ export const RoutingProvider = ({ ); }; + +export function validateRoutes( + routes: Map, + routeParents: Map, +) { + const notLeafRoutes = new Set(routeParents.values()); + notLeafRoutes.delete(undefined); + + for (const route of routeParents.keys()) { + if (notLeafRoutes.has(route)) { + continue; + } + + let currentRouteRef: RouteRef | undefined = route; + + let fullPath = ''; + while (currentRouteRef) { + const path = routes.get(currentRouteRef); + if (!path) { + throw new Error(`No path for ${currentRouteRef}`); + } + fullPath = `${path}${fullPath}`; + currentRouteRef = routeParents.get(currentRouteRef); + } + + const params = fullPath.match(/:(\w+)/g); + if (params) { + for (let j = 0; j < params.length; j++) { + for (let i = j + 1; i < params.length; i++) { + if (params[i] === params[j]) { + throw new Error( + `Parameter ${params[i]} is duplicated in path ${fullPath}`, + ); + } + } + } + } + } +}