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

index abd238b7a25067ddd398454eb73dfaaf94a49287..b29514c26f95f46de9f788d8d97797f1f526fe48 100644 (file)
@@ -70,7 +70,7 @@ import {
   routerViewLocationKey,
 } from './injectionSymbols'
 import { addDevtools } from './devtools'
-import { RouteNamedMap, RouteNamedMapGeneric } from './types/named'
+import { RouteNamedMap } from './types/named'
 
 /**
  * Internal type to define an ErrorHandler
@@ -255,13 +255,11 @@ export interface Router<Options extends RouterOptions = RouterOptions> {
    *
    * @param to - Route location to navigate to
    */
-  push<
-    RouteMap extends RouteNamedMapGeneric = RouteNamedMap<Options['routes']>,
-    Name extends keyof RouteMap = keyof RouteNamedMap<Options['routes']>
-  >(
-    to: RouteNamedMapGeneric extends RouteMap
-      ? RouteLocationRaw
-      : RouteLocationNamedRaw<RouteMap, Name> | RouteLocationPathRaw | string
+  push(
+    to:
+      | RouteLocationNamedRaw<RouteNamedMap<Options['routes']>>
+      | string
+      | RouteLocationPathRaw
   ): Promise<NavigationFailure | void | undefined>
 
   /**
@@ -270,13 +268,11 @@ export interface Router<Options extends RouterOptions = RouterOptions> {
    *
    * @param to - Route location to navigate to
    */
-  replace<
-    RouteMap extends RouteNamedMapGeneric = RouteNamedMap<Options['routes']>,
-    Name extends keyof RouteMap = keyof RouteNamedMap<Options['routes']>
-  >(
-    to: RouteNamedMapGeneric extends RouteMap
-      ? RouteLocationRaw
-      : RouteLocationNamedRaw<RouteMap, Name> | RouteLocationPathRaw | string
+  replace(
+    to:
+      | RouteLocationNamedRaw<RouteNamedMap<Options['routes']>>
+      | string
+      | RouteLocationPathRaw
   ): Promise<NavigationFailure | void | undefined>
 
   /**
index a74c2f0c1d9899f19f38a35dd9ee760a254e47b3..ddfd0dc1187147cd3b6de1f66d177de50ef6b794 100644 (file)
@@ -4,7 +4,7 @@ import { Ref, ComponentPublicInstance, Component, DefineComponent } from 'vue'
 import { RouteRecord, RouteRecordNormalized } from '../matcher/types'
 import { HistoryState } from '../history/common'
 import { NavigationFailure } from '../errors'
-import { RouteNamedMapGeneric } from './named'
+import { RouteNamedInfo, RouteNamedMapGeneric } from './named'
 
 export type Lazy<T> = () => Promise<T>
 export type Override<T, U> = Pick<T, Exclude<keyof T, keyof U>> & U
@@ -69,32 +69,24 @@ export interface LocationAsName {
  * @internal
  */
 export interface LocationAsRelativeRaw<
-  RouteMap extends RouteNamedMapGeneric = RouteNamedMapGeneric,
-  Name extends keyof RouteMap = keyof RouteMap
+  Name extends RouteRecordName = RouteRecordName,
+  Info extends RouteNamedInfo = RouteNamedInfo
 > {
-  name?: RouteNamedMapGeneric extends RouteMap ? RouteRecordName : Name
-  params?: RouteNamedMapGeneric extends RouteMap
-    ? RouteParamsRaw
-    : RouteMap[Name]['paramsRaw']
+  name?: Name
+  params?: Info extends RouteNamedInfo<any, any, infer ParamsRaw>
+    ? ParamsRaw
+    : RouteParamsRaw
 }
 
-// this one didn't work ðŸ¤”
-// export type LocationAsRelativeRaw<
-//   RouteMap extends RouteNamedMapGeneric = RouteNamedMapGeneric
-// > = {
-//   [N in keyof RouteMap]: {
-//     name?: N
-//     params?: RouteMap[N]['paramsRaw']
-//   }
-// }[keyof RouteMap]
-
+/**
+ * @internal
+ */
 export interface LocationAsRelative<
-  RouteMap extends RouteNamedMapGeneric = RouteNamedMapGeneric,
-  Name extends keyof RouteMap = keyof RouteMap
+  Info extends RouteNamedInfo = RouteNamedInfo
 > {
-  params?: RouteNamedMapGeneric extends RouteMap
-    ? RouteParams
-    : RouteMap[Name]['params']
+  params?: Info extends RouteNamedInfo<any, infer Params, any>
+    ? Params
+    : RouteParams
 }
 
 export interface RouteLocationOptions {
@@ -128,11 +120,15 @@ export type RouteLocationRaw =
  * @internal
  */
 export type RouteLocationNamedRaw<
-  RouteMap extends RouteNamedMapGeneric = RouteNamedMapGeneric,
-  Name extends keyof RouteMap = keyof RouteMap
-> = RouteQueryAndHash &
-  LocationAsRelativeRaw<RouteMap, Name> &
-  RouteLocationOptions
+  RouteMap extends RouteNamedMapGeneric = RouteNamedMapGeneric
+> = RouteNamedMapGeneric extends RouteMap
+  ? // allows assigning a RouteLocationRaw to RouteLocationNamedRaw
+    RouteQueryAndHash & LocationAsRelativeRaw & RouteLocationOptions
+  : {
+      [K in Extract<keyof RouteMap, RouteRecordName>]: RouteQueryAndHash &
+        LocationAsRelativeRaw<K, RouteMap[K]> &
+        RouteLocationOptions
+    }[Extract<keyof RouteMap, RouteRecordName>]
 
 export type RouteLocationPathRaw =
   | RouteQueryAndHash & LocationAsPath & RouteLocationOptions
index 4740dc3ab88a57a64ec004f3d2f514398074ac52..74b786347f05effa71a6058cae72d31b305de37b 100644 (file)
@@ -1,4 +1,9 @@
-import type { RouteParams, RouteParamsRaw, RouteRecordRaw } from '.'
+import type {
+  RouteParams,
+  RouteParamsRaw,
+  RouteRecordRaw,
+  RouteRecordName,
+} from '.'
 import type { _JoinPath, ParamsFromPath, ParamsRawFromPath } from './paths'
 
 export type RouteNamedMap<
@@ -6,32 +11,30 @@ export type RouteNamedMap<
   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 | symbol
-              ? {
-                  [N in Name]: {
-                    // name: N
-                    params: ParamsFromPath<_JoinPath<Prefix, Path>>
-                    // TODO: ParamsRawFromPath
-                    paramsRaw: ParamsRawFromPath<_JoinPath<Prefix, Path>>
-                    path: _JoinPath<Prefix, Path>
-                  }
+    ? (R extends _RouteNamedRecordBaseInfo<
+        infer Name,
+        infer Path,
+        infer Children
+      >
+        ? (Name extends RouteRecordName
+            ? {
+                [N in Name]: {
+                  // name: N
+                  params: ParamsFromPath<_JoinPath<Prefix, Path>>
+                  // TODO: ParamsRawFromPath
+                  paramsRaw: ParamsRawFromPath<_JoinPath<Prefix, Path>>
+                  path: _JoinPath<Prefix, Path>
                 }
+              }
+            : {
+                // NO_NAME: 1
+              }) &
+            // Recurse children
+            (Children extends Readonly<RouteRecordRaw[]>
+              ? RouteNamedMap<Children, _JoinPath<Prefix, Path>>
               : {
-                  // NO_NAME: 1
-                }) &
-              // Recurse children
-              (Children extends Readonly<RouteRecordRaw[]>
-                ? RouteNamedMap<Children, _JoinPath<Prefix, Path>>
-                : {
-                    // NO_CHILDREN: 1
-                  })
-          : never // Path must be a string
+                  // NO_CHILDREN: 1
+                })
         : {
             // EMPTY: 1
           }) &
@@ -41,11 +44,30 @@ export type RouteNamedMap<
       // END: 1
     }
 
-export type RouteNamedMapGeneric = Record<
-  string | symbol | number,
-  {
-    params: RouteParams
-    paramsRaw: RouteParamsRaw
-    path: string
-  }
->
+export interface _RouteNamedRecordBaseInfo<
+  Name extends RouteRecordName = RouteRecordName, // we don't care about symbols
+  Path extends string = string,
+  Children extends Readonly<RouteRecordRaw[]> = Readonly<RouteRecordRaw[]>
+> {
+  name?: Name
+  path: Path
+  children?: Children
+}
+
+/**
+ * Generic map of named routes from a list of route records.
+ */
+export type RouteNamedMapGeneric = Record<RouteRecordName, RouteNamedInfo>
+
+/**
+ * Relevant information about a named route record to deduce its params.
+ */
+export interface RouteNamedInfo<
+  Path extends string = string,
+  Params extends RouteParams = RouteParams,
+  ParamsRaw extends RouteParamsRaw = RouteParamsRaw
+> {
+  params: Params
+  paramsRaw: ParamsRaw
+  path: Path
+}
index 8ff156f6988128e03a926a2d17ef90de8314de38..024b827e0350be3cfd9418c968b3f4e654fd8702 100644 (file)
@@ -10,10 +10,20 @@ import { RouteParams, RouteParamsRaw, RouteParamValueRaw } from '.'
  */
 export type ParamsFromPath<P extends string = string> = string extends P
   ? RouteParams // Generic version
+  : _ExtractParamsPath<_RemoveRegexpFromParam<P>, false> extends Record<
+      any,
+      never
+    >
+  ? Record<any, never>
   : _ExtractParamsPath<_RemoveRegexpFromParam<P>, false>
 
 export type ParamsRawFromPath<P extends string = string> = string extends P
   ? RouteParamsRaw // Generic version
+  : _ExtractParamsPath<_RemoveRegexpFromParam<P>, true> extends Record<
+      any,
+      never
+    >
+  ? Record<any, never>
   : _ExtractParamsPath<_RemoveRegexpFromParam<P>, true>
 
 /**
index ad194a72a160f9d5b820421b21bcfbb6db64fdf5..3fbb347d1f41e3a28c4fa77adc5c78fbed1b6c57 100644 (file)
@@ -82,17 +82,21 @@ for (const method of methods) {
   r2[method]({ params: { a: 2 } })
   r2[method]({ params: {} })
   r2[method]({ params: { opt: 'hey' } })
+  // @ts-expect-error: but not non existent
+  r2[method]({ params: { fake_param: 'hey' } })
 
   // routes with no params
   r2[method]({ name: 'nested' })
   r2[method]({ name: 'nested', params: {} })
   // FIXME: is it possible to support this version
-  // // @ts-expect-error: does not accept any params
-  // r2[method]({ name: 'nested', params: { eo: 'true' } })
+  // @ts-expect-error: does not accept any params
+  r2[method]({ name: 'nested', params: { id: 2 } })
 }
 
-// still allow generics to be passed for convenience
+// NOTE: not possible if we use the named routes as the point is to provide valid routes only
+// @ts-expect-error
 r2.push({} as unknown as RouteLocationRaw)
+// @ts-expect-error
 r2.replace({} as unknown as RouteLocationRaw)
 
 // createMap(r2.options.routes, true).
index 0342cec257c2b142add9a0429215811c47285807..0ae4918bde8b790b084ebff7459cfc04933f33e2 100644 (file)
@@ -19,6 +19,10 @@ expectType<{ id: readonly [string, ...string[]] }>(params('/users/:id+'))
 expectType<{ id?: string | null | undefined }>(params('/users/:id?'))
 expectType<{ id?: readonly string[] | null | undefined }>(params('/users/:id*'))
 
+expectType<Record<any, never>>(params('/hello/other/thing'))
+// @ts-expect-error
+expectType<{ thing: 'e' }>(params('/hello/other/thing'))
+
 // @ts-expect-error
 expectType<{ other: string }>(params('/users/:id'))
 // @ts-expect-error