]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
feat: allow string in matcher resolve
authorEduardo San Martin Morote <posva13@gmail.com>
Thu, 9 Jan 2025 10:54:20 +0000 (11:54 +0100)
committerEduardo San Martin Morote <posva13@gmail.com>
Wed, 16 Jul 2025 07:57:56 +0000 (09:57 +0200)
packages/router/src/experimental/router.ts
packages/router/src/location.ts
packages/router/src/new-route-resolver/resolver.spec.ts
packages/router/src/new-route-resolver/resolver.test-d.ts
packages/router/src/new-route-resolver/resolver.ts

index 1b252336e3d620e1ef32fa605e984429279294b4..a6c70afffc690736b00a91d3361c138b0baa40ef 100644 (file)
@@ -83,7 +83,6 @@ import {
   routerKey,
   routerViewLocationKey,
 } from '../injectionSymbols'
-import { MatcherLocationAsPathAbsolute } from '../new-route-resolver/matcher-location'
 
 /**
  * resolve, reject arguments of Promise constructor
@@ -537,11 +536,6 @@ export function experimental_createRouter(
       currentLocation && assign({}, currentLocation || currentRoute.value)
     // currentLocation = assign({}, currentLocation || currentRoute.value)
 
-    const locationObject = locationAsObject(
-      rawLocation,
-      currentRoute.value.path
-    )
-
     if (__DEV__) {
       if (!isRouteLocation(rawLocation)) {
         warn(
@@ -551,9 +545,12 @@ export function experimental_createRouter(
         return resolve({})
       }
 
-      if (!locationObject.hash?.startsWith('#')) {
+      if (
+        typeof rawLocation === 'object' &&
+        rawLocation.hash?.startsWith('#')
+      ) {
         warn(
-          `A \`hash\` should always start with the character "#". Replace "${locationObject.hash}" with "#${locationObject.hash}".`
+          `A \`hash\` should always start with the character "#". Replace "${rawLocation.hash}" with "#${rawLocation.hash}".`
         )
       }
     }
@@ -571,12 +568,10 @@ export function experimental_createRouter(
     // }
 
     const matchedRoute = matcher.resolve(
-      // FIXME: should be ok
-      // locationObject as MatcherLocationAsPathRelative,
-      // locationObject as MatcherLocationAsRelative,
-      // locationObject as MatcherLocationAsName, // TODO: this one doesn't allow an undefined currentLocation, the other ones work
-      locationObject as MatcherLocationAsPathAbsolute,
-      currentLocation as unknown as NEW_LocationResolved<EXPERIMENTAL_RouteRecordNormalized>
+      // incompatible types
+      rawLocation as any,
+      // incompatible `matched` requires casting
+      currentLocation as any
     )
     const href = routerHistory.createHref(matchedRoute.fullPath)
 
index 9c896e1e4065d134df8bd41b688dc20700e9935c..77c1666ccca9d7b43c33fba146c8042910669dec 100644 (file)
@@ -10,7 +10,7 @@ import { RouteLocation, RouteLocationNormalizedLoaded } from './typed-routes'
  * Location object returned by {@link `parseURL`}.
  * @internal
  */
-interface LocationNormalized {
+export interface LocationNormalized {
   path: string
   fullPath: string
   hash: string
index ecc2d5e39969183f99c73dfb03d467e2a14f1fc9..436349a047cdaa9b7eb9394461d15f14c6839633 100644 (file)
@@ -337,9 +337,7 @@ describe('RouterMatcher', () => {
           })
         })
 
