Skip to content

Commit f20dbf1

Browse files
committed
feat: allow string in matcher resolve
1 parent 3416bd0 commit f20dbf1

File tree

5 files changed

+78
-53
lines changed

5 files changed

+78
-53
lines changed

packages/router/src/experimental/router.ts

+9-14
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ import {
8383
routerKey,
8484
routerViewLocationKey,
8585
} from '../injectionSymbols'
86-
import { MatcherLocationAsPathAbsolute } from '../new-route-resolver/matcher-location'
8786

8887
/**
8988
* resolve, reject arguments of Promise constructor
@@ -537,11 +536,6 @@ export function experimental_createRouter(
537536
currentLocation && assign({}, currentLocation || currentRoute.value)
538537
// currentLocation = assign({}, currentLocation || currentRoute.value)
539538

540-
const locationObject = locationAsObject(
541-
rawLocation,
542-
currentRoute.value.path
543-
)
544-
545539
if (__DEV__) {
546540
if (!isRouteLocation(rawLocation)) {
547541
warn(
@@ -551,9 +545,12 @@ export function experimental_createRouter(
551545
return resolve({})
552546
}
553547

554-
if (!locationObject.hash?.startsWith('#')) {
548+
if (
549+
typeof rawLocation === 'object' &&
550+
rawLocation.hash?.startsWith('#')
551+
) {
555552
warn(
556-
`A \`hash\` should always start with the character "#". Replace "${locationObject.hash}" with "#${locationObject.hash}".`
553+
`A \`hash\` should always start with the character "#". Replace "${rawLocation.hash}" with "#${rawLocation.hash}".`
557554
)
558555
}
559556
}
@@ -571,12 +568,10 @@ export function experimental_createRouter(
571568
// }
572569

573570
const matchedRoute = matcher.resolve(
574-
// FIXME: should be ok
575-
// locationObject as MatcherLocationAsPathRelative,
576-
// locationObject as MatcherLocationAsRelative,
577-
// locationObject as MatcherLocationAsName, // TODO: this one doesn't allow an undefined currentLocation, the other ones work
578-
locationObject as MatcherLocationAsPathAbsolute,
579-
currentLocation as unknown as NEW_LocationResolved<EXPERIMENTAL_RouteRecordNormalized>
571+
// incompatible types
572+
rawLocation as any,
573+
// incompatible `matched` requires casting
574+
currentLocation as any
580575
)
581576
const href = routerHistory.createHref(matchedRoute.fullPath)
582577

packages/router/src/location.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { RouteLocation, RouteLocationNormalizedLoaded } from './typed-routes'
1010
* Location object returned by {@link `parseURL`}.
1111
* @internal
1212
*/
13-
interface LocationNormalized {
13+
export interface LocationNormalized {
1414
path: string
1515
fullPath: string
1616
hash: string

packages/router/src/new-route-resolver/resolver.spec.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -337,18 +337,15 @@ describe('RouterMatcher', () => {
337337
})
338338
})
339339

