routerKey,
routerViewLocationKey,
} from '../injectionSymbols'
+import { MatcherLocationAsPathAbsolute } from '../new-route-resolver/matcher-location'
/**
* resolve, reject arguments of Promise constructor
* Arbitrary data attached to the record.
*/
meta?: RouteMeta
+
+ components?: Record<string, unknown>
+ component?: unknown
+
+ redirect?: unknown
}
// TODO: is it worth to have 2 types for the undefined values?
return !!matcher.getMatcher(name)
}
+ function locationAsObject(
+ to: RouteLocationRaw | RouteLocationNormalized,
+ currentLocation: string = currentRoute.value.path
+ ): Exclude<RouteLocationRaw, string> | RouteLocationNormalized {
+ return typeof to === 'string'
+ ? parseURL(parseQuery, to, currentLocation)
+ : to
+ }
+
function resolve(
rawLocation: RouteLocationRaw,
currentLocation?: RouteLocationNormalizedLoaded
currentLocation && assign({}, currentLocation || currentRoute.value)
// currentLocation = assign({}, currentLocation || currentRoute.value)
+ const locationObject = locationAsObject(
+ rawLocation,
+ currentRoute.value.path
+ )
+
if (__DEV__) {
if (!isRouteLocation(rawLocation)) {
warn(
return resolve({})
}
- if (
- typeof rawLocation === 'object' &&
- !rawLocation.hash?.startsWith('#')
- ) {
+ if (!locationObject.hash?.startsWith('#')) {
warn(
- `A \`hash\` should always start with the character "#". Replace "${rawLocation.hash}" with "#${rawLocation.hash}".`
+ `A \`hash\` should always start with the character "#". Replace "${locationObject.hash}" with "#${locationObject.hash}".`
)
}
}
const matchedRoute = matcher.resolve(
// FIXME: should be ok
- // @ts-expect-error: too many overlads
- rawLocation,
- currentLocation
+ // 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>
)
const href = routerHistory.createHref(matchedRoute.fullPath)
if (__DEV__) {
if (href.startsWith('//')) {
warn(
- `Location "${rawLocation}" resolved to "${href}". A resolved location cannot start with multiple slashes.`
+ `Location ${JSON.stringify(
+ rawLocation
+ )} resolved to "${href}". A resolved location cannot start with multiple slashes.`
)
}
if (!matchedRoute.matched.length) {
})
}
- function locationAsObject(
- to: RouteLocationRaw | RouteLocationNormalized
- ): Exclude<RouteLocationRaw, string> | RouteLocationNormalized {
- return typeof to === 'string'
- ? parseURL(parseQuery, to, currentRoute.value.path)
- : assign({}, to)
- }
-
function checkCanceledNavigation(
to: RouteLocationNormalized,
from: RouteLocationNormalized
hash = location.slice(hashPos, location.length)
}
- // TODO(major): path ?? location
path = resolveRelativePath(
+ // TODO(major): path ?? location
path != null
? path
: // empty path means a relative query or hash `?foo=f`, `#thing`
*/
params?: undefined
}
+
+// TODO: does it make sense to support absolute paths objects?
+
export interface MatcherLocationAsPathAbsolute
extends MatcherLocationAsPathRelative {
path: `/${string}`
import { describe, expect, it } from 'vitest'
import { defineComponent } from 'vue'
import { RouteComponent, RouteRecordRaw } from '../types'
-import { stringifyURL } from '../location'
+import { NEW_stringifyURL } from '../location'
import { mockWarn } from '../../__tests__/vitest-mock-warn'
import {
createCompiledMatcher,
type NEW_MatcherRecordRaw,
type NEW_LocationResolved,
type NEW_MatcherRecord,
+ NO_MATCH_LOCATION,
} from './resolver'
import { miss } from './matchers/errors'
import { MatcherPatternPath, MatcherPatternPathStatic } from './matcher-pattern'
} from './matcher-location'
// TODO: should be moved to a different test file
// used to check backward compatible paths
-import { PathParams, tokensToParser } from '../matcher/pathParserRanker'
+import {
+ PATH_PARSER_OPTIONS_DEFAULTS,
+ PathParams,
+ tokensToParser,
+} from '../matcher/pathParserRanker'
import { tokenizePath } from '../matcher/pathTokenizer'
+import { mergeOptions } from '../utils'
// for raw route record
const component: RouteComponent = defineComponent({})
// for normalized route records
const components = { default: component }
+function isMatchable(record: RouteRecordRaw): boolean {
+ return !!(
+ record.name ||
+ (record.components && Object.keys(record.components).length) ||
+ record.redirect
+ )
+}
+
function compileRouteRecord(
record: RouteRecordRaw,
parentRecord?: RouteRecordRaw
? record.path
: (parentRecord?.path || '') + record.path
record.path = path
- const parser = tokensToParser(tokenizePath(record.path), {
- // start: true,
- end: record.end,
- sensitive: record.sensitive,
- strict: record.strict,
- })
+ const parser = tokensToParser(
+ tokenizePath(record.path),
+ mergeOptions(PATH_PARSER_OPTIONS_DEFAULTS, record)
+ )
+
+ // console.log({ record, parser })
return {
+ group: !isMatchable(record),
name: record.name,
path: {
const records = (Array.isArray(record) ? record : [record]).map(
(record): EXPERIMENTAL_RouteRecordRaw =>
isExperimentalRouteRecordRaw(record)
- ? record
+ ? { components, ...record }
: compileRouteRecord(record)
)
const matcher = createCompiledMatcher<NEW_MatcherRecord>()
path,
query: {},
hash: '',
+ // by default we have a symbol on every route
name: expect.any(Symbol) as symbol,
// must non enumerable
// matched: [],
params: (typeof toLocation === 'object' && toLocation.params) || {},
- fullPath: stringifyURL(stringifyQuery, {
- path: expectedLocation.path || '/',
- query: expectedLocation.query,
- hash: expectedLocation.hash,
- }),
+ fullPath: NEW_stringifyURL(
+ stringifyQuery,
+ expectedLocation.path || path || '/',
+ expectedLocation.query,
+ expectedLocation.hash
+ ),
...expectedLocation,
}
const resolvedFrom = isMatcherLocationResolved(fromLocation)
? fromLocation
- : // FIXME: is this a ts bug?
- // @ts-expect-error
- matcher.resolve(fromLocation)
+ : matcher.resolve(
+ // FIXME: is this a ts bug?
+ // @ts-expect-error
+ typeof fromLocation === 'string'
+ ? { path: fromLocation }
+ : fromLocation
+ )
+
+ // console.log({ toLocation, resolved, expectedLocation, resolvedFrom })
expect(
matcher.resolve(
- // FIXME: WTF?
+ // FIXME: should work now
// @ts-expect-error
- toLocation,
- resolvedFrom
+ typeof toLocation === 'string' ? { path: toLocation } : toLocation,
+ resolvedFrom === START_LOCATION ? undefined : resolvedFrom
)
).toMatchObject({
...resolved,
})
}
- /**
- *
- * @param record - Record or records we are testing the matcher against
- * @param location - location we want to resolve against
- * @param [start] Optional currentLocation used when resolving
- * @returns error
- */
- function assertErrorMatch(
- record: RouteRecordRaw | RouteRecordRaw[],
- toLocation: Exclude<MatcherLocationRaw, string> | `/${string}`,
- fromLocation:
- | NEW_LocationResolved<NEW_MatcherRecord>
- // absolute locations only
- | `/${string}`
- | MatcherLocationAsNamed
- | MatcherLocationAsPathAbsolute = START_LOCATION
- ) {
- assertRecordMatch(record, toLocation, {}, fromLocation)
- }
-
- describe.skip('LocationAsPath', () => {
+ describe('LocationAsPath', () => {
it('resolves a normal path', () => {
assertRecordMatch({ path: '/', name: 'Home', components }, '/', {
name: 'Home',
})
it('resolves a normal path without name', () => {
+ assertRecordMatch({ path: '/', components }, '/', {
+ path: '/',
+ params: {},
+ })
assertRecordMatch(
{ path: '/', components },
{ path: '/' },
- { name: undefined, path: '/', params: {} }
+ { path: '/', params: {} }
)
})
assertRecordMatch(
{ path: '/users/:id/:other', components },
{ path: '/users/posva/hey' },
- { name: undefined, params: { id: 'posva', other: 'hey' } }
+ { name: expect.any(Symbol), params: { id: 'posva', other: 'hey' } }
)
})
assertRecordMatch(
{ path: '/', components },
{ path: '/foo' },
- { name: undefined, params: {}, path: '/foo', matched: [] }
+ { params: {}, path: '/foo', matched: [] }
)
})
assertRecordMatch(
{ path: '/home/', name: 'Home', components },
{ path: '/home/' },
- { name: 'Home', path: '/home/', matched: expect.any(Array) }
+ { name: 'Home', path: '/home/' }
)
})
path: '/home/',
name: 'Home',
components,
- options: { strict: true },
+ strict: true,
}
- assertErrorMatch(record, { path: '/home' })
+ assertRecordMatch(record, { path: '/home' }, NO_MATCH_LOCATION)
assertRecordMatch(
record,
{ path: '/home/' },
- { name: 'Home', path: '/home/', matched: expect.any(Array) }
+ { name: 'Home', path: '/home/' }
)
})
path: '/home',
name: 'Home',
components,
- options: { strict: true },
+ strict: true,
}
assertRecordMatch(
record,
{ path: '/home' },
- { name: 'Home', path: '/home', matched: expect.any(Array) }
+ { name: 'Home', path: '/home' }
)
- assertErrorMatch(record, { path: '/home/' })
+ assertRecordMatch(record, { path: '/home/' }, NO_MATCH_LOCATION)
})
})
})
it('throws if the named route does not exists', () => {
- expect(() =>
- assertErrorMatch(
- { path: '/', components },
- { name: 'Home', params: {} }
- )
- ).toThrowError('Matcher "Home" not found')
+ const matcher = createCompiledMatcher([])
+ expect(() => matcher.resolve({ name: 'Home', params: {} })).toThrowError(
+ 'Matcher "Home" not found'
+ )
})
it('merges params', () => {
)
})
- // TODO: new matcher no longer allows implicit param merging
- it.todo('only keep existing params', () => {
+ // TODO: this test doesn't seem useful, it's the same as the test above
+ // maybe remove it?
+ it('only keep existing params', () => {
assertRecordMatch(
{ path: '/:a/:b', name: 'p', components },
{ name: 'p', params: { b: 'b' } },
})
})
- describe.skip('LocationAsRelative', () => {
+ describe('LocationAsRelative', () => {
// TODO: not sure where this warning should appear now
it.todo('warns if a path isn not absolute', () => {
const matcher = createCompiledMatcher([
{ path: new MatcherPatternPathStatic('/') },
])
- matcher.resolve('two', matcher.resolve('/'))
+ matcher.resolve({ path: 'two' }, matcher.resolve({ path: '/' }))
expect('received "two"').toHaveBeenWarned()
})
assertRecordMatch(
record,
{ params: { id: 'posva', role: 'admin' } },
- { name: undefined, path: '/users/posva/m/admin' },
+ { path: '/users/posva/m/admin' },
{
path: '/users/ed/m/user',
// params: { id: 'ed', role: 'user' },
record,
{},
{
- name: undefined,
path: '/users/ed/m/user',
params: { id: 'ed', role: 'user' },
},
})
it('throws if the current named route does not exists', () => {
- const record = { path: '/', components }
- const start = {
- name: 'home',
- params: {},
- path: '/',
- matched: [record],
- }
- // the property should be non enumerable
- Object.defineProperty(start, 'matched', { enumerable: false })
- expect(
- assertErrorMatch(
- record,
- { params: { a: 'foo' } },
+ const matcher = createCompiledMatcher([])
+ expect(() =>
+ matcher.resolve(
+ {},
{
- name: 'home',
+ name: 'ko',
params: {},
- // matched: start.matched.map(normalizeRouteRecord),
- // meta: {},
+ fullPath: '/',
+ hash: '',
+ matched: [],
+ path: '/',
+ query: {},
}
)
- ).toMatchSnapshot()
+ ).toThrowError('Matcher "ko" not found')
})
it('avoids records with children without a component nor name', () => {
- assertErrorMatch(
+ assertRecordMatch(
{
path: '/articles',
children: [{ path: ':id', components }],
},
- { path: '/articles' }
+ { path: '/articles' },
+ NO_MATCH_LOCATION
)
})
- it('avoid deeply nested records with children without a component nor name', () => {
- assertErrorMatch(
+ it('avoids deeply nested records with children without a component nor name', () => {
+ assertRecordMatch(
{
path: '/app',
components,
},
],
},
- { path: '/articles' }
+ { path: '/articles' },
+ NO_MATCH_LOCATION
)
})
{ path: new MatcherPatternPathStatic('/users') },
])
- expect(matcher.resolve('/')).toMatchObject({
+ expect(matcher.resolve({ path: '/' })).toMatchObject({
fullPath: '/',
path: '/',
params: {},
hash: '',
})
- expect(matcher.resolve('/users')).toMatchObject({
+ expect(matcher.resolve({ path: '/users' })).toMatchObject({
fullPath: '/users',
path: '/users',
params: {},
},
])
- expect(matcher.resolve('/users/1')).toMatchObject({
+ expect(matcher.resolve({ path: '/users/1' })).toMatchObject({
fullPath: '/users/1',
path: '/users/1',
params: { id: '1' },
})
describe('resolve()', () => {
- describe('absolute locations as strings', () => {
+ describe.todo('absolute locations as strings', () => {
it('resolves string locations with no params', () => {
const matcher = createCompiledMatcher([EMPTY_PATH_ROUTE])
- expect(matcher.resolve('/?a=a&b=b#h')).toMatchObject({
+ expect(matcher.resolve({ path: '/?a=a&b=b#h' })).toMatchObject({
path: '/',
params: {},
query: { a: 'a', b: 'b' },
it('resolves a not found string', () => {
const matcher = createCompiledMatcher()
- expect(matcher.resolve('/bar?q=1#hash')).toEqual({
+ expect(matcher.resolve({ path: '/bar?q=1#hash' })).toEqual({
...NO_MATCH_LOCATION,
fullPath: '/bar?q=1#hash',
path: '/bar',
it('resolves string locations with params', () => {
const matcher = createCompiledMatcher([USER_ID_ROUTE])
- expect(matcher.resolve('/users/1?a=a&b=b#h')).toMatchObject({
+ expect(matcher.resolve({ path: '/users/1?a=a&b=b#h' })).toMatchObject({
path: '/users/1',
params: { id: 1 },
query: { a: 'a', b: 'b' },
hash: '#h',
})
- expect(matcher.resolve('/users/54?a=a&b=b#h')).toMatchObject({
+ expect(matcher.resolve({ path: '/users/54?a=a&b=b#h' })).toMatchObject({
path: '/users/54',
params: { id: 54 },
query: { a: 'a', b: 'b' },
},
])
- expect(matcher.resolve('/foo?page=100&b=b#h')).toMatchObject({
+ expect(matcher.resolve({ path: '/foo?page=100&b=b#h' })).toMatchObject({
params: { page: 100 },
path: '/foo',
query: {
},
])
- expect(matcher.resolve('/foo?a=a&b=b#bar')).toMatchObject({
+ expect(matcher.resolve({ path: '/foo?a=a&b=b#bar' })).toMatchObject({
hash: '#bar',
params: { hash: 'bar' },
path: '/foo',
},
])
- expect(matcher.resolve('/users/24?page=100#bar')).toMatchObject({
+ expect(
+ matcher.resolve({ path: '/users/24?page=100#bar' })
+ ).toMatchObject({
params: { id: 24, page: 100, hash: 'bar' },
})
})
])
expect(
- matcher.resolve('foo', matcher.resolve('/nested/'))
+ matcher.resolve(
+ { path: 'foo' },
+ matcher.resolve({ path: '/nested/' })
+ )
).toMatchObject({
params: {},
path: '/nested/foo',
hash: '',
})
expect(
- matcher.resolve('../foo', matcher.resolve('/nested/'))
+ matcher.resolve(
+ { path: '../foo' },
+ matcher.resolve({ path: '/nested/' })
+ )
).toMatchObject({
params: {},
path: '/foo',
hash: '',
})
expect(
- matcher.resolve('./foo', matcher.resolve('/nested/'))
+ matcher.resolve(
+ { path: './foo' },
+ matcher.resolve({ path: '/nested/' })
+ )
).toMatchObject({
params: {},
path: '/nested/foo',
const matcher = createCompiledMatcher([ANY_PATH_ROUTE])
describe('decodes', () => {
it('handles encoded string path', () => {
- expect(matcher.resolve('/%23%2F%3F')).toMatchObject({
+ expect(matcher.resolve({ path: '/%23%2F%3F' })).toMatchObject({
fullPath: '/%23%2F%3F',
path: '/%23%2F%3F',
query: {},
})
})
- it('decodes query from a string', () => {
+ // 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
expect(matcher.resolve('/foo?foo=%23%2F%3F')).toMatchObject({
path: '/foo',
fullPath: '/foo?foo=%23%2F%3F',
})
})
- it('decodes hash from a string', () => {
+ it.todo('decodes hash from a string', () => {
+ // @ts-expect-error: does not suppor fullPath
expect(matcher.resolve('/foo#%22')).toMatchObject({
path: '/foo',
fullPath: '/foo#%22',
describe('matcher.resolve()', () => {
it('resolves absolute string locations', () => {
- expectTypeOf(matcher.resolve('/foo')).toEqualTypeOf<
+ expectTypeOf(matcher.resolve({ path: '/foo' })).toEqualTypeOf<
NEW_LocationResolved<TMatcherRecord>
>()
})
it('resolves relative locations', () => {
expectTypeOf(
- matcher.resolve('foo', {} as NEW_LocationResolved<TMatcherRecord>)
+ matcher.resolve(
+ { path: 'foo' },
+ {} as NEW_LocationResolved<TMatcherRecord>
+ )
).toEqualTypeOf<NEW_LocationResolved<TMatcherRecord>>()
})
-import {
- type LocationQuery,
- parseQuery,
- normalizeQuery,
- stringifyQuery,
-} from '../query'
+import { type LocationQuery, normalizeQuery, stringifyQuery } from '../query'
import type {
MatcherPatternHash,
MatcherPatternPath,
} from './matcher-pattern'
import { warn } from '../warning'
import { encodeQueryValue as _encodeQueryValue, encodeParam } from '../encoding'
-import { parseURL, NEW_stringifyURL } from '../location'
+import { NEW_stringifyURL, resolveRelativePath } from '../location'
import type {
MatcherLocationAsNamed,
MatcherLocationAsPathAbsolute,
/**
* 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>
+ // ): 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.
*/
resolve(
- location: MatcherLocationAsNamed
+ location: MatcherLocationAsNamed,
+ // TODO: is this useful?
+ currentLocation?: undefined
): NEW_LocationResolved<TMatcherRecord>
/**
* @param location - The location to resolve.
*/
resolve(
- location: MatcherLocationAsPathAbsolute
+ location: MatcherLocationAsPathAbsolute,
+ // TODO: is this useful?
+ currentLocation?: undefined
+ // currentLocation?: NEW_LocationResolved<TMatcherRecord>
): NEW_LocationResolved<TMatcherRecord>
resolve(
* Allowed location objects to be passed to {@link NEW_RouterResolver['resolve']}
*/
export type MatcherLocationRaw =
- | `/${string}`
- | string
+ // | `/${string}`
+ // | string
| MatcherLocationAsNamed
| MatcherLocationAsPathAbsolute
| MatcherLocationAsPathRelative
* Array of nested routes.
*/
children?: NEW_MatcherRecordRaw[]
+
+ /**
+ * Is this a record that groups children. Cannot be matched
+ */
+ group?: boolean
}
export interface NEW_MatcherRecordBase<T> {
query?: MatcherPatternQuery
hash?: MatcherPatternHash
+ group?: boolean
+
parent?: T
}
// 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 | NEW_LocationResolved<TMatcherRecord>
- ]
- | [
- relativeLocation: string,
- currentLocation: NEW_LocationResolved<TMatcherRecord>
+ absoluteLocation: MatcherLocationAsPathAbsolute,
+ currentLocation?: undefined
]
- | [absoluteLocation: MatcherLocationAsPathAbsolute]
| [
relativeLocation: MatcherLocationAsPathRelative,
currentLocation: NEW_LocationResolved<TMatcherRecord>
]
- | [location: MatcherLocationAsNamed]
+ | [location: MatcherLocationAsNamed, currentLocation?: undefined]
| [
relativeLocation: MatcherLocationAsRelative,
currentLocation: NEW_LocationResolved<TMatcherRecord>
function resolve(
...args: MatcherResolveArgs
): NEW_LocationResolved<TMatcherRecord> {
- const [location, currentLocation] = args
+ const [to, currentLocation] = args
+
+ if (to.name || to.path == null) {
+ // relative location or by name
+ if (__DEV__ && to.name == null && currentLocation == null) {
+ console.warn(
+ `Cannot resolve an unnamed relative location without a current location. This will throw in production.`,
+ to
+ )
+ // NOTE: normally there is no query, hash or path but this helps debug
+ // what kind of object location was passed
+ // @ts-expect-error: to is never
+ const query = normalizeQuery(to.query)
+ // @ts-expect-error: to is never
+ const hash = to.hash ?? ''
+ // @ts-expect-error: to is never
+ const path = to.path ?? '/'
+ return {
+ ...NO_MATCH_LOCATION,
+ fullPath: NEW_stringifyURL(stringifyQuery, path, query, hash),
+ path,
+ query,
+ hash,
+ }
+ }
- // string location, e.g. '/foo', '../bar', 'baz', '?page=1'
- if (typeof location === 'string') {
+ // either one of them must be defined and is catched by the dev only warn above
+ const name = to.name ?? currentLocation?.name
+ // FIXME: remove once name cannot be null
+ const matcher = name != null && matchers.get(name)
+ if (!matcher) {
+ throw new Error(`Matcher "${String(name)}" not found`)
+ }
+
+ // unencoded params in a formatted form that the user came up with
+ const params: MatcherParamsFormatted = {
+ ...currentLocation?.params,
+ ...to.params,
+ }
+ const path = matcher.path.build(params)
+ const hash = matcher.hash?.build(params) ?? ''
+ const matched = buildMatched(matcher)
+ const query = Object.assign(
+ {
+ ...currentLocation?.query,
+ ...normalizeQuery(to.query),
+ },
+ ...matched.map(matcher => matcher.query?.build(params))
+ )
+
+ return {
+ name,
+ fullPath: NEW_stringifyURL(stringifyQuery, path, query, hash),
+ path,
+ query,
+ hash,
+ params,
+ matched,
+ }
+ // string location, e.g. '/foo', '../bar', 'baz', '?page=1'
+ } else {
// parseURL handles relative paths
- const url = parseURL(parseQuery, location, currentLocation?.path)
+ // 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 matcher: TMatcherRecord | undefined
let matched: NEW_LocationResolved<TMatcherRecord>['matched'] | undefined
...url,
...NO_MATCH_LOCATION,
// already decoded
- query: url.query,
- hash: url.hash,
+ // query: url.query,
+ // hash: url.hash,
}
}
// matcher exists if matched exists
name: matcher!.name,
params: parsedParams,
- // already decoded
- query: url.query,
- hash: url.hash,
matched,
}
// TODO: handle object location { path, query, hash }
- } 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
- )
- const query = normalizeQuery(location.query)
- const hash = location.hash ?? ''
- const path = location.path ?? '/'
- return {
- ...NO_MATCH_LOCATION,
- fullPath: NEW_stringifyURL(stringifyQuery, path, query, hash),
- 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
- // FIXME: remove once name cannot be null
- const matcher = name != null && matchers.get(name)
- if (!matcher) {
- throw new Error(`Matcher "${String(location.name)}" not found`)
- }
-
- // unencoded params in a formatted form that the user came up with
- const params: MatcherParamsFormatted = {
- ...currentLocation?.params,
- ...location.params,
- }
- const path = matcher.path.build(params)
- const hash = matcher.hash?.build(params) ?? ''
- const matched = buildMatched(matcher)
- const query = Object.assign(
- {
- ...currentLocation?.query,
- ...normalizeQuery(location.query),
- },
- ...matched.map(matcher => matcher.query?.build(params))
- )
-
- return {
- name,
- fullPath: NEW_stringifyURL(stringifyQuery, path, query, hash),
- path,
- query,
- hash,
- params,
- matched,
- }
}
}
- function addRoute(record: NEW_MatcherRecordRaw, parent?: TMatcherRecord) {
+ function addMatcher(record: NEW_MatcherRecordRaw, parent?: TMatcherRecord) {
const name = record.name ?? (__DEV__ ? Symbol('unnamed-route') : Symbol())
// FIXME: proper normalization of the record
// @ts-expect-error: we are not properly normalizing the record yet
name,
parent,
}
- matchers.set(name, normalizedRecord)
+ // TODO:
+ // record.children
+ if (!normalizedRecord.group) {
+ matchers.set(name, normalizedRecord)
+ }
return normalizedRecord
}
for (const record of records) {
- addRoute(record)
+ addMatcher(record)
}
- function removeRoute(matcher: TMatcherRecord) {
+ function removeMatcher(matcher: TMatcherRecord) {
matchers.delete(matcher.name)
// TODO: delete children and aliases
}
- function clearRoutes() {
+ function clearMatchers() {
matchers.clear()
}
return {
resolve,
- addMatcher: addRoute,
- removeMatcher: removeRoute,
- clearMatchers: clearRoutes,
+ addMatcher,
+ removeMatcher,
+ clearMatchers,
getMatcher,
getMatchers,
}
*/
export type LocationQueryValue = string | null
/**
- * Possible values when defining a query.
+ * Possible values when defining a query. `undefined` allows to remove a value.
*
* @internal
*/