diff --git a/packages/react-router/src/link.tsx b/packages/react-router/src/link.tsx index 8768a362fcc..cae8bfc5e82 100644 --- a/packages/react-router/src/link.tsx +++ b/packages/react-router/src/link.tsx @@ -10,6 +10,7 @@ import type { Trim } from './fileRoute' import type { AnyRoute, RootSearchSchema } from './route' import type { RouteByPath, + RouteLeaves, RoutePaths, RoutePathsAutoComplete, } from './routeInfo' @@ -88,8 +89,8 @@ export type RemoveLeadingSlashes = T extends `/${infer R}` export type ResolvePaths = RouteByPath> extends never - ? RoutePaths - : RoutePaths>> + ? RouteLeaves + : RouteLeaves>> export type SearchPaths< TRouteTree extends AnyRoute, @@ -137,7 +138,7 @@ export type AbsolutePathAutoComplete< ? never : './') | (string extends TFrom ? '../' : TFrom extends `/` ? never : '../') - | RoutePaths + | RouteLeaves | (TFrom extends '/' ? never : SearchPaths) export type RelativeToPathAutoComplete< @@ -432,7 +433,15 @@ export type LinkOptions< } export type CheckPath = - ResolveRoute extends never ? TFail : TPass + ResolveRoute extends infer TRoute extends AnyRoute + ? [TRoute] extends [never] + ? TFail + : string extends TTo + ? TPass + : unknown extends TRoute['children'] + ? TPass + : TFail + : TFail export type ResolveRelativePath = TFrom extends string ? TTo extends string diff --git a/packages/react-router/src/routeInfo.ts b/packages/react-router/src/routeInfo.ts index f643d41fb99..3e9d796645e 100644 --- a/packages/react-router/src/routeInfo.ts +++ b/packages/react-router/src/routeInfo.ts @@ -1,14 +1,23 @@ import type { AnyRoute } from './route' -import type { Expand, UnionToIntersection, UnionToTuple } from './utils' +import type { UnionToIntersection, UnionToTuple } from './utils' export type ParseRoute = TRouteTree extends { types: { children: infer TChildren } } - ? TChildren extends ReadonlyArray + ? TChildren extends ReadonlyArray ? ParseRoute : TAcc : TAcc +export type RouteLeaves = + ParseRoute extends infer TRoute extends AnyRoute + ? TRoute extends any + ? TRoute['types']['children'] extends ReadonlyArray + ? never + : TRoute['fullPath'] + : never + : never + export type RoutesById = { [K in ParseRoute as K['id']]: K } diff --git a/packages/react-router/tests/link.test-d.tsx b/packages/react-router/tests/link.test-d.tsx index 12eee7530f0..a542599d12f 100644 --- a/packages/react-router/tests/link.test-d.tsx +++ b/packages/react-router/tests/link.test-d.tsx @@ -79,22 +79,14 @@ test('when navigating to the root', () => { | './' | '' | '/' - | '/invoices' | '/invoices/' - | '/invoices/$invoiceId' - | '/invoices/$invoiceId/details' | '/invoices/$invoiceId/details/$detailId' | '/invoices/$invoiceId/edit' - | '/posts' | '/posts/' | '/posts/$postId' - | 'invoices' | 'invoices/' - | 'invoices/$invoiceId' - | 'invoices/$invoiceId/details' | 'invoices/$invoiceId/details/$detailId' | 'invoices/$invoiceId/edit' - | 'posts' | 'posts/' | 'posts/$postId' | undefined @@ -110,13 +102,9 @@ test('when navigating from a route with no params and no search to the root', () | './' | '/' | '' - | '/invoices' | '/invoices/' - | '/invoices/$invoiceId' - | '/invoices/$invoiceId/details' | '/invoices/$invoiceId/edit' | '/invoices/$invoiceId/details/$detailId' - | '/posts' | '/posts/' | '/posts/$postId' | '$postId' @@ -136,20 +124,39 @@ test('when navigating from a route with no params and no search to the parent ro .parameter(0) .toHaveProperty('to') .toEqualTypeOf< - | '../posts' | '../posts/' | '../posts/$postId' - | '../invoices/$invoiceId' | '../invoices/$invoiceId/edit' - | '../invoices/$invoiceId/details' | '../invoices/$invoiceId/details/$detailId' - | '../invoices' | '../invoices/' | '../' | undefined >() }) +test('cannot navigate to a branch', () => { + expectTypeOf(Link) + .parameter(0) + .toHaveProperty('to') + .toEqualTypeOf< + | '' + | '../' + | './' + | '/' + | '/invoices/' + | '/invoices/$invoiceId/details/$detailId' + | '/invoices/$invoiceId/edit' + | '/posts/' + | '/posts/$postId' + | 'invoices/' + | 'invoices/$invoiceId/details/$detailId' + | 'invoices/$invoiceId/edit' + | 'posts/' + | 'posts/$postId' + | undefined + >() +}) + test('from autocompletes to all absolute routes', () => { const TestLink = Link expectTypeOf(TestLink) @@ -248,7 +255,7 @@ test('when navigating to a route with params', () => { }) test('when navigating from a route with no params to a route with params', () => { - const TestLink = Link + const TestLink = Link expectTypeOf(TestLink).parameter(0).toMatchTypeOf<{ params: unknown }>()