Skip to content

Commit 1f09349

Browse files
authored
route-pattern: better types (#10881)
1 parent 677805e commit 1f09349

File tree

6 files changed

+56
-23
lines changed

6 files changed

+56
-23
lines changed

packages/route-pattern/src/lib/href.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { RequiredParams, OptionalParams } from './params.ts'
22
import { parse, type ParseResult, type Token } from './parse.ts'
33
import type { RoutePattern } from './route-pattern.ts'
4+
import type { UnknownArgs } from './type-utils.ts'
45
import type { Variant } from './variant.ts'
56

67
/**
@@ -28,7 +29,7 @@ export class MissingParamError extends Error {
2829
* @return A function that builds hrefs from patterns and parameters
2930
*/
3031
export function createHrefBuilder<T extends string | RoutePattern = string>(): HrefBuilder<T> {
31-
return (pattern: string | RoutePattern, ...args: any) =>
32+
return (pattern: string | RoutePattern, ...args: UnknownArgs) =>
3233
formatHref(parse(typeof pattern === 'string' ? pattern : pattern.source), ...args)
3334
}
3435

packages/route-pattern/src/lib/matcher.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,28 @@ import type { RoutePattern } from './route-pattern.ts'
33
/**
44
* An interface for matching URLs against patterns.
55
*/
6-
export interface Matcher<T = any> {
6+
export interface Matcher<data = unknown> {
77
/**
88
* Add a pattern to the matcher.
99
*
1010
* @param pattern The pattern to add
1111
* @param data The data to associate with the pattern
1212
*/
13-
add<P extends string>(pattern: P | RoutePattern<P>, data: T): void
13+
add<source extends string>(pattern: source | RoutePattern<source>, data: data): void
1414
/**
1515
* Find the best match for a URL.
1616
*
1717
* @param url The URL to match
1818
* @return The match result, or `null` if no match was found
1919
*/
20-
match(url: string | URL): MatchResult<T> | null
20+
match(url: string | URL): MatchResult<data> | null
2121
/**
2222
* Find all matches for a URL.
2323
*
2424
* @param url The URL to match
2525
* @return A generator that yields all matches
2626
*/
27-
matchAll(url: string | URL): Generator<MatchResult<T>>
27+
matchAll(url: string | URL): Generator<MatchResult<data>>
2828
/**
2929
* The number of patterns in the matcher.
3030
*/
@@ -34,11 +34,11 @@ export interface Matcher<T = any> {
3434
/**
3535
* The result of matching a URL against a pattern.
3636
*/
37-
export interface MatchResult<T = any> {
37+
export interface MatchResult<data = unknown> {
3838
/**
3939
* The data associated with the matched pattern.
4040
*/
41-
data: T
41+
data: data
4242
/**
4343
* The parameters extracted from the URL.
4444
*/

packages/route-pattern/src/lib/matchers/array.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ import { RoutePattern } from '../route-pattern.ts'
1010
* - Pattern set changes frequently (cheap to rebuild)
1111
* - Memory footprint needs to be minimal
1212
*/
13-
export class ArrayMatcher<T = any> implements Matcher<T> {
14-
#pairs: { pattern: RoutePattern; data: T }[] = []
13+
export class ArrayMatcher<data = unknown> implements Matcher<data> {
14+
#pairs: { pattern: RoutePattern; data: data }[] = []
1515
#count = 0
1616

1717
/**
1818
* @param pattern The pattern to add
1919
* @param data The data to associate with the pattern
2020
*/
21-
add<P extends string>(pattern: P | RoutePattern<P>, data: T): void {
21+
add<source extends string>(pattern: source | RoutePattern<source>, data: data): void {
2222
let routePattern = typeof pattern === 'string' ? new RoutePattern(pattern) : pattern
2323
this.#pairs.push({ pattern: routePattern, data })
2424
this.#count++
@@ -28,7 +28,7 @@ export class ArrayMatcher<T = any> implements Matcher<T> {
2828
* @param url The URL to match
2929
* @return The match result, or `null` if no match was found
3030
*/
31-
match(url: string | URL): MatchResult<T> | null {
31+
match(url: string | URL): MatchResult<data> | null {
3232
if (typeof url === 'string') url = new URL(url)
3333

3434
for (let { pattern, data } of this.#pairs) {
@@ -45,7 +45,7 @@ export class ArrayMatcher<T = any> implements Matcher<T> {
4545
* @param url The URL to match
4646
* @return A generator that yields all matches
4747
*/
48-
*matchAll(url: string | URL): Generator<MatchResult<T>> {
48+
*matchAll(url: string | URL): Generator<MatchResult<data>> {
4949
if (typeof url === 'string') url = new URL(url)
5050

5151
for (let { pattern, data } of this.#pairs) {

packages/route-pattern/src/lib/matchers/trie.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ export interface TrieMatcherOptions {
158158
* - Match performance matters more than build time
159159
* - You need exhaustive matching via `matchAll()`
160160
*/
161-
export class TrieMatcher<T = any> implements Matcher<T> {
161+
export class TrieMatcher<data = unknown> implements Matcher<data> {
162162
#pathnameOnlyRoot: TrieNode
163163
#originRoot: OriginTrieNode
164164
#patternCount = 0
@@ -183,7 +183,7 @@ export class TrieMatcher<T = any> implements Matcher<T> {
183183
* @param pattern The pattern to add
184184
* @param node The data to associate with the pattern
185185
*/
186-
add(pattern: string | RoutePattern, node: T): void {
186+
add(pattern: string | RoutePattern, node: data): void {
187187
let routePattern = typeof pattern === 'string' ? new RoutePattern(pattern) : pattern
188188
let parsed = parse(routePattern.source)
189189

@@ -299,7 +299,7 @@ export class TrieMatcher<T = any> implements Matcher<T> {
299299
* @param url The URL to match
300300
* @return The match result, or `null` if no match was found
301301
*/
302-
match(url: string | URL): MatchResult<T> | null {
302+
match(url: string | URL): MatchResult<data> | null {
303303
let urlObj = typeof url === 'string' ? new URL(url) : url
304304

305305
let parsedUrl: ParsedURL = {
@@ -343,7 +343,7 @@ export class TrieMatcher<T = any> implements Matcher<T> {
343343
* @param url The URL to match
344344
* @return A generator that yields all matches
345345
*/
346-
*matchAll(url: string | URL): Generator<MatchResult<T>> {
346+
*matchAll(url: string | URL): Generator<MatchResult<data>> {
347347
let urlObj = typeof url === 'string' ? new URL(url) : url
348348
let pathname = urlObj.pathname
349349

@@ -477,7 +477,7 @@ export class TrieMatcher<T = any> implements Matcher<T> {
477477
port: string | undefined,
478478
pathnameTokens: Token[] | undefined,
479479
pattern: RoutePattern,
480-
userNode: T,
480+
userNode: data,
481481
searchConstraints?: SearchConstraints,
482482
parsed?: ParseResult,
483483
): void {
@@ -579,7 +579,7 @@ export class TrieMatcher<T = any> implements Matcher<T> {
579579
node: TrieNode,
580580
tokens: Token[],
581581
pattern: RoutePattern,
582-
userNode: T,
582+
userNode: data,
583583
searchConstraints?: SearchConstraints,
584584
parsed?: ParseResult,
585585
): void {
@@ -818,7 +818,7 @@ export class TrieMatcher<T = any> implements Matcher<T> {
818818
#addPatternMatch(
819819
node: TrieNode,
820820
pattern: RoutePattern,
821-
userNode: T,
821+
userNode: data,
822822
paramNames: string[],
823823
matchOrigin: boolean,
824824
searchConstraints?: SearchConstraints,
@@ -1499,7 +1499,7 @@ export class TrieMatcher<T = any> implements Matcher<T> {
14991499
}
15001500
}
15011501

1502-
#tryOriginMatch(parsedUrl: ParsedURL, segments: string[], urlObj: URL): MatchResult<T> | null {
1502+
#tryOriginMatch(parsedUrl: ParsedURL, segments: string[], urlObj: URL): MatchResult<data> | null {
15031503
let results = this.#findOriginMatches(urlObj, segments, parsedUrl.search, true)
15041504
if (results.length > 0) {
15051505
let best = results[0]
@@ -1514,7 +1514,7 @@ export class TrieMatcher<T = any> implements Matcher<T> {
15141514
baseParams: Record<string, string>,
15151515
search: string,
15161516
urlObj: URL,
1517-
): MatchResult<T> | null {
1517+
): MatchResult<data> | null {
15181518
let initialState: MatchState = {
15191519
segments,
15201520
segmentIndex: 0,
@@ -1530,7 +1530,7 @@ export class TrieMatcher<T = any> implements Matcher<T> {
15301530
return null
15311531
}
15321532

1533-
#tryStaticPathMatch(segments: string[], search: string, url: URL): MatchResult<T> | null {
1533+
#tryStaticPathMatch(segments: string[], search: string, url: URL): MatchResult<data> | null {
15341534
let results = this.#walkStaticPath(segments, search, false)
15351535
if (results.length > 0) {
15361536
let best = results[0]

packages/route-pattern/src/lib/parse.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { split, type SplitPattern, type Split } from './split.ts'
22
import { parseSearchConstraints, type SearchConstraints } from './search-constraints.ts'
3+
import type { ForceDistributive } from './type-utils.ts'
34

45
/**
56
* An error thrown when a pattern fails to parse.
@@ -176,7 +177,7 @@ export interface ParsedPattern {
176177

177178
// prettier-ignore
178179
export type Parse<T extends string> =
179-
T extends any ?
180+
T extends ForceDistributive ?
180181
Split<T> extends infer S extends SplitPattern ?
181182
{
182183
protocol: S['protocol'] extends string ? ParsePart<S['protocol']> : undefined

packages/route-pattern/src/lib/type-utils.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,34 @@ export type IsEqual<A, B> =
44
(<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false
55

66
export type Simplify<T> = { [K in keyof T]: T[K] } & {}
7+
8+
/**
9+
* Function arguments are contravariant, so unknown args must be typed as `Array<any>`
10+
*
11+
* Usage:
12+
*
13+
* ```ts
14+
* type UnknownFunction = (args: UnknownArgs) => unknown
15+
* ```
16+
*/
17+
export type UnknownArgs = Array<any>
18+
19+
/**
20+
* Force TS to distribute a union with `T extends ForceDistributeUnion ? ... : ...`
21+
* as a more explicit alias of the common `T extends any ? ... : ...` pattern.
22+
*
23+
* See: https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
24+
*
25+
* Usage:
26+
*
27+
* ```ts
28+
* type Stuff<T> =
29+
* T extends ForceDistributive ?
30+
* // Now, operate on each member of the union separately
31+
* string extends T ? 'string' :
32+
* T
33+
* :
34+
* never
35+
* ```
36+
*/
37+
export type ForceDistributive = any

0 commit comments

Comments
 (0)