]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
refactor: renames and minor changes
authorEduardo San Martin Morote <posva13@gmail.com>
Fri, 6 Dec 2024 21:01:44 +0000 (22:01 +0100)
committerEduardo San Martin Morote <posva13@gmail.com>
Fri, 6 Dec 2024 21:01:44 +0000 (22:01 +0100)
packages/router/src/new-route-resolver/matcher-location.ts
packages/router/src/new-route-resolver/matcher-pattern.ts
packages/router/src/new-route-resolver/matcher.test-d.ts
packages/router/src/new-route-resolver/matcher.ts

index 1bfcd9a164b9ce28ac9bf63fe6cd3bf8dcd50c97..c205a4564533fd09c028a8fdadda7b24f4b9e175 100644 (file)
@@ -6,7 +6,7 @@ import type { MatcherName } from './matcher'
  */
 export type MatcherParamsFormatted = Record<string, unknown>
 
-export interface MatcherLocationAsName {
+export interface MatcherLocationAsNamed {
   name: MatcherName
   params: MatcherParamsFormatted
   query?: LocationQueryRaw
index 3b2bbdbfde1823cc63bb3a2b23f5c8a0f8d68a69..049109e11d7c80562e0a3d9354a58a39a18a6a61 100644 (file)
@@ -47,11 +47,11 @@ export interface MatcherPattern {
    *   query: { used: String }, // we require a `used` query param
    * })
    * // /?used=2
-   * pattern.parseLocation({ path: '/', query: { used: '' }, hash: '' }) // null
+   * pattern.parseLocation({ path: '/', query: { used: '' }, hash: '' }) // null becauso no /foo
    * // /foo?used=2&notUsed&notUsed=2#hello
    * pattern.parseLocation({ path: '/foo', query: { used: '2', notUsed: [null, '2']}, hash: '#hello' })
-   * // { used: '2' } // we extract the required params
-   * // /foo?used=2#hello
+   * // [{}, { used: '2' }, {}]// we extract the required params
+   * // /foo?other=2#hello
    * pattern.parseLocation({ path: '/foo', query: {}, hash: '#hello' })
    * // null // the query param is missing
    * ```
@@ -109,6 +109,7 @@ export interface PatternPathParamOptions<T = unknown>
 
 export interface PatternQueryParamOptions<T = unknown>
   extends PatternParamOptions_Base<T> {
+  // FIXME: can be removed? seems to be the same as above
   get: (value: MatcherQueryParamsValue) => T
   set?: (value: T) => MatcherQueryParamsValue
 }
@@ -153,7 +154,7 @@ export class MatcherPatternImpl implements MatcherPattern {
     query: MatcherQueryParams
     hash: string
   }) {
-    // TODO: is this performant? bench compare to a check with `null
+    // TODO: is this performant? bench compare to a check with `null`
     try {
       return [
         this.path.match(location.path),
index 412cb0719870d84906a7d175c3de9be6357b6d5d..bb45c5129ac87c90bad8f5375ec7fb6d6a785bb4 100644 (file)
@@ -1,16 +1,42 @@
-import { describe, it } from 'vitest'
+import { describe, expectTypeOf, it } from 'vitest'
 import { NEW_LocationResolved, createCompiledMatcher } from './matcher'
 
 describe('Matcher', () => {
-  it('resolves locations', () => {
-    const matcher = createCompiledMatcher()
-    matcher.resolve('/foo')
-    // @ts-expect-error: needs currentLocation
-    matcher.resolve('foo')
-    matcher.resolve('foo', {} as NEW_LocationResolved)
-    matcher.resolve({ name: 'foo', params: {} })
-    // @ts-expect-error: needs currentLocation
-    matcher.resolve({ params: { id: 1 } })
-    matcher.resolve({ params: { id: 1 } }, {} as NEW_LocationResolved)
+  const matcher = createCompiledMatcher()
+
+  describe('matcher.resolve()', () => {
+    it('resolves absolute string locations', () => {
+      expectTypeOf(
+        matcher.resolve('/foo')
+      ).toEqualTypeOf<NEW_LocationResolved>()
+    })
+
+    it('fails on non absolute location without a currentLocation', () => {
+      // @ts-expect-error: needs currentLocation
+      matcher.resolve('foo')
+    })
+
+    it('resolves relative locations', () => {
+      expectTypeOf(
+        matcher.resolve('foo', {} as NEW_LocationResolved)
+      ).toEqualTypeOf<NEW_LocationResolved>()
+    })
+
+    it('resolved named locations', () => {
+      expectTypeOf(
+        matcher.resolve({ name: 'foo', params: {} })
+      ).toEqualTypeOf<NEW_LocationResolved>()
+    })
+
+    it('fails on object relative location without a currentLocation', () => {
+      // @ts-expect-error: needs currentLocation
+      matcher.resolve({ params: { id: 1 } })
+    })
+
+    it('resolves object relative locations with a currentLocation', () => {
+      expectTypeOf(
+        matcher.resolve({ params: { id: 1 } }, {} as NEW_LocationResolved)
+      ).toEqualTypeOf<NEW_LocationResolved>()
+    })
   })
 })
index 4aa742e9303a53aa50d8ee7529affff417ef8033..31b1d031929b30edf5dea085051b1f0a9db41861 100644 (file)
@@ -13,7 +13,7 @@ import {
 } from '../encoding'
 import { parseURL, stringifyURL } from '../location'
 import type {
-  MatcherLocationAsName,
+  MatcherLocationAsNamed,
   MatcherLocationAsRelative,
   MatcherParamsFormatted,
 } from './matcher-location'
@@ -31,7 +31,7 @@ export interface RouteResolver {
 
   /**
    * Resolves a string location relative to another location. A relative location can be `./same-folder`,
-   * `../parent-folder`, or even `same-folder`.
+   * `../parent-folder`, `same-folder`, or even `?page=2`.
    */
   resolve(
     relativeLocation: string,
@@ -41,7 +41,7 @@ export interface RouteResolver {
   /**
    * Resolves a location by its name. Any required params or query must be passed in the `options` argument.
    */
-  resolve(location: MatcherLocationAsName): NEW_LocationResolved
+  resolve(location: MatcherLocationAsNamed): NEW_LocationResolved
 
   /**
    * Resolves a location by its path. Any required query must be passed.
@@ -67,7 +67,7 @@ export interface RouteResolver {
 type MatcherResolveArgs =
   | [absoluteLocation: `/${string}`]
   | [relativeLocation: string, currentLocation: NEW_LocationResolved]
-  | [location: MatcherLocationAsName]
+  | [location: MatcherLocationAsNamed]
   | [
       relativeLocation: MatcherLocationAsRelative,
       currentLocation: NEW_LocationResolved
@@ -108,7 +108,11 @@ export type MatcherPathParams = Record<string, MatcherPathParamsValue>
 export type MatcherQueryParamsValue = string | null | Array<string | null>
 export type MatcherQueryParams = Record<string, MatcherQueryParamsValue>
 
-export function applyToParams<R>(
+/**
+ * Apply a function to all properties in an object. It's used to encode/decode params and queries.
+ * @internal
+ */
+export function applyFnToObject<R>(
   fn: (v: string | number | null | undefined) => R,
   params: MatcherPathParams | LocationQuery | undefined
 ): Record<string, R | R[]> {
@@ -195,7 +199,7 @@ function transformObject<T>(
 }
 
 export const NO_MATCH_LOCATION = {
-  name: Symbol('no-match'),
+  name: __DEV__ ? Symbol('no-match') : Symbol(),
   params: {},
   matched: [],
 } satisfies Omit<NEW_LocationResolved, 'path' | 'hash' | 'query' | 'fullPath'>
@@ -215,8 +219,9 @@ export function createCompiledMatcher(): RouteResolver {
 
   function resolve(...args: MatcherResolveArgs): NEW_LocationResolved {
     const [location, currentLocation] = args
+
+    // string location, e.g. '/foo', '../bar', 'baz', '?page=1'
     if (typeof location === 'string') {
-      // string location, e.g. '/foo', '../bar', 'baz'
       const url = parseURL(parseQuery, location, currentLocation?.path)
 
       let matcher: MatcherPattern | undefined
@@ -257,6 +262,21 @@ export function createCompiledMatcher(): RouteResolver {
       }
     } else {
       // relative location or by name
+      if (__DEV__ && location.name == null && currentLocation == null) {
+        console.warn(
+          `Cannot resolve an unnamed relative location without a current location. This will throw in production.`,
+          location
+        )
+        return {
+          ...NO_MATCH_LOCATION,
+          fullPath: '/',
+          path: '/',
+          query: {},
+          hash: '',
+        }
+      }
+
+      // either one of them must be defined and is catched by the dev only warn above
       const name = location.name ?? currentLocation!.name
       const matcher = matchers.get(name)
       if (!matcher) {
@@ -264,7 +284,8 @@ export function createCompiledMatcher(): RouteResolver {
       }
 
       // unencoded params in a formatted form that the user came up with
-      const params = location.params ?? currentLocation!.params
+      const params: MatcherParamsFormatted =
+        location.params ?? currentLocation!.params
       const mixedUnencodedParams = matcher.matchParams(params)
 
       if (!mixedUnencodedParams) {