/
route-regex.ts
132 lines (114 loc) · 3.55 KB
/
route-regex.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import { escapeStringRegexp } from '../../escape-regexp'
interface Group {
pos: number
repeat: boolean
optional: boolean
}
function parseParameter(param: string) {
const optional = param.startsWith('[') && param.endsWith(']')
if (optional) {
param = param.slice(1, -1)
}
const repeat = param.startsWith('...')
if (repeat) {
param = param.slice(3)
}
return { key: param, repeat, optional }
}
export function getParametrizedRoute(route: string) {
const segments = (route.replace(/\/$/, '') || '/').slice(1).split('/')
const groups: { [groupName: string]: Group } = {}
let groupIndex = 1
const parameterizedRoute = segments
.map((segment) => {
if (segment.startsWith('[') && segment.endsWith(']')) {
const { key, optional, repeat } = parseParameter(segment.slice(1, -1))
groups[key] = { pos: groupIndex++, repeat, optional }
return repeat ? (optional ? '(?:/(.+?))?' : '/(.+?)') : '/([^/]+?)'
} else {
return `/${escapeStringRegexp(segment)}`
}
})
.join('')
// dead code eliminate for browser since it's only needed
// while generating routes-manifest
if (typeof window === 'undefined') {
let routeKeyCharCode = 97
let routeKeyCharLength = 1
// builds a minimal routeKey using only a-z and minimal number of characters
const getSafeRouteKey = () => {
let routeKey = ''
for (let i = 0; i < routeKeyCharLength; i++) {
routeKey += String.fromCharCode(routeKeyCharCode)
routeKeyCharCode++
if (routeKeyCharCode > 122) {
routeKeyCharLength++
routeKeyCharCode = 97
}
}
return routeKey
}
const routeKeys: { [named: string]: string } = {}
let namedParameterizedRoute = segments
.map((segment) => {
if (segment.startsWith('[') && segment.endsWith(']')) {
const { key, optional, repeat } = parseParameter(segment.slice(1, -1))
// replace any non-word characters since they can break
// the named regex
let cleanedKey = key.replace(/\W/g, '')
let invalidKey = false
// check if the key is still invalid and fallback to using a known
// safe key
if (cleanedKey.length === 0 || cleanedKey.length > 30) {
invalidKey = true
}
if (!isNaN(parseInt(cleanedKey.slice(0, 1)))) {
invalidKey = true
}
if (invalidKey) {
cleanedKey = getSafeRouteKey()
}
routeKeys[cleanedKey] = key
return repeat
? optional
? `(?:/(?<${cleanedKey}>.+?))?`
: `/(?<${cleanedKey}>.+?)`
: `/(?<${cleanedKey}>[^/]+?)`
} else {
return `/${escapeStringRegexp(segment)}`
}
})
.join('')
return {
parameterizedRoute,
namedParameterizedRoute,
groups,
routeKeys,
}
}
return {
parameterizedRoute,
groups,
}
}
export interface RouteRegex {
groups: { [groupName: string]: Group }
namedRegex?: string
re: RegExp
routeKeys?: { [named: string]: string }
}
export function getRouteRegex(normalizedRoute: string): RouteRegex {
const result = getParametrizedRoute(normalizedRoute)
if ('routeKeys' in result) {
return {
re: new RegExp(`^${result.parameterizedRoute}(?:/)?$`),
groups: result.groups,
routeKeys: result.routeKeys,
namedRegex: `^${result.namedParameterizedRoute}(?:/)?$`,
}
}
return {
re: new RegExp(`^${result.parameterizedRoute}(?:/)?$`),
groups: result.groups,
}
}