]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
feat(types): add RouteNamepMap type
authorEduardo San Martin Morote <posva13@gmail.com>
Tue, 3 May 2022 14:37:40 +0000 (16:37 +0200)
committerEduardo San Martin Morote <posva@users.noreply.github.com>
Thu, 30 Jun 2022 07:59:00 +0000 (09:59 +0200)
src/index.ts
src/types/index.ts
src/types/named.ts
src/types/paths.ts
test-dts/namedRoutes.test-d.ts

index 46ead1add30517d4c295d20e2c11d9dd77519901..d1eb2d1333b98d719f627ff51c0706aefb5a6155 100644 (file)
@@ -68,6 +68,7 @@ export type {
   _RemoveRegexpFromParam,
   _RemoveUntilClosingPar,
 } from './types/paths'
+export type { RouteNamedMap } from './types/named'
 
 export { createRouter } from './router'
 export type { Router, RouterOptions, RouterScrollBehavior } from './router'
index 7ececb4c24bd9ae2b171cad7729d0b9cac8cd647..5124a45f64fe2148601012750d7e9a34eab0ce8c 100644 (file)
@@ -215,10 +215,6 @@ export interface _RouteRecordBase extends PathParserOptions {
    */
   redirect?: RouteRecordRedirectOption
 
-  /**
-   * Array of nested routes.
-   */
-  children?: RouteRecordRaw[]
   /**
    * Aliases for the record. Allows defining extra paths that will behave like a
    * copy of the record. Allows having paths shorthands like `/users/:id` and
@@ -298,7 +294,7 @@ export interface RouteRecordSingleViewWithChildren extends _RouteRecordBase {
   /**
    * Array of nested routes.
    */
-  children: RouteRecordRaw[]
+  children: Readonly<RouteRecordRaw[]>
 
   /**
    * Allow passing down params as props to the component rendered by `router-view`.
index d238da59a81a64b1df1fce993d51d9d5eb2d9120..ce3cf33c1bf2a2a0385e423d9e7c1b432c61681c 100644 (file)
@@ -1,4 +1,6 @@
-import { Router } from '../router'
+import type { RouteRecordRaw } from '.'
+import type { Router } from '../router'
+import { JoinPath, ParamsFromPath } from './paths'
 
 /**
  * This will flat the routes into an object with `key` === `router.name`
@@ -43,3 +45,36 @@ export type ExtractRoutes<T extends Router> = ExtractNamedRoutes<
  * ```
  */
 export interface NamedLocationMap {}
+
+export type RouteNamedMap<
+  Routes extends Readonly<RouteRecordRaw[]>,
+  Prefix extends string = ''
+> = Routes extends readonly [infer R, ...infer Rest]
+  ? Rest extends Readonly<RouteRecordRaw[]>
+    ? (R extends {
+        name?: infer Name
+        path: infer Path
+        children?: infer Children
+      }
+        ? Path extends string
+          ? (Name extends string
+              ? {
+                  [N in Name]: ParamsFromPath<JoinPath<Prefix, Path>>
+                }
+              : {
+                  // NO_NAME: 1
+                }) &
+              (Children extends Readonly<RouteRecordRaw[]>
+                ? RouteNamedMap<Children, JoinPath<Prefix, Path>>
+                : {
+                    // NO_CHILDREN: 1
+                  })
+          : never // Path must be a string
+        : {
+            // EMPTY: 1
+          }) &
+        RouteNamedMap<Rest>
+    : never // R must be a valid route record
+  : {
+      // END: 1
+    }
index 72d965fb62601d7a75980b340d3f221c70f45460..4d32cfd414f4a6f31b20960e21ccd9fb61b3b778 100644 (file)
@@ -1,3 +1,15 @@
+/**
+ * Extract an object of params given a path like `/users/:id`.
+ *
+ * @example
+ * ```ts
+ * type P = ParamsFromPath<'/:id/b/:c*'> // { id: string; c?: string[] }
+ * ```
+ */
+export type ParamsFromPath<P extends string = string> = string extends P
+  ? PathParams // Generic version
+  : _ExtractParamsPath<_RemoveRegexpFromParam<P>>
+
 /**
  * Generic possible params from a path (after parsing).
  */
@@ -56,13 +68,6 @@ export type _ExtractParamsPath<P extends string> =
         _ExtractParamsPath<Rest>
     : {}
 
-/**
- * Extract an object of params given a path like `/users/:id`.
- */
-export type ParamsFromPath<P extends string = string> = string extends P
-  ? PathParams // Generic version
-  : _ExtractParamsPath<_RemoveRegexpFromParam<P>>
-
 /**
  * Gets the possible type of a param based on its modifier M.
  *
@@ -275,3 +280,12 @@ export type _ExtractPathParamKeys<P extends string> =
 export type ParamKeysFromPath<P extends string = string> = string extends P
   ? readonly PathParserParamKey[] // Generic version
   : _ExtractPathParamKeys<_RemoveRegexpFromParam<P>>
+
+export type JoinPath<
+  Prefix extends string,
+  Path extends string
+> = Path extends `/${string}`
+  ? Path
+  : '' extends Prefix
+  ? never
+  : `${Prefix}${Prefix extends `${string}/` ? '' : '/'}${Path}`
index 5fea37fac5c0d2d1fd7eb6022d215bb6e79a8cef..ce187e7ea2acc03819b878a86a93e33f1d2c19b1 100644 (file)
@@ -3,10 +3,17 @@ import {
   Router,
   ExtractRoutes,
   createRouter,
+  createWebHistory,
+  RouteRecordRaw,
+  expectType,
+  RouteNamedMap,
 } from './index'
 import { DefineComponent } from 'vue'
+import { JoinPath } from 'src/types/paths'
 
 declare const Comp: DefineComponent
+declare const component: DefineComponent
+declare const components: { default: DefineComponent }
 
 const routes = [
   {
@@ -46,6 +53,74 @@ const routes = [
   },
 ] as const
 
+export function defineRoutes<
+  Path extends string,
+  Routes extends Readonly<RouteRecordRaw<Path>[]>
+>(routes: Routes): Routes {
+  return routes
+}
+
+const r2 = createRouter({
+  history: createWebHistory(),
+  routes: [
+    { path: '/users/:id', name: 'UserDetails', component },
+    { path: '/no-name', /* no name */ component },
+    {
+      path: '/nested',
+      name: 'nested',
+      children: [
+        {
+          path: ':a',
+          name: 'nested-a',
+          children: [
+            {
+              path: 'b',
+              children: [{ path: ':c', name: 'nested-c', component }],
+            },
+          ],
+        },
+      ],
+    },
+  ] as const,
+})
+
+function joinPath<A extends string, B extends string>(
+  prefix: A,
+  path: B
+): JoinPath<A, B> {
+  return '' as any
+}
+
+function createMap<R extends Readonly<RouteRecordRaw[]>>(
+  routes: R
+): RouteNamedMap<R> {
+  return {} as any
+}
+
+expectType<'/nested/:a'>(joinPath('/nested', ':a'))
+expectType<'/nested/:a'>(joinPath('/nested/', ':a'))
+expectType<'/:a'>(joinPath('/nested', '/:a'))
+
+expectType<{
+  UserDetails: { id: string }
+  nested: {}
+  'nested-a': { a: string }
+  'nested-c': { a: string; c: string }
+}>(createMap(r2.options.routes))
+
+expectType<{
+  UserDetails: { nope: string }
+  // @ts-expect-error
+}>(createMap(r2.options.routes))
+expectType<{
+  'nested-c': { a: string; d: string }
+  // @ts-expect-error
+}>(createMap(r2.options.routes))
+expectType<{
+  nope: {}
+  // @ts-expect-error
+}>(createMap(r2.options.routes))
+
 declare const typed: ExtractNamedRoutes<typeof routes>
 
 typed['my-other-path']