Skip to content

Commit 5c79eb4

Browse files
BridgerBteemingc
andauthored
feat: allow resolve() to accept pathnames with query strings and hash fragments (#15458)
## Summary Adds a `ResolvablePath` type that extends `Pathname` with optional `?${string}` and `#${string}` suffixes, allowing `resolve()` to accept pathnames with query strings and hash fragments. - No runtime changes — `resolve_route()` already passes through `?` and `#` correctly - `Pathname` type is unchanged (still used for `page.url.pathname`) - Existing `resolve()` behavior with route IDs and params is unaffected ## Motivation Currently there is no way to use `goto()` with query strings or hash fragments without either disabling the lint rule or bypassing `resolve()`: 1. `resolve('/products?page=2')` fails type-checking (`Pathname` doesn't include `?`) 2. `goto(resolve('/products') + '?page=2')` fails the `svelte/no-navigation-without-resolve` lint rule (argument isn't a `resolve()` call) Per @teemingc's suggestion in #14750: > "It should be possible to add a union type with a template string such as `/my-route#${string}` and `/my-route?${string}`. This would continue to keep the function type-safe." This is a minimal type-only fix that doesn't conflict with #14756 (which adds richer `{ hash, searchParams }` options). ## What this enables ```js // All of these now type-check AND satisfy the lint rule: goto(resolve('/products?page=2')); goto(resolve(`/search?${params}`)); goto(resolve('/docs/intro#getting-started')); ``` ## Test plan - [x] `pnpm check` passes - [x] `pnpm lint` passes - [x] Existing tests pass (no runtime changes, only type widening) Closes #14750 --------- Co-authored-by: Tee Ming <chewteeming01@gmail.com>
1 parent 1006ca2 commit 5c79eb4

File tree

6 files changed

+76
-18
lines changed

6 files changed

+76
-18
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
feat: allow `resolve()` to accept pathnames with a search string and/or hash

packages/kit/src/runtime/app/paths/client.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { Asset, RouteId, Pathname, ResolvedPathname } from '$app/types' */
1+
/** @import { Asset, RouteId, RouteIdWithSearchOrHash, Pathname, PathnameWithSearchOrHash, ResolvedPathname } from '$app/types' */
22
/** @import { ResolveArgs } from './types.js' */
33
import { base, assets, hash_routing } from './internal/client.js';
44
import { resolve_route } from '../../../utils/routing.js';
@@ -47,7 +47,7 @@ const pathname_prefix = hash_routing ? '#' : '';
4747
* ```
4848
* @since 2.26
4949
*
50-
* @template {RouteId | Pathname} T
50+
* @template {RouteIdWithSearchOrHash | PathnameWithSearchOrHash} T
5151
* @param {ResolveArgs<T>} args
5252
* @returns {ResolvedPathname}
5353
*/

packages/kit/src/runtime/app/paths/public.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { RouteId, Pathname, ResolvedPathname } from '$app/types';
1+
import { RouteIdWithSearchOrHash, PathnameWithSearchOrHash, ResolvedPathname } from '$app/types';
22
import { ResolveArgs } from './types.js';
33

44
export { resolve, asset, match } from './client.js';
@@ -24,6 +24,6 @@ export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit
2424
/**
2525
* @deprecated Use [`resolve(...)`](https://svelte.dev/docs/kit/$app-paths#resolve) instead
2626
*/
27-
export function resolveRoute<T extends RouteId | Pathname>(
27+
export function resolveRoute<T extends RouteIdWithSearchOrHash | PathnameWithSearchOrHash>(
2828
...args: ResolveArgs<T>
2929
): ResolvedPathname;
Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
1-
import { Pathname, RouteId, RouteParams } from '$app/types';
1+
import {
2+
PathnameWithSearchOrHash,
3+
RouteId,
4+
RouteIdWithSearchOrHash,
5+
RouteParams
6+
} from '$app/types';
27

3-
export type ResolveArgs<T extends RouteId | Pathname> = T extends RouteId
4-
? RouteParams<T> extends Record<string, never>
5-
? [route: T]
6-
: [route: T, params: RouteParams<T>]
7-
: [route: T];
8+
type StripSearchOrHash<T extends string> = T extends `${infer Pathname}?${string}`
9+
? Pathname
10+
: T extends `${infer Pathname}#${string}`
11+
? Pathname
12+
: T;
13+
14+
export type ResolveArgs<T extends RouteIdWithSearchOrHash | PathnameWithSearchOrHash> =
15+
T extends RouteId
16+
? RouteParams<T> extends Record<string, never>
17+
? [route: T]
18+
: [route: T, params: RouteParams<T>]
19+
: StripSearchOrHash<T> extends infer U extends RouteId
20+
? RouteParams<U> extends Record<string, never>
21+
? [route: T]
22+
: [route: T, params: RouteParams<U>]
23+
: [route: T];

packages/kit/src/types/ambient.d.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ declare module '$app/types' {
104104
*/
105105
export type RouteId = ReturnType<AppTypes['RouteId']>;
106106

107+
/**
108+
* `RouteId`, but possibly suffixed with a search string and/or hash.
109+
*/
110+
export type RouteIdWithSearchOrHash = RouteId | `${RouteId}?${string}` | `${RouteId}#${string}`;
111+
107112
/**
108113
* A utility for getting the parameters associated with a given route.
109114
*/
@@ -123,6 +128,14 @@ declare module '$app/types' {
123128
*/
124129
export type Pathname = ReturnType<AppTypes['Pathname']>;
125130

131+
/**
132+
* `Pathname`, but possibly suffixed with a search string and/or hash.
133+
*/
134+
export type PathnameWithSearchOrHash =
135+
| Pathname
136+
| `${Pathname}?${string}`
137+
| `${Pathname}#${string}`;
138+
126139
/**
127140
* `Pathname`, but possibly prefixed with a base path. Used for `page.url.pathname`.
128141
*/

packages/kit/types/index.d.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3124,7 +3124,7 @@ declare module '$app/navigation' {
31243124
}
31253125

31263126
declare module '$app/paths' {
3127-
import type { RouteId, Pathname, ResolvedPathname, RouteParams, Asset } from '$app/types';
3127+
import type { RouteIdWithSearchOrHash, PathnameWithSearchOrHash, ResolvedPathname, RouteId, RouteParams, Asset, Pathname } from '$app/types';
31283128
/**
31293129
* A string that matches [`config.kit.paths.base`](https://svelte.dev/docs/kit/configuration#paths).
31303130
*
@@ -3146,14 +3146,25 @@ declare module '$app/paths' {
31463146
/**
31473147
* @deprecated Use [`resolve(...)`](https://svelte.dev/docs/kit/$app-paths#resolve) instead
31483148
*/
3149-
export function resolveRoute<T extends RouteId | Pathname>(
3149+
export function resolveRoute<T extends RouteIdWithSearchOrHash | PathnameWithSearchOrHash>(
31503150
...args: ResolveArgs<T>
31513151
): ResolvedPathname;
3152-
type ResolveArgs<T extends RouteId | Pathname> = T extends RouteId
3153-
? RouteParams<T> extends Record<string, never>
3154-
? [route: T]
3155-
: [route: T, params: RouteParams<T>]
3156-
: [route: T];
3152+
type StripSearchOrHash<T extends string> = T extends `${infer Base}?${string}`
3153+
? Base
3154+
: T extends `${infer Base}#${string}`
3155+
? Base
3156+
: T;
3157+
3158+
type ResolveArgs<T extends RouteIdWithSearchOrHash | PathnameWithSearchOrHash> =
3159+
T extends RouteId
3160+
? RouteParams<T> extends Record<string, never>
3161+
? [route: T]
3162+
: [route: T, params: RouteParams<T>]
3163+
: StripSearchOrHash<T> extends infer U extends RouteId
3164+
? RouteParams<U> extends Record<string, never>
3165+
? [route: T]
3166+
: [route: T, params: RouteParams<U>]
3167+
: [route: T];
31573168
/**
31583169
* Resolve the URL of an asset in your `static` directory, by prefixing it with [`config.kit.paths.assets`](https://svelte.dev/docs/kit/configuration#paths) if configured, or otherwise by prefixing it with the base path.
31593170
*
@@ -3191,7 +3202,7 @@ declare module '$app/paths' {
31913202
* @since 2.26
31923203
*
31933204
* */
3194-
export function resolve<T extends RouteId | Pathname>(...args: ResolveArgs<T>): ResolvedPathname;
3205+
export function resolve<T extends RouteIdWithSearchOrHash | PathnameWithSearchOrHash>(...args: ResolveArgs<T>): ResolvedPathname;
31953206
/**
31963207
* Match a path or URL to a route ID and extracts any parameters.
31973208
*
@@ -3577,6 +3588,11 @@ declare module '$app/types' {
35773588
*/
35783589
export type RouteId = ReturnType<AppTypes['RouteId']>;
35793590

3591+
/**
3592+
* `RouteId`, but possibly suffixed with a search string and/or hash.
3593+
*/
3594+
export type RouteIdWithSearchOrHash = RouteId | `${RouteId}?${string}` | `${RouteId}#${string}`;
3595+
35803596
/**
35813597
* A utility for getting the parameters associated with a given route.
35823598
*/
@@ -3596,6 +3612,14 @@ declare module '$app/types' {
35963612
*/
35973613
export type Pathname = ReturnType<AppTypes['Pathname']>;
35983614

3615+
/**
3616+
* `Pathname`, but possibly suffixed with a search string and/or hash.
3617+
*/
3618+
export type PathnameWithSearchOrHash =
3619+
| Pathname
3620+
| `${Pathname}?${string}`
3621+
| `${Pathname}#${string}`;
3622+
35993623
/**
36003624
* `Pathname`, but possibly prefixed with a base path. Used for `page.url.pathname`.
36013625
*/

0 commit comments

Comments
 (0)