8000 Refactor params to match up to next token · pillarjs/path-to-regexp@208cc83 · GitHub
[go: up one dir, main page]

Skip to content
Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 208cc83

Browse files
committed
Refactor params to match up to next token
1 parent cbf2c73 commit 208cc83

File tree

5 files changed

+584
-1424
lines changed

5 files changed

+584
-1424
lines changed

Readme.md

Lines changed: 94 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -17,75 +17,64 @@ npm install path-to-regexp --save
1717
## Usage
1818

1919
```js
20-
const { pathToRegexp, match, parse, compile } = require("path-to-regexp");
20+
const { match, compile, parse } = require("path-to-regexp");
2121

22-
// pathToRegexp(path, options?)
2322
// match(path, options?)
24-
// parse(path, options?)
2523
// compile(path, options?)
24+
// parse(path, options?)
2625
```
2726

28-
### Path to regexp
27+
### Match
2928

30-
The `pathToRegexp` function returns a regular expression with `keys` as a property. It accepts the following arguments:
29+
The `match` function returns a function for transforming paths into parameters:
3130

3231
- **path** A string.
33-
- **options** _(optional)_
32+
- **options** _(optional)_ (See [parse](#parse) for more options)
3433
- **sensitive** Regexp will be case sensitive. (default: `false`)
35-
- **trailing** Allows optional trailing delimiter to match. (default: `true`)
36-
- **strict** Verify patterns are valid and safe to use. (default: `false`, recommended: `true`)
37-
- **end** Match to the end of the string. (default: `true`)
38-
- **start** Match from the beginning of the string. (default: `true`)
39-
- **loose** Allow the delimiter to be arbitrarily repeated, e.g. `/` or `///`. (default: `true`)
40-
- **delimiter** The default delimiter for segments, e.g. `[^/]` for `:named` parameters. (default: `'/'`)
41-
- **encodePath** A function for encoding input strings. (default: `x => x`, recommended: [`encodeurl`](https://github.com/pillarjs/encodeurl) for unicode encoding)
34+
- **end** Validate the match reaches the end of the string. (default: `true`)
35+
- **decode** Function for decoding strings to params, or `false` to disable all processing. (default: `decodeURIComponent`)
4236

4337
```js
44-
const regexp = pathToRegexp("/foo/:bar");
45-
// regexp = /^\/+foo(?:\/+([^\/]+?))(?:\/+)?$/i
46-
// keys = [{ name: 'bar', prefix: '', suffix: '', pattern: '', modifier: '' }]
38+
const fn = match("/foo/:bar");
4739
```
4840

49-
**Please note:** The `RegExp` returned by `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).
41+
**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).
5042

5143
### Parameters
5244

53-
The path argument is used to define parameters and populate keys.
45+
Parameters match arbitrary strings in a path by matching up to the end of the segment, or up to any proceeding tokens.
5446

5547
#### Named parameters
5648

57-
Named parameters are defined by prefixing a colon to the parameter name (`:foo`). Parameter names can use any valid unicode identifier characters (similar to JavaScript).
49+
Named parameters are defined by prefixing a colon to the parameter name (`:foo`). Parameter names can use any valid unicode identifier characters, similar to JavaScript.
5850

5951
```js
60-
const regexp = pathToRegexp("/:foo/:bar");
61-
// keys = [{ name: 'foo', ... }, { name: 'bar', ... }]
52+
const fn = match("/:foo/:bar");
6253

63-
regexp.exec("/test/route");
64-
//=> [ '/test/route', 'test', 'route', index: 0 ]
54+
fn("/test/route");
55+
//=> { path: '/test/route', params: { foo: 'test', bar: 'route' } }
6556
```
6657

6758
##### Custom matching parameters
6859

6960
Parameters can have a custom regexp, which overrides the default match (`[^/]+`). For example, you can match digits or names in a path:
7061

7162
```js
72-
const regexpNumbers = pathToRegexp("/icon-:foo(\\d+).png");
73-
// keys = [{ name: 'foo', ... }]
63+
const exampleNumbers = match("/icon-:foo(\\d+).png");
7464

75-
regexpNumbers.exec("/icon-123.png");
76-
//=> ['/icon-123.png', '123']
65+
exampleNumbers("/icon-123.png");
66+
//=> { path: '/icon-123.png', params: { foo: '123' } }
7767

78-
regexpNumbers.exec("/icon-abc.png");
79-
//=> null
68+
exampleNumbers("/icon-abc.png");
69+
//=> false
8070

81-
const regexpWord = pathToRegexp("/(user|u)");
82-
// keys = [{ name: 0, ... }]
71+
const exampleWord = pathToRegexp("/(user|u)");
8372

84-
regexpWord.exec("/u");
85-
//=> ['/u', 'u']
73+
exampleWord("/u");
74+
//=> { path: '/u', params: { '0': 'u' } }
8675

87-
regexpWord.exec("/users");
88-
//=> null
76+
exampleWord("/users");
77+
//=> false
8978
```
9079

9180
**Tip:** Backslashes need to be escaped with another backslash in JavaScript strings.
@@ -95,25 +84,24 @@ regexpWord.exec("/users");
9584
It is possible to define a parameter without a name. The name will be numerically indexed:
9685

9786
```js
98-
const regexp = pathToRegexp("/:foo/(.*)");
99-
// keys = [{ name: 'foo', ... }, { name: '0', ... }]
87+
const fn = match("/:foo/(.*)");
10088

101-
regexp.exec("/test/route");
102-
//=> [ '/test/route', 'test', 'route', index: 0 ]
89+
fn("/test/route");
90+
//=> { path: '/test/route', params: { '0': 'route', foo: 'test' } }
10391
```
10492

105-
##### Custom prefix and suffix
93+
#### Custom prefix and suffix
10694

10795
Parameters can be wrapped in `{}` to create custom prefixes or suffixes for your segment:
10896

10997
```js
110-
const regexp = pathToRegexp("{/:attr1}?{-:attr2}?{-:attr3}?");
98+
const fn = match("{/:attr1}?{-:attr2}?{-:attr3}?");
11199

112-
regexp.exec("/test");
113-
// => ['/test', 'test', undefined, undefined]
100+
fn("/test");
101+
//=> { path: '/test', params: { attr1: 'test' } }
114102

115-
regexp.exec("/test-test");
116-
// => ['/test', 'test', 'test', undefined]
103+
fn("/test-test");
104+
//=> { path: '/test-test', params: { attr1: 'test', attr2: 'test' } }
117105
```
118106

119107
#### Modifiers
@@ -125,99 +113,78 @@ Modifiers are used after parameters with custom prefixes and suffixes (`{}`).
125113
Parameters can be suffixed with a question mark (`?`) to make the parameter optional.
126114

127115
```js
128-
const regexp = pathToRegexp("/:foo{/:bar}?");
129-
// keys = [{ name: 'foo', ... }, { name: 'bar', prefix: '/', modifier: '?' }]
116+
const fn = match("/:foo{/:bar}?");
130117

131-
regexp.exec("/test");
132-
//=> [ '/test', 'test', undefined, index: 0 ]
118+
fn("/test");
119+
//=> { path: '/test', params: { foo: 'test' } }
133120

134-
regexp.exec("/test/route");
135-
//=> [ '/test/route', 'test', 'route', index: 0 ]
121+
fn("/test/route");
122+
//=> { path: '/test/route', params: { foo: 'test', bar: 'route' } }
136123
```
137124

138125
##### Zero or more
139126

140127
Parameters can be suffixed with an asterisk (`*`) to denote a zero or more parameter matches.
141128

142129
```js
143-
const regexp = pathToRegexp("{/:foo}*");
144-
// keys = [{ name: 'foo', prefix: '/', modifier: '*' }]
130+
const fn = match("{/:foo}*");
145131

146-
regexp.exec("/foo");
147-
//=> [ '/foo', "foo", index: 0 ]
132+
fn("/foo");
133+
//=> { path: '/foo', params: { foo: [ 'foo' ] } }
148134

149-
regexp.exec("/bar/baz");
150-
//=> [ '/bar/baz', 'bar/baz', index: 0 ]
135+
fn("/bar/baz");
136+
//=> { path: '/bar/baz', params: { foo: [ 'bar', 'baz' ] } }
151137
```
152138

153139
##### One or more
154140

155141
Parameters can be suffixed with a plus sign (`+`) to denote a one or more parameter matches.
156142

157143
```js
158-
const regexp = pathToRegexp("{/:foo}+");
159-
// keys = [{ name: 'foo', prefix: '/', modifier: '+' }]
144+
const fn = match("{/:foo}+");
160145

161-
regexp.exec("/");
162-
//=> null
146+
fn("/");
147+
//=> false
163148

164-
regexp.exec("/bar/baz");
165-
//=> [ '/bar/baz', 'bar/baz', index: 0 ]
149+
fn("/bar/baz");
150+
//=> { path: '/bar/baz', params: { foo: [ 'bar', 'baz' ] } }
166151
```
167152

168153
##### Custom separator
169154

170155
By default, parameters set the separator as the `prefix + suffix` of the token. Using `;` you can modify this:
171156

172157
```js
173-
const regexp = pathToRegexp("/name{/:parts;-}+");
158+
const fn = match("/name{/:parts;-}+");
174159

175-
regexp.exec("/name");
176-
//=> null
160+
fn("/name");
161+
//=> false
177162

178-
regexp.exec("/bar/1-2-3");
179-
//=> [ '/name/1-2-3', '1-2-3', index: 0 ]
163+
fn("/bar/1-2-3");
164+
//=> { path: '/name/1-2-3', params: { parts: [ '1', '2', '3' ] } }
180165
```
181166

182167
#### Wildcard
183168

184-
A wildcard can also be used. It is roughly equivalent to `(.*)`.
169+
A wildcard is also supported. It is roughly equivalent to `(.*)`.
185170

186171
```js
187-
const regexp = pathToRegexp("/*");
188-
// keys = [{ name: '0', pattern: '[^\\/]*', separator: '/', modifier: '*' }]
189-
190-
regexp.exec("/");
191-
//=> [ '/', '', index: 0 ]
192-
193-
regexp.exec("/bar/baz");
194-
//=> [ '/bar/baz', 'bar/baz', index: 0 ]
195-
```
172+
const fn = match("/*");
196173

197-
### Match
174+
fn("/");
175+
//=> { path: '/', params: {} }
198176

199-
The `match` function returns a function for transforming paths into parameters:
200-
201-
- **path** A string.
202-
- **options** _(optional)_ The same options as `pathToRegexp`, plus:
203-
- **decode** Function for decoding strings for params, or `false` to disable entirely. (default: `decodeURIComponent`)
204-
205-
```js
206-
const fn = match("/user/:id");
207-
208-
fn("/user/123"); //=> { path: '/user/123', index: 0, params: { id: '123' } }
209-
fn("/invalid"); //=> false
210-
fn("/user/caf%C3%A9"); //=> { path: '/user/caf%C3%A9', index: 0, params: { id: 'café' } }
177+
fn("/bar/baz");
178+
//=> { path: '/bar/baz', params: { '0': [ 'bar', 'baz' ] } }
211179
```
212180

213-
**Note:** Setting `decode: false` disables the "splitting" behavior of repeated parameters, which is useful if you need the exactly matched parameter back.
214-
215181
### Compile ("Reverse" Path-To-RegExp)
216182

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

219185
- **path** A string.
220-
- **options** _(optional)_ Similar to `pathToRegexp` (`delimiter`, `encodePath`, `sensitive`, and `loose`), plus:
186+
- **options** (See [parse](#parse) for more options)
187+
- **sensitive** Regexp will be case sensitive. (default: `false`)
221188
- **validate** When `false` the function can produce an invalid (unmatched) path. (default: `true`)
222189
- **encode** Function for encoding input strings for output into the path, or `false` to disable entirely. (default: `encodeURIComponent`)
223190

@@ -245,14 +212,17 @@ toPathRegexp({ id: "123" }); //=> "/user/123"
245212

246213
## Developers
247214

248-
- If you are rewriting paths with match and compiler, consider using `encode: false` and `decode: false` to keep raw paths passed around.
215+
- If you are rewriting paths with match and compile, consider using `encode: false` and `decode: false` to keep raw paths passed around.
249216
- To ensure matches work on paths containing characters usually encoded, consider using [encodeurl](https://github.com/pillarjs/encodeurl) for `encodePath`.
250-
- If matches are intended to be exact, you need to set `loose: false`, `trailing: false`, and `sensitive: true`.
251-
- Enable `strict: true` to detect ReDOS issues.
252217

253218
### Parse
254219

255-
A `parse` function is available and returns `TokenData`, the set of tokens and other metadata parsed from the input string. `TokenData` is can passed directly into `pathToRegexp`, `match`, and `compile`. It accepts only two options, `delimiter` and `encodePath`, which makes those options redundant in the above methods.
220+
The `parse` function accepts a string and returns `TokenData`, the set of tokens and other metadata parsed from the input string. `TokenData` is can used with `$match` and `$compile`.
221+
222+
- **path** A string.
223+
- **options** _(optional)_
224+
- **delimiter** The default delimiter for segments, e.g. `[^/]` for `:named` parameters. (default: `'/'`)
225+
- **encodePath** A function for encoding input strings. (default: `x => x`, recommended: [`encodeurl`](https://github.com/pillarjs/encodeurl) for unicode encoding)
256226

257227
### Tokens
258228

@@ -267,14 +237,14 @@ The `tokens` returned by `TokenData` is an array of strings or keys, represented
267237

268238
### Custom path
269239

270-
In some applications, you may not be able to use the `path-to-regexp` syntax (e.g. file-based routing), but you can still use this library for `match`, `compile`, and `pathToRegexp` by building your own `TokenData` instance. For example:
240+
In some applications, you may not be able to use the `path-to-regexp` syntax, but still want to use this library for `match` and `compile`. For example:
271241

272242
```js
273243
import { TokenData, match } from "path-to-regexp";
274244

275245
const tokens = ["/", { name: "foo" }];
276246
const path = new TokenData(tokens, "/");
277-
const fn = match(path);
247+
const fn = $match(path);
278248

279249
fn("/test"); //=> { path: '/test', index: 0, params: { foo: 'test' } }
280250
```
@@ -299,6 +269,30 @@ Used as a [custom separator](#custom-separator) for repeated parameters.
299269

300270
These characters have been reserved for future use.
301271

272+
### Missing separator
273+
274+
Repeated parameters must have a separator to be valid. For example, `{:foo}*` can't be used. Separators can be defined manually, such as `{:foo;/}*`, or they default to the suffix and prefix with the parameter, such as `{/:foo}*`.
275+
276+
### Missing parameter name
277+
278+
Parameter names, the part after `:`, must be a valid JavaScript identifier. For example, it cannot start with a number or dash. If you want a parameter name that uses these characters you can wrap the name in quotes, e.g. `:"my-name"`.
279+
280+
### Unterminated quote
281+
282+
Parameter names can be wrapped in double quote characters, and this error means you forgot to close the quote character.
283+
284+
### Pattern cannot start with "?"
285+
286+
Parameters in `path-to-regexp` must be basic groups. However, you can use features that require the `?` nested within the pattern. For example, `:foo((?!login)[^/]+)` is valid, but `:foo(?!login)` is not.
287+
288+
### Capturing groups are not allowed
289+
290+
A parameter pattern can not contain nested capturing groups.
291+
292+
### Unbalanced or missing pattern
293+
294+
A parameter pattern must have the expected number of parentheses. An unbalanced amount, such as `((?!login)` implies something has been written that is invalid. Check you didn't forget any parentheses.
295+
302296
### Express <= 4.x
303297

304298
Path-To-RegExp breaks compatibility with Express <= `4.x` in the following ways:

scripts/redos.ts

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,28 @@
11
import { checkSync } from "recheck";
2-
import { pathToRegexp } from "../src/index.js";
2+
import { match } from "../src/index.js";
3+
import { MATCH_TESTS } from "../src/cases.spec.js";
34

4-
const TESTS = [
5-
"/abc{abc:foo}?",
6-
"/:foo{abc:foo}?",
7-
"{:attr1}?{:attr2/}?",
8-
"{:attr1/}?{:attr2/}?",
9-
"{:foo.}?{:bar.}?",
10-
"{:foo([^\\.]+).}?{:bar.}?",
11-
":foo(a+):bar(b+)",
12-
];
5+
let safe = 0;
6+
let fail = 0;
7+
8+
const TESTS = new Set(MATCH_TESTS.map((test) => test.path));
9+
// const TESTS = [
10+
// ":path([^\\.]+).:ext",
11+
// ":path.:ext(\\w+)",
12+
// ":path{.:ext([^\\.]+)}",
13+
// "/:path.:ext(\\\\w+)",
14+
// ];
1315

1416
for (const path of TESTS) {
15-
try {
16-
const re = pathToRegexp(path, { strict: true });
17-
const result = checkSync(re.source, re.flags);
18-
if (result.status === "safe") {
19-
console.log("Safe:", path, String(re));
20-
} else {
21-
console.log("Fail:", path, String(re));
22-
}
23-
} catch (err) {
24-
try {
25-
const re = pathToRegexp(path);
26-
const result = checkSync(re.source, re.flags);
27-
if (result.status === "safe") {
28-
console.log("Invalid:", path, String(re));
29-
} else {
30-
console.log("Pass:", path, String(re));
31-
}
32-
} catch (err) {
33-
console.log("Error:", path, err.message);
34-
}
17+
const { re } = match(path);
18+
const result = checkSync(re.source, re.flags);
19+
if (result.status === "safe") {
20+
safe++;
21+
console.log("Safe:", path, String(re));
22+
} else {
23+
fail++;
24+
console.log("Fail:", path, String(re));
3525
}
3626
}
27+
28+
console.log("Safe:", safe, "Fail:", fail);

0 commit comments

Comments
 (0)
0