Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 64 additions & 54 deletions packages/router-core/src/path.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { isServer } from '@tanstack/router-core/isServer'
import { last } from './utils'
import {
SEGMENT_TYPE_OPTIONAL_PARAM,
Expand Down Expand Up @@ -254,73 +253,84 @@ export function interpolatePath({
// declaration doesn't reference a property that no longer exists.
...rest
}: InterpolatePathOptions): InterPolatePathResult {
void rest
// Tracking if any params are missing in the `params` object
// when interpolating the path
let isMissingParams = false
const usedParams: Record<string, unknown> = Object.create(null)

if (!path || path === '/')
if (!path || path === '/') {
return { interpolatedPath: '/', usedParams, isMissingParams }
if (!path.includes('$'))
}
if (!path.includes('$')) {
return { interpolatedPath: path, usedParams, isMissingParams }
}

// Fast path for common templates like `/posts/$id` or `/files/$`.
// Braced segments (`{...}`) are more complex (prefix/suffix/optional) and are
// handled by the general parser below.
if (path.indexOf('{') === -1) {
const length = path.length
let cursor = 0
let joined = ''

while (cursor < length) {
// Skip slashes between segments. '/' code is 47
while (cursor < length && path.charCodeAt(cursor) === 47) {
cursor++
}
if (cursor >= length) {
break
}

if (isServer ?? rest.server) {
// Fast path for common templates like `/posts/$id` or `/files/$`.
// Braced segments (`{...}`) are more complex (prefix/suffix/optional) and are
// handled by the general parser below.
if (path.indexOf('{') === -1) {
const length = path.length
let cursor = 0
let joined = ''

while (cursor < length) {
// Skip slashes between segments. '/' code is 47
while (cursor < length && path.charCodeAt(cursor) === 47) cursor++
if (cursor >= length) break

const start = cursor
let end = path.indexOf('/', cursor)
if (end === -1) end = length
cursor = end

const part = path.substring(start, end)
if (!part) continue

// `$id` or `$` (splat). '$' code is 36
if (part.charCodeAt(0) === 36) {
if (part.length === 1) {
const splat = params._splat
usedParams._splat = splat
// TODO: Deprecate *
usedParams['*'] = splat

if (!splat) {
isMissingParams = true
continue
}

const value = encodeParam('_splat', params, decoder)
joined += '/' + value
} else {
const key = part.substring(1)
if (!isMissingParams && !(key in params)) {
isMissingParams = true
}
usedParams[key] = params[key]

const value = encodeParam(key, params, decoder) ?? 'undefined'
joined += '/' + value
const start = cursor
let end = path.indexOf('/', cursor)
if (end === -1) {
end = length
}
cursor = end

const part = path.substring(start, end)
if (!part) {
continue
}

// `$id` or `$` (splat). '$' code is 36
if (part.charCodeAt(0) === 36) {
if (part.length === 1) {
const splat = params._splat
usedParams._splat = splat
// TODO: Deprecate *
usedParams['*'] = splat

if (!splat) {
isMissingParams = true
continue
}

const value = encodeParam('_splat', params, decoder)
joined += '/' + value
} else {
joined += '/' + part
const key = part.substring(1)
if (!isMissingParams && !(key in params)) {
isMissingParams = true
}
usedParams[key] = params[key]

const value = encodeParam(key, params, decoder) ?? 'undefined'
joined += '/' + value
}
} else {
joined += '/' + part
}
}

if (path.endsWith('/')) joined += '/'

const interpolatedPath = joined || '/'
return { usedParams, interpolatedPath, isMissingParams }
if (path.endsWith('/')) {
joined += '/'
}

const interpolatedPath = joined || '/'
return { usedParams, interpolatedPath, isMissingParams }
}

const length = path.length
Expand Down