path-to-regexp
Advanced tools
Comparing version 7.0.0 to 7.1.0
@@ -11,7 +11,7 @@ /** | ||
/** | ||
* Set the default delimiter for repeat parameters. (default: `'/'`) | ||
* The default delimiter for segments. (default: `'/'`) | ||
*/ | ||
delimiter?: string; | ||
/** | ||
* Function for encoding input strings for output into path. | ||
* A function for encoding input strings. | ||
*/ | ||
@@ -22,20 +22,24 @@ encodePath?: Encode; | ||
/** | ||
* When `true` the regexp will be case sensitive. (default: `false`) | ||
* Regexp will be case sensitive. (default: `false`) | ||
*/ | ||
sensitive?: boolean; | ||
/** | ||
* Allow delimiter to be arbitrarily repeated. (default: `true`) | ||
* Allow the delimiter to be arbitrarily repeated. (default: `true`) | ||
*/ | ||
loose?: boolean; | ||
/** | ||
* When `true` the regexp will match to the end of the string. (default: `true`) | ||
* Verify patterns are valid and safe to use. (default: `false`) | ||
*/ | ||
end?: boolean; | ||
strict?: boolean; | ||
/** | ||
* When `true` the regexp will match from the beginning of the string. (default: `true`) | ||
* Match from the beginning of the string. (default: `true`) | ||
*/ | ||
start?: boolean; | ||
/** | ||
* When `true` the regexp allows an optional trailing delimiter to match. (default: `true`) | ||
* Match to the end of the string. (default: `true`) | ||
*/ | ||
end?: boolean; | ||
/** | ||
* Allow optional trailing delimiter to match. (default: `true`) | ||
*/ | ||
trailing?: boolean; | ||
@@ -51,12 +55,16 @@ } | ||
/** | ||
* When `true` the validation will be case sensitive. (default: `false`) | ||
* Regexp will be case sensitive. (default: `false`) | ||
*/ | ||
sensitive?: boolean; | ||
/** | ||
* Allow delimiter to be arbitrarily repeated. (default: `true`) | ||
* Allow the delimiter to be arbitrarily repeated. (default: `true`) | ||
*/ | ||
loose?: boolean; | ||
/** | ||
* When `false` the function can produce an invalid (unmatched) path. (default: `true`) | ||
* Verify patterns are valid and safe to use. (default: `false`) | ||
*/ | ||
strict?: boolean; | ||
/** | ||
* Verifies the function is producing a valid path. (default: `true`) | ||
*/ | ||
validate?: boolean; | ||
@@ -83,3 +91,3 @@ /** | ||
*/ | ||
export declare function compile<P extends object = object>(path: Path, options?: CompileOptions): PathFunction<P>; | ||
export declare function compile<P extends ParamData = ParamData>(path: Path, options?: CompileOptions): PathFunction<P>; | ||
export type ParamData = Partial<Record<string, string | string[]>>; | ||
@@ -126,5 +134,2 @@ export type PathFunction<P extends ParamData> = (data?: P) => string; | ||
export type Path = string | TokenData; | ||
export type PathRegExp = RegExp & { | ||
keys: Key[]; | ||
}; | ||
/** | ||
@@ -131,0 +136,0 @@ * Normalize the given path string, returning a regular expression. |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.pathToRegexp = exports.match = exports.compile = exports.parse = exports.TokenData = void 0; | ||
exports.TokenData = void 0; | ||
exports.parse = parse; | ||
exports.compile = compile; | ||
exports.match = match; | ||
exports.pathToRegexp = pathToRegexp; | ||
const DEFAULT_DELIMITER = "/"; | ||
const NOOP_VALUE = (value) => value; | ||
const ID_CHAR = /^\p{XID_Continue}$/u; | ||
const DEBUG_URL = "https://git.new/pathToRegexpError"; | ||
const SIMPLE_TOKENS = { | ||
@@ -106,3 +111,3 @@ "!": "!", | ||
const { type: nextType, index } = this.peek(); | ||
throw new TypeError(`Unexpected ${nextType} at ${index}, expected ${type}: https://git.new/pathToRegexpError`); | ||
throw new TypeError(`Unexpected ${nextType} at ${index}, expected ${type}: ${DEBUG_URL}`); | ||
} | ||
@@ -118,3 +123,3 @@ text() { | ||
modifier() { | ||
return (this.tryConsume("?") || this.tryConsume("*") || this.tryConsume("+") || ""); | ||
return this.tryConsume("?") || this.tryConsume("*") || this.tryConsume("+"); | ||
} | ||
@@ -136,3 +141,3 @@ } | ||
function parse(str, options = {}) { | ||
const { delimiter = DEFAULT_DELIMITER, encodePath = NOOP_VALUE } = options; | ||
const { encodePath = NOOP_VALUE, delimiter = encodePath(DEFAULT_DELIMITER) } = options; | ||
const tokens = []; | ||
@@ -154,3 +159,3 @@ const it = lexer(str); | ||
if (next.type === "*") { | ||
throw new TypeError(`Unexpected * at ${next.index}, you probably want \`/*\` or \`{/:foo}*\`: https://git.new/pathToRegexpError`); | ||
throw new TypeError(`Unexpected * at ${next.index}, you probably want \`/*\` or \`{/:foo}*\`: ${DEBUG_URL}`); | ||
} | ||
@@ -163,3 +168,3 @@ continue; | ||
name: String(key++), | ||
pattern: `[^${escape(delimiter)}]*`, | ||
pattern: `(?:(?!${escape(delimiter)}).)*`, | ||
modifier: "*", | ||
@@ -176,3 +181,3 @@ separator: delimiter, | ||
const suffix = it.text(); | ||
const separator = it.tryConsume(";") ? it.text() : prefix + suffix; | ||
const separator = it.tryConsume(";") && it.text(); | ||
it.consume("}"); | ||
@@ -195,3 +200,2 @@ const modifier = it.modifier(); | ||
} | ||
exports.parse = parse; | ||
/** | ||
@@ -204,3 +208,2 @@ * Compile a string to a template function for the path. | ||
} | ||
exports.compile = compile; | ||
/** | ||
@@ -216,3 +219,3 @@ * Convert a single token into a path building function. | ||
const optional = token.modifier === "?" || token.modifier === "*"; | ||
const { prefix = "", suffix = "", separator = "" } = token; | ||
const { prefix = "", suffix = "", separator = suffix + prefix } = token; | ||
if (encode && repeated) { | ||
@@ -269,13 +272,12 @@ const stringify = (value, index) => { | ||
function compileTokens(data, options) { | ||
const { encode = encodeURIComponent, loose = true, validate = true, } = options; | ||
const reFlags = flags(options); | ||
const { encode = encodeURIComponent, loose = true, validate = true, strict = false, } = options; | ||
const flags = toFlags(options); | ||
const stringify = toStringify(loose, data.delimiter); | ||
const keyToRegexp = toKeyRegexp(stringify, data.delimiter); | ||
const sources = toRegExpSource(data, stringify, [], flags, strict); | ||
// Compile all the tokens into regexps. | ||
const encoders = data.tokens.map((token) => { | ||
const encoders = data.tokens.map((token, index) => { | ||
const fn = tokenToFunction(token, encode); | ||
if (!validate || typeof token === "string") | ||
return fn; | ||
const pattern = keyToRegexp(token); | ||
const validRe = new RegExp(`^${pattern}$`, reFlags); | ||
const validRe = new RegExp(`^${sources[index]}$`, flags); | ||
return (data) => { | ||
@@ -307,3 +309,4 @@ const value = fn(data); | ||
if (decode && (key.modifier === "+" || key.modifier === "*")) { | ||
const re = new RegExp(stringify(key.separator || ""), "g"); | ||
const { prefix = "", suffix = "", separator = suffix + prefix } = key; | ||
const re = new RegExp(stringify(separator), "g"); | ||
return (value) => value.split(re).map(decode); | ||
@@ -313,4 +316,4 @@ } | ||
}); | ||
return function match(pathname) { | ||
const m = re.exec(pathname); | ||
return function match(input) { | ||
const m = re.exec(input); | ||
if (!m) | ||
@@ -330,3 +333,2 @@ return false; | ||
} | ||
exports.match = match; | ||
/** | ||
@@ -336,3 +338,3 @@ * Escape a regular expression string. | ||
function escape(str) { | ||
return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1"); | ||
return str.replace(/([.+*?^${}()[\]|/\\])/g, "\\$1"); | ||
} | ||
@@ -343,3 +345,4 @@ /** | ||
function looseReplacer(value, loose) { | ||
return loose ? `${escape(value)}+` : escape(value); | ||
const escaped = escape(value); | ||
return loose ? `(?:${escaped})+(?!${escaped})` : escaped; | ||
} | ||
@@ -352,3 +355,3 @@ /** | ||
return escape; | ||
const re = new RegExp(`[^${escape(delimiter)}]+|(.)`, "g"); | ||
const re = new RegExp(`(?:(?!${escape(delimiter)}).)+|(.)`, "g"); | ||
return (value) => value.replace(re, looseReplacer); | ||
@@ -359,3 +362,3 @@ } | ||
*/ | ||
function flags(options) { | ||
function toFlags(options) { | ||
return options.sensitive ? "" : "i"; | ||
@@ -367,20 +370,12 @@ } | ||
function tokensToRegexp(data, keys, options) { | ||
const { trailing = true, start = true, end = true, loose = true } = options; | ||
const { trailing = true, loose = true, start = true, end = true, strict = false, } = options; | ||
const flags = toFlags(options); | ||
const stringify = toStringify(loose, data.delimiter); | ||
const keyToRegexp = toKeyRegexp(stringify, data.delimiter); | ||
const sources = toRegExpSource(data, stringify, keys, flags, strict); | ||
let pattern = start ? "^" : ""; | ||
for (const token of data.tokens) { | ||
if (typeof token === "string") { | ||
pattern += stringify(token); | ||
} | ||
else { | ||
if (token.name) | ||
keys.push(token); | ||
pattern += keyToRegexp(token); | ||
} | ||
} | ||
pattern += sources.join(""); | ||
if (trailing) | ||
pattern += `(?:${stringify(data.delimiter)})?`; | ||
pattern += end ? "$" : `(?=${escape(data.delimiter)}|$)`; | ||
return new RegExp(pattern, flags(options)); | ||
return new RegExp(pattern, flags); | ||
} | ||
@@ -390,20 +385,53 @@ /** | ||
*/ | ||
function toKeyRegexp(stringify, delimiter) { | ||
const segmentPattern = `[^${escape(delimiter)}]+?`; | ||
return (key) => { | ||
const prefix = key.prefix ? stringify(key.prefix) : ""; | ||
const suffix = key.suffix ? stringify(key.suffix) : ""; | ||
const modifier = key.modifier || ""; | ||
if (key.name) { | ||
const pattern = key.pattern || segmentPattern; | ||
if (key.modifier === "+" || key.modifier === "*") { | ||
const mod = key.modifier === "*" ? "?" : ""; | ||
const split = key.separator ? stringify(key.separator) : ""; | ||
return `(?:${prefix}((?:${pattern})(?:${split}(?:${pattern}))*)${suffix})${mod}`; | ||
function toRegExpSource(data, stringify, keys, flags, strict) { | ||
const defaultPattern = `(?:(?!${escape(data.delimiter)}).)+?`; | ||
let backtrack = ""; | ||
let safe = true; | ||
return data.tokens.map((token, index) => { | ||
if (typeof token === "string") { | ||
backtrack = token; | ||
return stringify(token); | ||
} | ||
const { prefix = "", suffix = "", separator = suffix + prefix, modifier = "", } = token; | ||
const pre = stringify(prefix); | ||
const post = stringify(suffix); | ||
if (token.name) { | ||
const pattern = token.pattern ? `(?:${token.pattern})` : defaultPattern; | ||
const re = checkPattern(pattern, token.name, flags); | ||
safe || (safe = safePattern(re, prefix || backtrack)); | ||
if (!safe) { | ||
throw new TypeError(`Ambiguous pattern for "${token.name}": ${DEBUG_URL}`); | ||
} | ||
return `(?:${prefix}(${pattern})${suffix})${modifier}`; | ||
safe = !strict || safePattern(re, suffix); | ||
backtrack = ""; | ||
keys.push(token); | ||
if (modifier === "+" || modifier === "*") { | ||
const mod = modifier === "*" ? "?" : ""; | ||
const sep = stringify(separator); | ||
if (!sep) { | ||
throw new TypeError(`Missing separator for "${token.name}": ${DEBUG_URL}`); | ||
} | ||
safe || (safe = !strict || safePattern(re, separator)); | ||
if (!safe) { | ||
throw new TypeError(`Ambiguous pattern for "${token.name}" separator: ${DEBUG_URL}`); | ||
} | ||
safe = !strict; | ||
return `(?:${pre}(${pattern}(?:${sep}${pattern})*)${post})${mod}`; | ||
} | ||
return `(?:${pre}(${pattern})${post})${modifier}`; | ||
} | ||
return `(?:${prefix}${suffix})${modifier}`; | ||
}; | ||
return `(?:${pre}${post})${modifier}`; | ||
}); | ||
} | ||
function checkPattern(pattern, name, flags) { | ||
try { | ||
return new RegExp(`^${pattern}$`, flags); | ||
} | ||
catch (err) { | ||
throw new TypeError(`Invalid pattern for "${name}": ${err.message}`); | ||
} | ||
} | ||
function safePattern(re, value) { | ||
return value ? !re.test(value) : false; | ||
} | ||
/** | ||
@@ -422,3 +450,2 @@ * Normalize the given path string, returning a regular expression. | ||
} | ||
exports.pathToRegexp = pathToRegexp; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "path-to-regexp", | ||
"version": "7.0.0", | ||
"version": "7.1.0", | ||
"description": "Express style path to RegExp utility", | ||
@@ -37,4 +37,5 @@ "keywords": [ | ||
"@vitest/coverage-v8": "^1.4.0", | ||
"recheck": "^4.4.5", | ||
"size-limit": "^11.1.2", | ||
"typescript": "^5.1.6" | ||
"typescript": "^5.5.3" | ||
}, | ||
@@ -41,0 +42,0 @@ "engines": { |
@@ -35,8 +35,9 @@ # Path-to-RegExp | ||
- **sensitive** Regexp will be case sensitive. (default: `false`) | ||
- **trailing** Regexp allows an optional trailing delimiter to match. (default: `true`) | ||
- **trailing** Allows optional trailing delimiter to match. (default: `true`) | ||
- **strict** Verify patterns are valid and safe to use. (default: `false`, recommended: `true`) | ||
- **end** Match to the end of the string. (default: `true`) | ||
- **start** Match from the beginning of the string. (default: `true`) | ||
- **loose** Allow the delimiter to be repeated an arbitrary number of times. (default: `true`) | ||
- **loose** Allow the delimiter to be arbitrarily repeated, e.g. `/` or `///`. (default: `true`) | ||
- **delimiter** The default delimiter for segments, e.g. `[^/]` for `:named` parameters. (default: `'/'`) | ||
- **encodePath** A function to encode strings before inserting into `RegExp`. (default: `x => x`, recommended: [`encodeurl`](https://github.com/pillarjs/encodeurl)) | ||
- **encodePath** A function for encoding input strings. (default: `x => x`, recommended: [`encodeurl`](https://github.com/pillarjs/encodeurl) for unicode encoding) | ||
@@ -168,2 +169,16 @@ ```js | ||
##### Custom separator | ||
By default, parameters set the separator as the `prefix + suffix` of the token. Using `;` you can modify this: | ||
```js | ||
const regexp = pathToRegexp("/name{/:parts;-}+"); | ||
regexp.exec("/name"); | ||
//=> null | ||
regexp.exec("/bar/1-2-3"); | ||
//=> [ '/name/1-2-3', '1-2-3', index: 0 ] | ||
``` | ||
#### Wildcard | ||
@@ -193,4 +208,3 @@ | ||
```js | ||
// Make sure you consistently `decode` segments. | ||
const fn = match("/user/:id", { decode: decodeURIComponent }); | ||
const fn = match("/user/:id"); | ||
@@ -216,5 +230,4 @@ fn("/user/123"); //=> { path: '/user/123', index: 0, params: { id: '123' } } | ||
toPath({ id: 123 }); //=> "/user/123" | ||
toPath({ id: "name" }); //=> "/user/name" | ||
toPath({ id: "café" }); //=> "/user/caf%C3%A9" | ||
toPath({ id: ":/" }); //=> "/user/%3A%2F" | ||
@@ -225,3 +238,3 @@ // When disabling `encode`, you need to make sure inputs are encoded correctly. No arrays are accepted. | ||
toPathRaw({ id: "%3A%2F" }); //=> "/user/%3A%2F" | ||
toPathRaw({ id: ":/" }); //=> "/user/:/", throws when `validate: false` is not set. | ||
toPathRaw({ id: ":/" }); //=> Throws, "/user/:/" when `validate` is `false`. | ||
@@ -243,2 +256,3 @@ const toPathRepeated = compile("{/:segment}+"); | ||
- If matches are intended to be exact, you need to set `loose: false`, `trailing: false`, and `sensitive: true`. | ||
- Enable `strict: true` to detect ReDOS issues. | ||
@@ -249,4 +263,6 @@ ### Parse | ||
### Token Information | ||
### Tokens | ||
The `tokens` returned by `TokenData` is an array of strings or keys, represented as objects, with the following properties: | ||
- `name` The name of the token | ||
@@ -259,2 +275,16 @@ - `prefix` _(optional)_ The prefix string for the segment (e.g. `"/"`) | ||
### Custom path | ||
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: | ||
```js | ||
import { TokenData, match } from "path-to-regexp"; | ||
const tokens = ["/", { name: "foo" }]; | ||
const path = new TokenData(tokens, "/"); | ||
const fn = match(path); | ||
fn("/test"); //=> { path: '/test', index: 0, params: { foo: 'test' } } | ||
``` | ||
## Errors | ||
@@ -272,4 +302,8 @@ | ||
### Unexpected `!`, `@`, `,`, or `;` | ||
### Unexpected `;` | ||
Used as a [custom separator](#custom-separator) for repeated parameters. | ||
### Unexpected `!`, `@`, or `,` | ||
These characters have been reserved for future use. | ||
@@ -276,0 +310,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
64954
569
326
0
8