340-
// TODO: move to the router as the matcher dosen't handle a plain string
341-
it.todo('decodes query from a string', () => {
342-
// @ts-expect-error: does not suppor fullPath
340+
it('decodes query from a string', () => {
343341
expect(matcher.resolve('/foo?foo=%23%2F%3F')).toMatchObject({
344342
path: '/foo',
345343
fullPath: '/foo?foo=%23%2F%3F',
346344
query: { foo: '#/?' },
347345
})
348346
})
349347

350-
it.todo('decodes hash from a string', () => {
351-
// @ts-expect-error: does not suppor fullPath
348+
it('decodes hash from a string', () => {
352349
expect(matcher.resolve('/foo#%22')).toMatchObject({
353350
path: '/foo',
354351
fullPath: '/foo#%22',

packages/router/src/new-route-resolver/resolver.test-d.ts

+18-4
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,16 @@ describe('Matcher', () => {
1818
expectTypeOf(matcher.resolve({ path: '/foo' })).toEqualTypeOf<
1919
NEW_LocationResolved<TMatcherRecord>
2020
>()
21+
expectTypeOf(matcher.resolve('/foo')).toEqualTypeOf<
22+
NEW_LocationResolved<TMatcherRecord>
23+
>()
2124
})
2225

2326
it('fails on non absolute location without a currentLocation', () => {
2427
// @ts-expect-error: needs currentLocation
2528
matcher.resolve('foo')
29+
// @ts-expect-error: needs currentLocation
30+
matcher.resolve({ path: 'foo' })
2631
})
2732

2833
it('resolves relative locations', () => {
@@ -32,6 +37,9 @@ describe('Matcher', () => {
3237
{} as NEW_LocationResolved<TMatcherRecord>
3338
)
3439
).toEqualTypeOf<NEW_LocationResolved<TMatcherRecord>>()
40+
expectTypeOf(
41+
matcher.resolve('foo', {} as NEW_LocationResolved<TMatcherRecord>)
42+
).toEqualTypeOf<NEW_LocationResolved<TMatcherRecord>>()
3543
})
3644

3745
it('resolved named locations', () => {
@@ -42,7 +50,9 @@ describe('Matcher', () => {
4250

4351
it('fails on object relative location without a currentLocation', () => {
4452
// @ts-expect-error: needs currentLocation
45-
matcher.resolve({ params: { id: 1 } })
53+
matcher.resolve({ params: { id: '1' } })
54+
// @ts-expect-error: needs currentLocation
55+
matcher.resolve({ query: { id: '1' } })
4656
})
4757

4858
it('resolves object relative locations with a currentLocation', () => {
@@ -57,13 +67,17 @@ describe('Matcher', () => {
5767

5868
it('does not allow a name + path', () => {
5969
matcher.resolve({
60-
// ...({} as NEW_LocationResolved),
70+
// ...({} as NEW_LocationResolved<TMatcherRecord>),
6171
name: 'foo',
6272
params: {},
6373
// @ts-expect-error: name + path
6474
path: '/e',
6575
})
66-
// @ts-expect-error: name + currentLocation
67-
matcher.resolve({ name: 'a', params: {} }, {} as NEW_LocationResolved)
76+
matcher.resolve(
77+
// @ts-expect-error: name + currentLocation
78+
{ name: 'a', params: {} },
79+
//
80+
{} as NEW_LocationResolved<TMatcherRecord>
81+
)
6882
})
6983
})

packages/router/src/new-route-resolver/resolver.ts

+48-29
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
1-
import { type LocationQuery, normalizeQuery, stringifyQuery } from '../query'
1+
import {
2+
type LocationQuery,
3+
normalizeQuery,
4+
parseQuery,
5+
stringifyQuery,
6+
} from '../query'
27
import type {
38
MatcherPatternHash,
49
MatcherPatternPath,
510
MatcherPatternQuery,
611
} from './matcher-pattern'
712
import { warn } from '../warning'
813
import { encodeQueryValue as _encodeQueryValue, encodeParam } from '../encoding'
9-
import { NEW_stringifyURL, resolveRelativePath } from '../location'
14+
import {
15+
LocationNormalized,
16+
NEW_stringifyURL,
17+
parseURL,
18+
resolveRelativePath,
19+
} from '../location'
1020
import type {
1121
MatcherLocationAsNamed,
1222
MatcherLocationAsPathAbsolute,
@@ -32,19 +42,19 @@ export interface NEW_RouterResolver<TMatcherRecordRaw, TMatcherRecord> {
3242
/**
3343
* Resolves an absolute location (like `/path/to/somewhere`).
3444
*/
35-
// resolve(
36-
// absoluteLocation: `/${string}`,
37-
// currentLocation?: undefined | NEW_LocationResolved<TMatcherRecord>
38-
// ): NEW_LocationResolved<TMatcherRecord>
45+
resolve(
46+
absoluteLocation: `/${string}`,
47+
currentLocation?: undefined
48+
): NEW_LocationResolved<TMatcherRecord>
3949

4050
/**
4151
* Resolves a string location relative to another location. A relative location can be `./same-folder`,
4252
* `../parent-folder`, `same-folder`, or even `?page=2`.
4353
*/
44-
// resolve(
45-
// relativeLocation: string,
46-
// currentLocation: NEW_LocationResolved<TMatcherRecord>
47-
// ): NEW_LocationResolved<TMatcherRecord>
54+
resolve(
55+
relativeLocation: string,
56+
currentLocation: NEW_LocationResolved<TMatcherRecord>
57+
): NEW_LocationResolved<TMatcherRecord>
4858

4959
/**
5060
* 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> {
5363
location: MatcherLocationAsNamed,
5464
// TODO: is this useful?
5565
currentLocation?: undefined
66+
// currentLocation?: undefined | NEW_LocationResolved<TMatcherRecord>
5667
): NEW_LocationResolved<TMatcherRecord>
5768

5869
/**
@@ -63,7 +74,7 @@ export interface NEW_RouterResolver<TMatcherRecordRaw, TMatcherRecord> {
6374
location: MatcherLocationAsPathAbsolute,
6475
// TODO: is this useful?
6576
currentLocation?: undefined
66-
// currentLocation?: NEW_LocationResolved<TMatcherRecord>
77+
// currentLocation?: NEW_LocationResolved<TMatcherRecord> | undefined
6778
): NEW_LocationResolved<TMatcherRecord>
6879

6980
resolve(
@@ -121,7 +132,7 @@ export interface NEW_RouterResolver<TMatcherRecordRaw, TMatcherRecord> {
121132
*/
122133
export type MatcherLocationRaw =
123134
// | `/${string}`
124-
// | string
135+
| string
125136
| MatcherLocationAsNamed
126137
| MatcherLocationAsPathAbsolute
127138
| MatcherLocationAsPathRelative
@@ -355,23 +366,27 @@ export function createCompiledMatcher<
355366

356367
// NOTE: because of the overloads, we need to manually type the arguments
357368
type MatcherResolveArgs =
358-
// | [
359-
// absoluteLocation: `/${string}`,
360-
// currentLocation?: undefined | NEW_LocationResolved<TMatcherRecord>
361-
// ]
362-
// | [
363-
// relativeLocation: string,
364-
// currentLocation: NEW_LocationResolved<TMatcherRecord>
365-
// ]
369+
| [absoluteLocation: `/${string}`, currentLocation?: undefined]
370+
| [
371+
relativeLocation: string,
372+
currentLocation: NEW_LocationResolved<TMatcherRecord>
373+
]
366374
| [
367375
absoluteLocation: MatcherLocationAsPathAbsolute,
376+
// Same as above
377+
// currentLocation?: NEW_LocationResolved<TMatcherRecord> | undefined
368378
currentLocation?: undefined
369379
]
370380
| [
371381
relativeLocation: MatcherLocationAsPathRelative,
372382
currentLocation: NEW_LocationResolved<TMatcherRecord>
373383
]
374-
| [location: MatcherLocationAsNamed, currentLocation?: undefined]
384+
| [
385+
location: MatcherLocationAsNamed,
386+
// Same as above
387+
// currentLocation?: NEW_LocationResolved<TMatcherRecord> | undefined
388+
currentLocation?: undefined
389+
]
375390
| [
376391
relativeLocation: MatcherLocationAsRelative,
377392
currentLocation: NEW_LocationResolved<TMatcherRecord>
@@ -382,7 +397,7 @@ export function createCompiledMatcher<
382397
): NEW_LocationResolved<TMatcherRecord> {
383398
const [to, currentLocation] = args
384399

385-
if (to.name || to.path == null) {
400+
if (typeof to === 'object' && (to.name || to.path == null)) {
386401
// relative location or by name
387402
if (__DEV__ && to.name == null && currentLocation == null) {
388403
console.warn(
@@ -442,13 +457,17 @@ export function createCompiledMatcher<
442457
// string location, e.g. '/foo', '../bar', 'baz', '?page=1'
443458
} else {
444459
// parseURL handles relative paths
445-
// parseURL(to.path, currentLocation?.path)
446-
const query = normalizeQuery(to.query)
447-
const url = {
448-
fullPath: NEW_stringifyURL(stringifyQuery, to.path, query, to.hash),
449-
path: resolveRelativePath(to.path, currentLocation?.path || '/'),
450-
query,
451-
hash: to.hash || '',
460+
let url: LocationNormalized
461+
if (typeof to === 'string') {
462+
url = parseURL(parseQuery, to, currentLocation?.path)
463+
} else {
464+
const query = normalizeQuery(to.query)
465+
url = {
466+
fullPath: NEW_stringifyURL(stringifyQuery, to.path, query, to.hash),
467+
path: resolveRelativePath(to.path, currentLocation?.path || '/'),
468+
query,
469+
hash: to.hash || '',
470+
}
452471
}
453472

454473
let matcher: TMatcherRecord | undefined

0 commit comments

Comments
 (0)