Skip to content

Commit d6150f5

Browse files
committed
Add pathToRegexp method back
1 parent a43e545 commit d6150f5

File tree

4 files changed

+101
-98
lines changed

4 files changed

+101
-98
lines changed

Readme.md

+16-9
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,7 @@ npm install path-to-regexp --save
1717
## Usage
1818

1919
```js
20-
const { match, compile, parse } = require("path-to-regexp");
21-
22-
// match(path, options?)
23-
// compile(path, options?)
24-
// parse(path, options?)
20+
const { match, pathToRegexp, compile, parse } = require("path-to-regexp");
2521
```
2622

2723
### Parameters
@@ -64,20 +60,31 @@ fn("/users/123/delete");
6460

6561
The `match` function returns a function for matching strings against a path:
6662

63+
- **path** String or array of strings.
64+
- **options** _(optional)_ (Extends [pathToRegexp](#pathToRegexp) options)
65+
- **decode** Function for decoding strings to params, or `false` to disable all processing. (default: `decodeURIComponent`)
66+
67+
```js
68+
const fn = match("/foo/:bar");
69+
```
70+
71+
**Please note:** `path-to-regexp` is intended for ordered data (e.g. paths, hosts). It can not handle arbitrarily ordered data (e.g. query strings, URL fragments, JSON, etc).
72+
73+
## PathToRegexp
74+
75+
The `pathToRegexp` function returns a regular expression for matching strings against paths. It
76+
6777
- **path** String or array of strings.
6878
- **options** _(optional)_ (See [parse](#parse) for more options)
6979
- **sensitive** Regexp will be case sensitive. (default: `false`)
7080
- **end** Validate the match reaches the end of the string. (default: `true`)
7181
- **delimiter** The default delimiter for segments, e.g. `[^/]` for `:named` parameters. (default: `'/'`)
7282
- **trailing** Allows optional trailing delimiter to match. (default: `true`)
73-
- **decode** Function for decoding strings to params, or `false` to disable all processing. (default: `decodeURIComponent`)
7483

7584
```js
76-
const fn = match("/foo/:bar");
85+
const { regexp, keys } = pathToRegexp("/foo/:bar");
7786
```
7887

79-
**Please note:** `path-to-regexp` is intended for ordered data (e.g. pathnames, hostnames). It can not handle arbitrarily ordered data (e.g. query strings, URL fragments, JSON, etc).
80-
8188
## Compile ("Reverse" Path-To-RegExp)
8289

8390
The `compile` function will return a function for transforming parameters into a valid path:

scripts/redos.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { checkSync } from "recheck";
2-
import { match } from "../src/index.js";
2+
import { pathToRegexp } from "../src/index.js";
33
import { MATCH_TESTS } from "../src/cases.spec.js";
44

55
let safe = 0;
@@ -8,14 +8,14 @@ let fail = 0;
88
const TESTS = MATCH_TESTS.map((x) => x.path);
99

1010
for (const path of TESTS) {
11-
const { re } = match(path) as any;
12-
const result = checkSync(re.source, re.flags);
11+
const { regexp } = pathToRegexp(path);
12+
const result = checkSync(regexp.source, regexp.flags);
1313
if (result.status === "safe") {
1414
safe++;
15-
console.log("Safe:", path, String(re));
15+
console.log("Safe:", path, String(regexp));
1616
} else {
1717
fail++;
18-
console.log("Fail:", path, String(re));
18+
console.log("Fail:", path, String(regexp));
1919
}
2020
}
2121

src/cases.spec.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,10 @@ export const MATCH_TESTS: MatchTestSet[] = [
302302
input: "/test/",
303303
expected: { path: "/test/", params: {} },
304304
},
305+
{
306+
input: "/TEST/",
307+
expected: { path: "/TEST/", params: {} },
308+
},
305309
],
306310
},
307311
{
@@ -394,11 +398,11 @@ export const MATCH_TESTS: MatchTestSet[] = [
394398
sensitive: true,
395399
},
396400
tests: [
401+
{ input: "/test", expected: false },
397402
{
398403
input: "/TEST",
399404
expected: { path: "/TEST", params: {} },
400405
},
401-
{ input: "/test", expected: false },
402406
],
403407
},
404408

src/index.ts

+75-83
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,7 @@ export interface ParseOptions {
2121
encodePath?: Encode;
2222
}
2323

24-
export interface MatchOptions {
25-
/**
26-
* Function for decoding strings for params, or `false` to disable entirely. (default: `decodeURIComponent`)
27-
*/
28-
decode?: Decode | false;
24+
export interface PathToRegexpOptions {
2925
/**
3026
* Matches the path completely without trailing characters. (default: `true`)
3127
*/
@@ -44,6 +40,13 @@ export interface MatchOptions {
4440
delimiter?: string;
4541
}
4642

43+
export interface MatchOptions extends PathToRegexpOptions {
44+
/**
45+
* Function for decoding strings for params, or `false` to disable entirely. (default: `decodeURIComponent`)
46+
*/
47+
decode?: Decode | false;
48+
}
49+
4750
export interface CompileOptions {
4851
/**
4952
* Function for encoding input strings for output into the path, or `false` to disable entirely. (default: `encodeURIComponent`)
@@ -109,13 +112,6 @@ function escape(str: string) {
109112
return str.replace(/[.+*?^${}()[\]|/\\]/g, "\\$&");
110113
}
111114

112-
/**
113-
* Get the flags for a regexp from the options.
114-
*/
115-
function toFlags(options: { sensitive?: boolean }) {
116-
return options.sensitive ? "s" : "is";
117-
}
118-
119115
/**
120116
* Tokenize input string.
121117
*/
@@ -253,6 +249,16 @@ export interface Group {
253249
tokens: Token[];
254250
}
255251

252+
/**
253+
* A token that corresponds with a regexp capture.
254+
*/
255+
export type Key = Parameter | Wildcard;
256+
257+
/**
258+
* A sequence of `path-to-regexp` keys that match capturing groups.
259+
*/
260+
export type Keys = Array<Key>;
261+
256262
/**
257263
* A sequence of path match characters.
258264
*/
@@ -316,14 +322,15 @@ export function parse(str: string, options: ParseOptions = {}): TokenData {
316322
}
317323

318324
/**
319-
* Transform tokens into a path building function.
325+
* Compile a string to a template function for the path.
320326
*/
321-
function $compile<P extends ParamData>(
322-
data: TokenData,
323-
options: CompileOptions,
324-
): PathFunction<P> {
327+
export function compile<P extends ParamData = ParamData>(
328+
path: Path,
329+
options: CompileOptions & ParseOptions = {},
330+
) {
325331
const { encode = encodeURIComponent, delimiter = DEFAULT_DELIMITER } =
326332
options;
333+
const data = path instanceof TokenData ? path : parse(path, options);
327334
const fn = tokensToFunction(data.tokens, delimiter, encode);
328335

329336
return function path(data: P = {} as P) {
@@ -335,19 +342,6 @@ function $compile<P extends ParamData>(
335342
};
336343
}
337344

338-
/**
339-
* Compile a string to a template function for the path.
340-
*/
341-
export function compile<P extends ParamData = ParamData>(
342-
path: Path,
343-
options: CompileOptions & ParseOptions = {},
344-
) {
345-
return $compile<P>(
346-
path instanceof TokenData ? path : parse(path, options),
347-
options,
348-
);
349-
}
350-
351345
export type ParamData = Partial<Record<string, string | string[]>>;
352346
export type PathFunction<P extends ParamData> = (data?: P) => string;
353347

@@ -451,75 +445,77 @@ export type Match<P extends ParamData> = false | MatchResult<P>;
451445
export type MatchFunction<P extends ParamData> = (path: string) => Match<P>;
452446

453447
/**
454-
* Create path match function from `path-to-regexp` spec.
448+
* Supported path types.
455449
*/
456-
function $match<P extends ParamData>(
457-
data: TokenData[],
458-
options: MatchOptions = {},
459-
): MatchFunction<P> {
460-
const {
461-
decode = decodeURIComponent,
462-
delimiter = DEFAULT_DELIMITER,
463-
end = true,
464-
trailing = true,
465-
} = options;
466-
const flags = toFlags(options);
467-
const sources: string[] = [];
468-
const keys: Array<Parameter | Wildcard> = [];
469-
470-
for (const { tokens } of data) {
471-
for (const seq of flatten(tokens, 0, [])) {
472-
const regexp = sequenceToRegExp(seq, delimiter, keys);
473-
sources.push(regexp);
474-
}
475-
}
476-
477-
let pattern = `^(?:${sources.join("|")})`;
478-
if (trailing) pattern += `(?:${escape(delimiter)}$)?`;
479-
pattern += end ? "$" : `(?=${escape(delimiter)}|$)`;
450+
export type Path = string | TokenData;
480451

481-
const re = new RegExp(pattern, flags);
452+
/**
453+
* Transform a path into a match function.
454+
*/
455+
export function match<P extends ParamData>(
456+
path: Path | Path[],
457+
options: MatchOptions & ParseOptions = {},
458+
): MatchFunction<P> {
459+
const { decode = decodeURIComponent, delimiter = DEFAULT_DELIMITER } =
460+
options;
461+
const { regexp, keys } = pathToRegexp(path, options);
482462

483463
const decoders = keys.map((key) => {
484464
if (decode === false) return NOOP_VALUE;
485465
if (key.type === "param") return decode;
486466
return (value: string) => value.split(delimiter).map(decode);
487467
});
488468

489-
return Object.assign(
490-
function match(input: string) {
491-
const m = re.exec(input);
492-
if (!m) return false;
469+
return function match(input: string) {
470+
const m = regexp.exec(input);
471+
if (!m) return false;
493472

494-
const { 0: path } = m;
495-
const params = Object.create(null);
473+
const path = m[0];
474+
const params = Object.create(null);
496475

497-
for (let i = 1; i < m.length; i++) {
498-
if (m[i] === undefined) continue;
476+
for (let i = 1; i < m.length; i++) {
477+
if (m[i] === undefined) continue;
499478

500-
const key = keys[i - 1];
501-
const decoder = decoders[i - 1];
502-
params[key.name] = decoder(m[i]);
503-
}
479+
const key = keys[i - 1];
480+
const decoder = decoders[i - 1];
481+
params[key.name] = decoder(m[i]);
482+
}
504483

505-
return { path, params };
506-
},
507-
{ re },
508-
);
484+
return { path, params };
485+
};
509486
}
510487

511-
export type Path = string | TokenData;
512-
513-
export function match<P extends ParamData>(
488+
export function pathToRegexp(
514489
path: Path | Path[],
515-
options: MatchOptions & ParseOptions = {},
516-
): MatchFunction<P> {
490+
options: PathToRegexpOptions & ParseOptions = {},
491+
) {
492+
const {
493+
delimiter = DEFAULT_DELIMITER,
494+
end = true,
495+
sensitive = false,
496+
trailing = true,
497+
} = options;
498+
const keys: Keys = [];
499+
const sources: string[] = [];
500+
const flags = sensitive ? "s" : "is";
517501
const paths = Array.isArray(path) ? path : [path];
518502
const items = paths.map((path) =>
519503
path instanceof TokenData ? path : parse(path, options),
520504
);
521505

522-
return $match(items, options);
506+
for (const { tokens } of items) {
507+
for (const seq of flatten(tokens, 0, [])) {
508+
const regexp = sequenceToRegExp(seq, delimiter, keys);
509+
sources.push(regexp);
510+
}
511+
}
512+
513+
let pattern = `^(?:${sources.join("|")})`;
514+
if (trailing) pattern += `(?:${escape(delimiter)}$)?`;
515+
pattern += end ? "$" : `(?=${escape(delimiter)}|$)`;
516+
517+
const regexp = new RegExp(pattern, flags);
518+
return { regexp, keys };
523519
}
524520

525521
/**
@@ -556,11 +552,7 @@ function* flatten(
556552
/**
557553
* Transform a flat sequence of tokens into a regular expression.
558554
*/
559-
function sequenceToRegExp(
560-
tokens: Flattened[],
561-
delimiter: string,
562-
keys: Array<Parameter | Wildcard>,
563-
): string {
555+
function sequenceToRegExp(tokens: Flattened[], delimiter: string, keys: Keys) {
564556
let result = "";
565557
let backtrack = "";
566558
let isSafeSegmentParam = true;

0 commit comments

Comments
 (0)