@react-navigation/core 7.1.1 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/StaticNavigation.js +9 -9
- package/lib/commonjs/StaticNavigation.js.map +1 -1
- package/lib/commonjs/arrayStartsWith.js +16 -0
- package/lib/commonjs/arrayStartsWith.js.map +1 -0
- package/lib/commonjs/getPathFromState.js +50 -40
- package/lib/commonjs/getPathFromState.js.map +1 -1
- package/lib/commonjs/getPatternParts.js +105 -0
- package/lib/commonjs/getPatternParts.js.map +1 -0
- package/lib/commonjs/getStateFromPath.js +106 -98
- package/lib/commonjs/getStateFromPath.js.map +1 -1
- package/lib/commonjs/types.js +2 -20
- package/lib/commonjs/types.js.map +1 -1
- package/lib/module/StaticNavigation.js +9 -9
- package/lib/module/StaticNavigation.js.map +1 -1
- package/lib/module/arrayStartsWith.js +12 -0
- package/lib/module/arrayStartsWith.js.map +1 -0
- package/lib/module/getPathFromState.js +50 -40
- package/lib/module/getPathFromState.js.map +1 -1
- package/lib/module/getPatternParts.js +101 -0
- package/lib/module/getPatternParts.js.map +1 -0
- package/lib/module/getStateFromPath.js +106 -98
- package/lib/module/getStateFromPath.js.map +1 -1
- package/lib/module/types.js +1 -20
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/commonjs/src/StaticNavigation.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/arrayStartsWith.d.ts +5 -0
- package/lib/typescript/commonjs/src/arrayStartsWith.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/getPathFromState.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/getPatternParts.d.ts +11 -0
- package/lib/typescript/commonjs/src/getPatternParts.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/getStateFromPath.d.ts.map +1 -1
- package/lib/typescript/module/src/StaticNavigation.d.ts.map +1 -1
- package/lib/typescript/module/src/arrayStartsWith.d.ts +5 -0
- package/lib/typescript/module/src/arrayStartsWith.d.ts.map +1 -0
- package/lib/typescript/module/src/getPathFromState.d.ts.map +1 -1
- package/lib/typescript/module/src/getPatternParts.d.ts +11 -0
- package/lib/typescript/module/src/getPatternParts.d.ts.map +1 -0
- package/lib/typescript/module/src/getStateFromPath.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/StaticNavigation.tsx +16 -12
- package/src/arrayStartsWith.tsx +10 -0
- package/src/getPathFromState.tsx +61 -58
- package/src/getPatternParts.tsx +126 -0
- package/src/getStateFromPath.tsx +142 -153
- package/lib/commonjs/package.json +0 -1
- package/lib/module/package.json +0 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-navigation/core",
|
|
3
3
|
"description": "Core utilities for building navigators",
|
|
4
|
-
"version": "7.
|
|
4
|
+
"version": "7.2.0",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
7
7
|
"react-native",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"clean": "del lib"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@react-navigation/routers": "^7.1.
|
|
49
|
+
"@react-navigation/routers": "^7.1.1",
|
|
50
50
|
"escape-string-regexp": "^4.0.0",
|
|
51
51
|
"nanoid": "3.3.7",
|
|
52
52
|
"query-string": "^7.1.3",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"del-cli": "^5.1.0",
|
|
64
64
|
"immer": "^10.0.3",
|
|
65
65
|
"react": "18.3.1",
|
|
66
|
-
"react-native-builder-bob": "^0.
|
|
66
|
+
"react-native-builder-bob": "^0.33.2",
|
|
67
67
|
"react-test-renderer": "18.2.0",
|
|
68
68
|
"typescript": "^5.5.2"
|
|
69
69
|
},
|
|
@@ -95,5 +95,5 @@
|
|
|
95
95
|
]
|
|
96
96
|
]
|
|
97
97
|
},
|
|
98
|
-
"gitHead": "
|
|
98
|
+
"gitHead": "19e6e5374159176c3642b60065bcc29376856180"
|
|
99
99
|
}
|
package/src/StaticNavigation.tsx
CHANGED
|
@@ -542,15 +542,27 @@ export function createPathConfigForStaticNavigation(
|
|
|
542
542
|
} else {
|
|
543
543
|
Object.assign(screenConfig, item.linking);
|
|
544
544
|
}
|
|
545
|
+
|
|
546
|
+
if (typeof screenConfig.path === 'string') {
|
|
547
|
+
// Normalize the path to remove leading and trailing slashes
|
|
548
|
+
screenConfig.path = screenConfig.path
|
|
549
|
+
.split('/')
|
|
550
|
+
.filter(Boolean)
|
|
551
|
+
.join('/');
|
|
552
|
+
}
|
|
545
553
|
}
|
|
546
554
|
|
|
547
555
|
let screens;
|
|
548
556
|
|
|
557
|
+
const skipInitialDetectionInChild =
|
|
558
|
+
skipInitialDetection ||
|
|
559
|
+
(screenConfig.path != null && screenConfig.path !== '');
|
|
560
|
+
|
|
549
561
|
if ('config' in item) {
|
|
550
562
|
screens = createPathConfigForTree(
|
|
551
563
|
item,
|
|
552
564
|
undefined,
|
|
553
|
-
|
|
565
|
+
skipInitialDetectionInChild
|
|
554
566
|
);
|
|
555
567
|
} else if (
|
|
556
568
|
'screen' in item &&
|
|
@@ -560,7 +572,7 @@ export function createPathConfigForStaticNavigation(
|
|
|
560
572
|
screens = createPathConfigForTree(
|
|
561
573
|
item.screen,
|
|
562
574
|
undefined,
|
|
563
|
-
|
|
575
|
+
skipInitialDetectionInChild
|
|
564
576
|
);
|
|
565
577
|
}
|
|
566
578
|
|
|
@@ -575,18 +587,10 @@ export function createPathConfigForStaticNavigation(
|
|
|
575
587
|
!('linking' in item && item.linking == null)
|
|
576
588
|
) {
|
|
577
589
|
if (screenConfig.path != null) {
|
|
578
|
-
if (!skipInitialDetection) {
|
|
579
|
-
// Normalize the path to remove leading and trailing slashes
|
|
580
|
-
const path = screenConfig.path
|
|
581
|
-
?.split('/')
|
|
582
|
-
.filter(Boolean)
|
|
583
|
-
.join('/');
|
|
584
|
-
|
|
590
|
+
if (!skipInitialDetection && screenConfig.path === '') {
|
|
585
591
|
// We encounter a leaf screen with empty path,
|
|
586
592
|
// Clear the initial screen config as it's not needed anymore
|
|
587
|
-
|
|
588
|
-
initialScreenConfig = undefined;
|
|
589
|
-
}
|
|
593
|
+
initialScreenConfig = undefined;
|
|
590
594
|
}
|
|
591
595
|
} else {
|
|
592
596
|
if (!skipInitialDetection && initialScreenConfig == null) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compare two arrays to check if the first array starts with the second array.
|
|
3
|
+
*/
|
|
4
|
+
export function arrayStartsWith<T>(array: T[], start: T[]) {
|
|
5
|
+
if (start.length > array.length) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return start.every((it, index) => it === array[index]);
|
|
10
|
+
}
|
package/src/getPathFromState.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
} from '@react-navigation/routers';
|
|
6
6
|
import * as queryString from 'query-string';
|
|
7
7
|
|
|
8
|
+
import { getPatternParts, type PatternPart } from './getPatternParts';
|
|
8
9
|
import type { PathConfig, PathConfigMap } from './types';
|
|
9
10
|
import { validatePathConfig } from './validatePathConfig';
|
|
10
11
|
|
|
@@ -16,10 +17,10 @@ type Options<ParamList extends {}> = {
|
|
|
16
17
|
|
|
17
18
|
type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
|
|
18
19
|
|
|
19
|
-
type StringifyConfig = Record<string, (value:
|
|
20
|
+
type StringifyConfig = Record<string, (value: unknown) => string>;
|
|
20
21
|
|
|
21
22
|
type ConfigItem = {
|
|
22
|
-
|
|
23
|
+
parts?: PatternPart[];
|
|
23
24
|
stringify?: StringifyConfig;
|
|
24
25
|
screens?: Record<string, ConfigItem>;
|
|
25
26
|
};
|
|
@@ -91,7 +92,7 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
91
92
|
): string {
|
|
92
93
|
if (state == null) {
|
|
93
94
|
throw Error(
|
|
94
|
-
|
|
95
|
+
`Got '${String(state)}' for the navigation state. You must pass a valid state object.`
|
|
95
96
|
);
|
|
96
97
|
}
|
|
97
98
|
|
|
@@ -104,7 +105,7 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
104
105
|
let path = '/';
|
|
105
106
|
let current: State | undefined = state;
|
|
106
107
|
|
|
107
|
-
const allParams: Record<string,
|
|
108
|
+
const allParams: Record<string, string> = {};
|
|
108
109
|
|
|
109
110
|
while (current) {
|
|
110
111
|
let index = typeof current.index === 'number' ? current.index : 0;
|
|
@@ -112,19 +113,20 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
112
113
|
state?: State;
|
|
113
114
|
};
|
|
114
115
|
|
|
115
|
-
let
|
|
116
|
+
let parts: PatternPart[] | undefined;
|
|
116
117
|
|
|
117
|
-
let focusedParams: Record<string,
|
|
118
|
-
const focusedRoute = getActiveRoute(state);
|
|
118
|
+
let focusedParams: Record<string, string> | undefined;
|
|
119
119
|
let currentOptions = configs;
|
|
120
120
|
|
|
121
|
+
const focusedRoute = getActiveRoute(state);
|
|
122
|
+
|
|
121
123
|
// Keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
|
|
122
124
|
const nestedRouteNames = [];
|
|
123
125
|
|
|
124
126
|
let hasNext = true;
|
|
125
127
|
|
|
126
128
|
while (route.name in currentOptions && hasNext) {
|
|
127
|
-
|
|
129
|
+
parts = currentOptions[route.name].parts;
|
|
128
130
|
|
|
129
131
|
nestedRouteNames.push(route.name);
|
|
130
132
|
|
|
@@ -138,7 +140,7 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
138
140
|
])
|
|
139
141
|
);
|
|
140
142
|
|
|
141
|
-
if (
|
|
143
|
+
if (parts?.length) {
|
|
142
144
|
Object.assign(allParams, currentParams);
|
|
143
145
|
}
|
|
144
146
|
|
|
@@ -147,17 +149,15 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
147
149
|
// We save it here since it's been stringified already
|
|
148
150
|
focusedParams = { ...currentParams };
|
|
149
151
|
|
|
150
|
-
|
|
151
|
-
?.split('/')
|
|
152
|
-
.filter((p) => p.startsWith(':'))
|
|
152
|
+
parts
|
|
153
153
|
// eslint-disable-next-line no-loop-func
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
154
|
+
?.forEach(({ param }) => {
|
|
155
|
+
if (param) {
|
|
156
|
+
// Remove the params present in the pattern since we'll only use the rest for query string
|
|
157
|
+
if (focusedParams) {
|
|
158
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
159
|
+
delete focusedParams[param];
|
|
160
|
+
}
|
|
161
161
|
}
|
|
162
162
|
});
|
|
163
163
|
}
|
|
@@ -186,28 +186,21 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
if (pattern === undefined) {
|
|
190
|
-
pattern = nestedRouteNames.join('/');
|
|
191
|
-
}
|
|
192
|
-
|
|
193
189
|
if (currentOptions[route.name] !== undefined) {
|
|
194
|
-
path +=
|
|
195
|
-
|
|
196
|
-
.map((p) => {
|
|
197
|
-
const name = getParamName(p);
|
|
198
|
-
|
|
190
|
+
path += parts
|
|
191
|
+
?.map(({ segment, param, optional }) => {
|
|
199
192
|
// We don't know what to show for wildcard patterns
|
|
200
193
|
// Showing the route name seems ok, though whatever we show here will be incorrect
|
|
201
194
|
// Since the page doesn't actually exist
|
|
202
|
-
if (
|
|
195
|
+
if (segment === '*') {
|
|
203
196
|
return route.name;
|
|
204
197
|
}
|
|
205
198
|
|
|
206
199
|
// If the path has a pattern for a param, put the param in the path
|
|
207
|
-
if (
|
|
208
|
-
const value = allParams[
|
|
200
|
+
if (param) {
|
|
201
|
+
const value = allParams[param];
|
|
209
202
|
|
|
210
|
-
if (value === undefined &&
|
|
203
|
+
if (value === undefined && optional) {
|
|
211
204
|
// Optional params without value assigned in route.params should be ignored
|
|
212
205
|
return '';
|
|
213
206
|
}
|
|
@@ -220,15 +213,20 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
220
213
|
);
|
|
221
214
|
}
|
|
222
215
|
|
|
223
|
-
return encodeURIComponent(
|
|
216
|
+
return encodeURIComponent(segment);
|
|
224
217
|
})
|
|
225
218
|
.join('/');
|
|
226
219
|
} else {
|
|
227
220
|
path += encodeURIComponent(route.name);
|
|
228
221
|
}
|
|
229
222
|
|
|
230
|
-
if (!focusedParams) {
|
|
231
|
-
focusedParams =
|
|
223
|
+
if (!focusedParams && focusedRoute.params) {
|
|
224
|
+
focusedParams = Object.fromEntries(
|
|
225
|
+
Object.entries(focusedRoute.params).map(([key, value]) => [
|
|
226
|
+
key,
|
|
227
|
+
String(value),
|
|
228
|
+
])
|
|
229
|
+
);
|
|
232
230
|
}
|
|
233
231
|
|
|
234
232
|
if (route.state) {
|
|
@@ -251,36 +249,37 @@ export function getPathFromState<ParamList extends {}>(
|
|
|
251
249
|
current = route.state;
|
|
252
250
|
}
|
|
253
251
|
|
|
252
|
+
// Include the root path if specified
|
|
253
|
+
if (options?.path) {
|
|
254
|
+
path = `${options.path}/${path}`;
|
|
255
|
+
}
|
|
256
|
+
|
|
254
257
|
// Remove multiple as well as trailing slashes
|
|
255
258
|
path = path.replace(/\/+/g, '/');
|
|
256
259
|
path = path.length > 1 ? path.replace(/\/$/, '') : path;
|
|
257
260
|
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
261
|
+
// If path doesn't start with a slash, add it
|
|
262
|
+
// This makes sure that history.pushState will update the path correctly instead of appending
|
|
263
|
+
if (!path.startsWith('/')) {
|
|
264
|
+
path = `/${path}`;
|
|
261
265
|
}
|
|
262
266
|
|
|
263
267
|
return path;
|
|
264
268
|
}
|
|
265
269
|
|
|
266
|
-
const getParamName = (pattern: string) =>
|
|
267
|
-
pattern.replace(/^:/, '').replace(/\?$/, '');
|
|
268
|
-
|
|
269
|
-
const joinPaths = (...paths: string[]): string =>
|
|
270
|
-
([] as string[])
|
|
271
|
-
.concat(...paths.map((p) => p.split('/')))
|
|
272
|
-
.filter(Boolean)
|
|
273
|
-
.join('/');
|
|
274
|
-
|
|
275
270
|
const createConfigItem = (
|
|
276
271
|
config: PathConfig<object> | string,
|
|
277
|
-
|
|
272
|
+
parentParts?: PatternPart[]
|
|
278
273
|
): ConfigItem => {
|
|
279
274
|
if (typeof config === 'string') {
|
|
280
275
|
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
|
|
281
|
-
const
|
|
276
|
+
const parts = getPatternParts(config);
|
|
277
|
+
|
|
278
|
+
if (parentParts) {
|
|
279
|
+
return { parts: [...parentParts, ...parts] };
|
|
280
|
+
}
|
|
282
281
|
|
|
283
|
-
return {
|
|
282
|
+
return { parts };
|
|
284
283
|
}
|
|
285
284
|
|
|
286
285
|
if (config.exact && config.path === undefined) {
|
|
@@ -291,18 +290,22 @@ const createConfigItem = (
|
|
|
291
290
|
|
|
292
291
|
// If an object is specified as the value (e.g. Foo: { ... }),
|
|
293
292
|
// It can have `path` property and `screens` prop which has nested configs
|
|
294
|
-
const
|
|
293
|
+
const parts =
|
|
295
294
|
config.exact !== true
|
|
296
|
-
?
|
|
297
|
-
|
|
295
|
+
? [
|
|
296
|
+
...(parentParts || []),
|
|
297
|
+
...(config.path ? getPatternParts(config.path) : []),
|
|
298
|
+
]
|
|
299
|
+
: config.path
|
|
300
|
+
? getPatternParts(config.path)
|
|
301
|
+
: undefined;
|
|
298
302
|
|
|
299
303
|
const screens = config.screens
|
|
300
|
-
? createNormalizedConfigs(config.screens,
|
|
304
|
+
? createNormalizedConfigs(config.screens, parts)
|
|
301
305
|
: undefined;
|
|
302
306
|
|
|
303
307
|
return {
|
|
304
|
-
|
|
305
|
-
pattern: pattern?.split('/').filter(Boolean).join('/'),
|
|
308
|
+
parts,
|
|
306
309
|
stringify: config.stringify,
|
|
307
310
|
screens,
|
|
308
311
|
};
|
|
@@ -310,11 +313,11 @@ const createConfigItem = (
|
|
|
310
313
|
|
|
311
314
|
const createNormalizedConfigs = (
|
|
312
315
|
options: PathConfigMap<object>,
|
|
313
|
-
|
|
316
|
+
parts?: PatternPart[]
|
|
314
317
|
): Record<string, ConfigItem> =>
|
|
315
318
|
Object.fromEntries(
|
|
316
319
|
Object.entries(options).map(([name, c]) => {
|
|
317
|
-
const result = createConfigItem(c,
|
|
320
|
+
const result = createConfigItem(c, parts);
|
|
318
321
|
|
|
319
322
|
return [name, result];
|
|
320
323
|
})
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
export type PatternPart = {
|
|
2
|
+
segment: string;
|
|
3
|
+
param?: string;
|
|
4
|
+
regex?: string;
|
|
5
|
+
optional?: boolean;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parse a path into an array of parts with information about each segment.
|
|
10
|
+
*/
|
|
11
|
+
export function getPatternParts(path: string): PatternPart[] {
|
|
12
|
+
const parts: PatternPart[] = [];
|
|
13
|
+
|
|
14
|
+
let current: PatternPart = { segment: '' };
|
|
15
|
+
|
|
16
|
+
let isRegex = false;
|
|
17
|
+
let isParam = false;
|
|
18
|
+
let regexInnerParens = 0;
|
|
19
|
+
|
|
20
|
+
// One extra iteration to add the last character
|
|
21
|
+
for (let i = 0; i <= path.length; i++) {
|
|
22
|
+
const char = path[i];
|
|
23
|
+
|
|
24
|
+
if (char != null) {
|
|
25
|
+
current.segment += char;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (char === ':') {
|
|
29
|
+
// The segment must start with a colon if it's a param
|
|
30
|
+
if (current.segment === ':') {
|
|
31
|
+
isParam = true;
|
|
32
|
+
} else {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Encountered ':' in the middle of a segment in path: ${path}`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
} else if (char === '(') {
|
|
38
|
+
if (isParam) {
|
|
39
|
+
if (isRegex) {
|
|
40
|
+
// The '(' is part of the regex if we're already inside one
|
|
41
|
+
regexInnerParens++;
|
|
42
|
+
} else {
|
|
43
|
+
isRegex = true;
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Encountered '(' without preceding ':' in path: ${path}`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
} else if (char === ')') {
|
|
51
|
+
if (isParam && isRegex) {
|
|
52
|
+
if (regexInnerParens) {
|
|
53
|
+
// The ')' is part of the regex if we're already inside one
|
|
54
|
+
regexInnerParens--;
|
|
55
|
+
current.regex += char;
|
|
56
|
+
} else {
|
|
57
|
+
isRegex = false;
|
|
58
|
+
isParam = false;
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Encountered ')' without preceding '(' in path: ${path}`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
} else if (char === '?') {
|
|
66
|
+
if (current.param) {
|
|
67
|
+
isParam = false;
|
|
68
|
+
|
|
69
|
+
current.optional = true;
|
|
70
|
+
} else {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Encountered '?' without preceding ':' in path: ${path}`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
} else if (char == null || (char === '/' && !isRegex)) {
|
|
76
|
+
isParam = false;
|
|
77
|
+
|
|
78
|
+
// Remove trailing slash from segment
|
|
79
|
+
current.segment = current.segment.replace(/\/$/, '');
|
|
80
|
+
|
|
81
|
+
if (current.segment === '') {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (current.param) {
|
|
86
|
+
current.param = current.param.replace(/^:/, '');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (current.regex) {
|
|
90
|
+
current.regex = current.regex.replace(/^\(/, '').replace(/\)$/, '');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
parts.push(current);
|
|
94
|
+
|
|
95
|
+
if (char == null) {
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
current = { segment: '' };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (isRegex) {
|
|
103
|
+
current.regex = current.regex || '';
|
|
104
|
+
current.regex += char;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (isParam && !isRegex) {
|
|
108
|
+
current.param = current.param || '';
|
|
109
|
+
current.param += char;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (isRegex) {
|
|
114
|
+
throw new Error(`Could not find closing ')' in path: ${path}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const params = parts.map((part) => part.param).filter(Boolean);
|
|
118
|
+
|
|
119
|
+
for (const [index, param] of params.entries()) {
|
|
120
|
+
if (params.indexOf(param) !== index) {
|
|
121
|
+
throw new Error(`Duplicate param name '${param}' found in path: ${path}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return parts;
|
|
126
|
+
}
|