Skip to content

Commit

Permalink
feat: allow string in matcher resolve
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Jan 9, 2025
1 parent 3416bd0 commit f20dbf1
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 53 deletions.
23 changes: 9 additions & 14 deletions packages/router/src/experimental/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ import {
routerKey,
routerViewLocationKey,
} from '../injectionSymbols'
import { MatcherLocationAsPathAbsolute } from '../new-route-resolver/matcher-location'

/**
* resolve, reject arguments of Promise constructor
Expand Down Expand Up @@ -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(
Expand All @@ -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}".`
)
}
}
Expand All @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion packages/router/src/location.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 2 additions & 5 deletions packages/router/src/new-route-resolver/resolver.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,18 +337,15 @@ 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',
query: { foo: '#/?' },
})
})

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',
Expand Down
22 changes: 18 additions & 4 deletions packages/router/src/new-route-resolver/resolver.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand All @@ -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', () => {
Expand All @@ -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', () => {
Expand All @@ -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>
)
})
})
77 changes: 48 additions & 29 deletions packages/router/src/new-route-resolver/resolver.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { type LocationQuery, normalizeQuery, stringifyQuery } from '../query'
import {
type LocationQuery,
normalizeQuery,
parseQuery,
stringifyQuery,
} from '../query'
import type {
MatcherPatternHash,
MatcherPatternPath,
MatcherPatternQuery,
} 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,
Expand All @@ -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.
Expand All @@ -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>

/**
Expand All @@ -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(
Expand Down Expand Up @@ -121,7 +132,7 @@ export interface NEW_RouterResolver<TMatcherRecordRaw, TMatcherRecord> {
*/
export type MatcherLocationRaw =
// | `/${string}`
// | string
| string
| MatcherLocationAsNamed
| MatcherLocationAsPathAbsolute
| MatcherLocationAsPathRelative
Expand Down Expand Up @@ -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>
Expand All @@ -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(
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit f20dbf1

Please sign in to comment.