-        // TODO: move to the router as the matcher dosen't handle a plain string
-        it.todo('decodes query from a string', () => {
-          // @ts-expect-error: does not suppor fullPath
+        it('decodes query from a string', () => {
           expect(matcher.resolve('/foo?foo=%23%2F%3F')).toMatchObject({
             path: '/foo',
             fullPath: '/foo?foo=%23%2F%3F',
@@ -347,8 +345,7 @@ describe('RouterMatcher', () => {
           })
         })
 
-        it.todo('decodes hash from a string', () => {
-          // @ts-expect-error: does not suppor fullPath
+        it('decodes hash from a string', () => {
           expect(matcher.resolve('/foo#%22')).toMatchObject({
             path: '/foo',
             fullPath: '/foo#%22',
index c04dfad31251b687ef69efd182e72b65cdf108b3..6da64da5187fedf3136225cba12208f52f746149 100644 (file)
@@ -18,11 +18,16 @@ describe('Matcher', () => {
       expectTypeOf(matcher.resolve({ path: '/foo' })).toEqualTypeOf<
         NEW_LocationResolved<TMatcherRecord>
       >()
+      expectTypeOf(matcher.resolve('/foo')).toEqualTypeOf<
+        NEW_LocationResolved<TMatcherRecord>
+      >()
     })
 
     it('fails on non absolute location without a currentLocation', () => {
       // @ts-expect-error: needs currentLocation
       matcher.resolve('foo')
+      // @ts-expect-error: needs currentLocation
+      matcher.resolve({ path: 'foo' })
     })
 
     it('resolves relative locations', () => {
@@ -32,6 +37,9 @@ describe('Matcher', () => {
           {} as NEW_LocationResolved<TMatcherRecord>
         )
       ).toEqualTypeOf<NEW_LocationResolved<TMatcherRecord>>()
+      expectTypeOf(
+        matcher.resolve('foo', {} as NEW_LocationResolved<TMatcherRecord>)
+      ).toEqualTypeOf<NEW_LocationResolved<TMatcherRecord>>()
     })
 
     it('resolved named locations', () => {
@@ -42,7 +50,9 @@ describe('Matcher', () => {
 
     it('fails on object relative location without a currentLocation', () => {
       // @ts-expect-error: needs currentLocation
-      matcher.resolve({ params: { id: 1 } })
+      matcher.resolve({ params: { id: '1' } })
+      // @ts-expect-error: needs currentLocation
+      matcher.resolve({ query: { id: '1' } })
     })
 
     it('resolves object relative locations with a currentLocation', () => {
@@ -57,13 +67,17 @@ describe('Matcher', () => {
 
   it('does not allow a name + path', () => {
     matcher.resolve({
-      // ...({} as NEW_LocationResolved),
+      // ...({} as NEW_LocationResolved<TMatcherRecord>),
       name: 'foo',
       params: {},
       // @ts-expect-error: name + path
       path: '/e',
     })
-    // @ts-expect-error: name + currentLocation
-    matcher.resolve({ name: 'a', params: {} }, {} as NEW_LocationResolved)
+    matcher.resolve(
+      // @ts-expect-error: name + currentLocation
+      { name: 'a', params: {} },
+      //
+      {} as NEW_LocationResolved<TMatcherRecord>
+    )
   })
 })
index 93b235c79560aba7407fb9a2bba13ab401d273c5..060aee34ce72b25c92b268f95114520a2e970141 100644 (file)
@@ -1,4 +1,9 @@
-import { type LocationQuery, normalizeQuery, stringifyQuery } from '../query'
+import {
+  type LocationQuery,
+  normalizeQuery,
+  parseQuery,
+  stringifyQuery,
+} from '../query'
 import type {
   MatcherPatternHash,
   MatcherPatternPath,
@@ -6,7 +11,12 @@ import type {
 } from './matcher-pattern'
 import { warn } from '../warning'
 import { encodeQueryValue as _encodeQueryValue, encodeParam } from '../encoding'
-import { NEW_stringifyURL, resolveRelativePath } from '../location'
+import {
+  LocationNormalized,
+  NEW_stringifyURL,
+  parseURL,
+  resolveRelativePath,
+} from '../location'
 import type {
   MatcherLocationAsNamed,
   MatcherLocationAsPathAbsolute,
@@ -32,19 +42,19 @@ export interface NEW_RouterResolver<TMatcherRecordRaw, TMatcherRecord> {
   /**
    * Resolves an absolute location (like `/path/to/somewhere`).
    */
-  // resolve(
-  //   absoluteLocation: `/${string}`,
-  //   currentLocation?: undefined | NEW_LocationResolved<TMatcherRecord>
-  // ): NEW_LocationResolved<TMatcherRecord>
+  resolve(
+    absoluteLocation: `/${string}`,
+    currentLocation?: undefined
+  ): NEW_LocationResolved<TMatcherRecord>
 
   /**
    * Resolves a string location relative to another location. A relative location can be `./same-folder`,
    * `../parent-folder`, `same-folder`, or even `?page=2`.
    */
-  // resolve(
-  //   relativeLocation: string,
-  //   currentLocation: NEW_LocationResolved<TMatcherRecord>
-  // ): NEW_LocationResolved<TMatcherRecord>
+  resolve(
+    relativeLocation: string,
+    currentLocation: NEW_LocationResolved<TMatcherRecord>
+  ): NEW_LocationResolved<TMatcherRecord>
 
   /**
    * Resolves a location by its name. Any required params or query must be passed in the `options` argument.
@@ -53,6 +63,7 @@ export interface NEW_RouterResolver<TMatcherRecordRaw, TMatcherRecord> {
     location: MatcherLocationAsNamed,
     // TODO: is this useful?
     currentLocation?: undefined
+    // currentLocation?: undefined | NEW_LocationResolved<TMatcherRecord>
   ): NEW_LocationResolved<TMatcherRecord>
 
   /**
@@ -63,7 +74,7 @@ export interface NEW_RouterResolver<TMatcherRecordRaw, TMatcherRecord> {
     location: MatcherLocationAsPathAbsolute,
     // TODO: is this useful?
     currentLocation?: undefined
-    // currentLocation?: NEW_LocationResolved<TMatcherRecord>
+    // currentLocation?: NEW_LocationResolved<TMatcherRecord> | undefined
   ): NEW_LocationResolved<TMatcherRecord>
 
   resolve(
@@ -121,7 +132,7 @@ export interface NEW_RouterResolver<TMatcherRecordRaw, TMatcherRecord> {
  */
 export type MatcherLocationRaw =
   // | `/${string}`
-  // | string
+  | string
   | MatcherLocationAsNamed
   | MatcherLocationAsPathAbsolute
   | MatcherLocationAsPathRelative
@@ -355,23 +366,27 @@ export function createCompiledMatcher<
 
   // NOTE: because of the overloads, we need to manually type the arguments
   type MatcherResolveArgs =
-    // | [
-    //     absoluteLocation: `/${string}`,
-    //     currentLocation?: undefined | NEW_LocationResolved<TMatcherRecord>
-    //   ]
-    // | [
-    //     relativeLocation: string,
-    //     currentLocation: NEW_LocationResolved<TMatcherRecord>
-    //   ]
+    | [absoluteLocation: `/${string}`, currentLocation?: undefined]
+    | [
+        relativeLocation: string,
+        currentLocation: NEW_LocationResolved<TMatcherRecord>
+      ]
     | [
         absoluteLocation: MatcherLocationAsPathAbsolute,
+        // Same as above
+        // currentLocation?: NEW_LocationResolved<TMatcherRecord> | undefined
         currentLocation?: undefined
       ]
     | [
         relativeLocation: MatcherLocationAsPathRelative,
         currentLocation: NEW_LocationResolved<TMatcherRecord>
       ]
-    | [location: MatcherLocationAsNamed, currentLocation?: undefined]
+    | [
+        location: MatcherLocationAsNamed,
+        // Same as above
+        // currentLocation?: NEW_LocationResolved<TMatcherRecord> | undefined
+        currentLocation?: undefined
+      ]
     | [
         relativeLocation: MatcherLocationAsRelative,
         currentLocation: NEW_LocationResolved<TMatcherRecord>
@@ -382,7 +397,7 @@ export function createCompiledMatcher<
   ): NEW_LocationResolved<TMatcherRecord> {
     const [to, currentLocation] = args
 
-    if (to.name || to.path == null) {
+    if (typeof to === 'object' && (to.name || to.path == null)) {
       // relative location or by name
       if (__DEV__ && to.name == null && currentLocation == null) {
         console.warn(
@@ -442,13 +457,17 @@ export function createCompiledMatcher<
       // string location, e.g. '/foo', '../bar', 'baz', '?page=1'
     } else {
       // parseURL handles relative paths
-      // parseURL(to.path, currentLocation?.path)
-      const query = normalizeQuery(to.query)
-      const url = {
-        fullPath: NEW_stringifyURL(stringifyQuery, to.path, query, to.hash),
-        path: resolveRelativePath(to.path, currentLocation?.path || '/'),
-        query,
-        hash: to.hash || '',
+      let url: LocationNormalized
+      if (typeof to === 'string') {
+        url = parseURL(parseQuery, to, currentLocation?.path)
+      } else {
+        const query = normalizeQuery(to.query)
+        url = {
+          fullPath: NEW_stringifyURL(stringifyQuery, to.path, query, to.hash),
+          path: resolveRelativePath(to.path, currentLocation?.path || '/'),
+          query,
+          hash: to.hash || '',
+        }
       }
 
       let matcher: TMatcherRecord | undefined