Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(deps): update dependency path-to-regexp@>=0.2.0 <1.9.0 to v8 #1447

Closed
wants to merge 1 commit into from

Conversation

renovate[bot]
Copy link
Contributor

@renovate renovate bot commented Feb 26, 2025

This PR contains the following updates:

Package Change Age Adoption Passing Confidence
path-to-regexp@>=0.2.0 <1.9.0 [^1.9.0 -> ^8.0.0](https://renovatebot.com/diffs/npm/path-to-regexp@&gt;&#x3D;0.2.0 <1.9.0/1.9.0/8.2.0) age adoption passing confidence

Release Notes

pillarjs/path-to-regexp (path-to-regexp@>=0.2.0 <1.9.0)

v8.2.0: 8.2.0

Compare Source

Fixed

  • Allowing path-to-regexp to run on older browsers by targeting ES2015
    • Target ES2015 5969033
      • Also saved 0.22kb (10%!) by removing the private class field down level
    • Remove s flag from regexp 51dbd45

v8.1.0

Compare Source

Added

  • Adds pathToRegexp method back for generating a regex
  • Adds stringify method for converting TokenData into a path string

v8.0.0: Simpler API

Compare Source

Heads up! This is a fairly large change (again) and I need to apologize in advance. If I foresaw what this version would have ended up being I would not have released version 7. A longer blog post and explanation will be incoming this week, but the pivot has been due to work on Express.js v5 and this will the finalized syntax used in Express moving forward.

Edit: The post is out - https://blakeembrey.com/posts/2024-09-web-redos/

Added

  • Adds key names to wildcards using *name syntax, aligns with : behavior but using an asterisk instead

Changed

  • Removes group suffixes of ?, +, and * - only optional exists moving forward (use wildcards for +, {*foo} for *)
  • Parameter names follow JS identifier rules and allow unicode characters

Added

  • Parameter names can now be quoted, e.g. :"foo-bar"
  • Match accepts an array of values, so the signature is now string | TokenData | Array<string | TokenData>

Removed

  • Removes loose mode
  • Removes regular expression overrides of parameters

v7.2.0: Support array inputs (again)

Compare Source

Added

  • Support array inputs for match and pathToRegexp 3fdd88f

v7.1.0: Strict mode

Compare Source

Added

  • Adds a strict option to detect potential ReDOS issues

Fixed

  • Fixes separator to default to suffix + prefix when not specified
  • Allows separator to be undefined in TokenData
    • This is only relevant if you are building TokenData manually, previously parse filled it in automatically

Comments

  • I highly recommend enabling strict: true and I'm probably releasing a V8 with it enabled by default ASAP as a necessary security mitigation

v7.0.0: Wildcard, unicode, and modifier changes

Compare Source

Hi all! There's a few major breaking changes in this release so read carefully.

Breaking changes:

  • The function returned by compile only accepts strings as values (i.e. no numbers, use String(value) before compiling a path)
    • For repeated values, when encode !== false, it must be an array of strings
  • Parameter names can contain all unicode identifier characters (defined as regex \p{XID_Continue}).
  • Modifiers (?, *, +) must be used after a param explicitly wrapped in {}
    • No more implied prefix of / or .
  • No support for arrays or regexes as inputs
  • The wildcard (standalone *) has been added back and matches Express.js expected behavior
  • Removed endsWith option
  • Renamed strict: true to trailing: false
  • Reserved ;, ,, !, and @ for future use-cases
  • Removed tokensToRegexp, tokensToFunction and regexpToFunction in favor of simplifying exports
  • Enable a "loose" mode by default, so / can be repeated multiple times in a matched path (i.e. /foo works like //foo, etc)
  • encode and decode no longer receive the token as the second parameter
  • Removed the ESM + CommonJS dual package in favor of only one CommonJS supported export
  • Minimum JS support for ES2020 (previous ES2015)
  • Encode defaults to encodeURIComponent and decode defaults to decodeURIComponent

Added:

  • Adds encodePath to fix an issue around encode being used for both path and parameters (the path and parameter should be encoded slightly differently)
  • Adds loose as an option to support arbitrarily matching the delimiter in paths, e.g. foo/bar and foo///bar should work the same
  • Allow encode and decode to be set to false which skips all processing of the parameters input/output
  • All remaining methods support TokenData (exported, returned by parse) as input
    • This should be useful if you are programmatically building paths to match or want to avoid parsing multiple times

Requests for feedback:

  • Requiring {} is an obvious drawback but I'm seeking feedback on whether it helps make path behavior clearer
    • Related: Removing / and . as implicit prefixes
  • Removing array and regex support is to reduce the overall package size for things many users don't need
  • Unicode IDs are added to align more closely with browser URLPattern behavior, which uses JS identifiers

v6.3.0: Fix backtracking in 6.x

Compare Source

Fixed

v6.2.2: Updated README

Compare Source

No API changes. Documentation only release.

Changed

v6.2.1: Fix matching :name* parameter

Compare Source

Fixed

  • Fix invalid matching of :name* parameter (#​261) 762bc6b
  • Compare delimiter string over regexp 86baef8

Added

v6.2.0: Named Capturing Groups

Compare Source

Added

  • Support named capturing groups for RegExps (#​225)

Fixed

  • Update strict flag documentation (#​227)
  • Ignore test files when bundling (#​220)

v6.1.0: Use `/#?` as Default Delimiter

Compare Source

Fixed

  • Use /#? as default delimiter to avoid matching on query or fragment parameters
    • If you are matching non-paths (e.g. hostnames), you can adjust delimiter: '.'

v6.0.0: Custom Prefix and Suffix Groups

Compare Source

This release reverts the prefix behavior added in v3 back to the behavior seen in v2. For the most part, path matching is backward compatible with v2 with these enhancements:

  1. Support for nested non-capturing groups in regexp, e.g. /(abc(?=d))
  2. Support for custom prefix and suffix groups using /{abc(.*)def}
  3. Tokens in an unexpected position will throw an error
    • Paths like /test(foo previously worked treating ( as a literal character, now it expects ( to be closed and is treated as a group
    • You can escape the character for the previous behavior, e.g. /test\(foo

Changed

  • Revert using any character as prefix, support prefixes option to configure this (starts as /. which acts like every version since 0.x again)
  • Add support for {} to capture prefix/suffix explicitly, enables custom use-cases like /:attr1{-:attr2}?

v5.0.0: Remove Default Encode URI Component

Compare Source

No changes to path rules since 3.x, except support for nested RegEx parts in 4.x.

Changed

  • Rename RegexpOptions interface to TokensToRegexpOptions
  • Remove normalizePathname from library, document solution in README
  • Encode using identity function as default, not encodeURIComponent

v4.0.5: Decode URI

Compare Source

Removed

  • Remove whitelist in favor of decodeURI (advanced behavior can happen outside path-to-regexp)

v4.0.4: Remove String#normalize

Compare Source

Fixed

  • Remove usage of String.prototype.normalize to continue supporting IE

v4.0.3: Normalize Path Whitelist

Compare Source

Added

  • Add normalize whitelist of characters (defaults to /%.-)

v4.0.2: Allow RegexpOptions in match

Compare Source

Fixed

  • Allow RegexpOptions in match(...) function

v4.0.1: Fix Spelling of Regexp

Compare Source

Fixed

  • Normalize regexp spelling across 4.x

v4.0.0: ES2015 Package for Bundlers

Compare Source

All path rules are backward compatible with 3.x, except for nested () and other RegEx special characters that were previously ignored.

Changed

  • Export names have changed to support ES2015 modules in bundlers
  • match does not default to decodeURIComponent

Added

  • New normalizePathname utility for supporting unicode paths in libraries
  • Support nested non-capturing groups within parameters
  • Add tree-shaking (via ES2015 modules) for webpack and other bundlers

v3.3.0: Add backtracking protection

Compare Source

Fixed

v3.2.0: Match Function

Compare Source

Added

  • Add native match function to library

v3.1.0: Validate and sensitive options

Compare Source

  • Add sensitive option for tokensToFunction (#​191)
  • Add validate option to path functions (#​178)

v3.0.0

Compare Source

  • Always use prefix character as delimiter token, allowing any character to be a delimiter (e.g. /:att1-:att2-:att3-:att4-:att5)
  • Remove partial support, prefer escaping the prefix delimiter explicitly (e.g. \\/(apple-)?icon-:res(\\d+).png)

v2.4.0

Compare Source

  • Support start option to disable anchoring from beginning of the string

v2.3.0

Compare Source

  • Use delimiter when processing repeated matching groups (e.g. foo/bar has no prefix, but has a delimiter)

v2.2.1

Compare Source

  • Allow empty string with end: false to match both relative and absolute paths

v2.2.0

Compare Source

  • Pass token as second argument to encode option (e.g. encode(value, token))

v2.1.0

Compare Source

  • Handle non-ending paths where the final character is a delimiter
    • E.g. /foo/ before required either /foo/ or /foo// to match in non-ending mode

v2.0.0

Compare Source

  • New option! Ability to set endsWith to match paths like /test?query=string up to the query string
  • New option! Set delimiters for specific characters to be treated as parameter prefixes (e.g. /:test)
  • Remove isarray dependency
  • Explicitly handle trailing delimiters instead of trimming them (e.g. /test/ is now treated as /test/ instead of /test when matching)
  • Remove overloaded keys argument that accepted options
  • Remove keys list attached to the RegExp output
  • Remove asterisk functionality (it's a real pain to properly encode)
  • Change tokensToFunction (e.g. compile) to accept an encode function for pretty encoding (e.g. pass your own implementation)

Configuration

📅 Schedule: Branch creation - "* 0-12 * * 3" (UTC), Automerge - At any time (no schedule defined).

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate bot requested a review from tackley as a code owner February 26, 2025 11:45
@renovate renovate bot added the dependencies Pull requests that update a dependency file label Feb 26, 2025
Copy link

[puLL-Merge] - pillarjs/path-to-regexp@v1.9.0..v8.0.0

Diff
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..23c96d7
--- /dev/null
+++ .github/workflows/ci.yml
@@ -0,0 +1,31 @@
+name: CI
+on:
+  - push
+  - pull_request
+permissions:
+  contents: read
+jobs:
+  test:
+    name: Node.js ${{ matrix.node-version }}
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        node-version:
+          - "16"
+          - "*"
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions/setup-node@v1
+        with:
+          node-version: ${{ matrix.node-version }}
+      - uses: actions/cache@v2
+        with:
+          path: ~/.npm
+          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
+          restore-keys: |
+            ${{ runner.os }}-node-
+      - run: npm ci
+      - run: npm test
+      - uses: codecov/codecov-action@v1
+        with:
+          name: Node.js ${{ matrix.node-version }}
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
new file mode 100644
index 0000000..5fcb48c
--- /dev/null
+++ .github/workflows/scorecard.yml
@@ -0,0 +1,74 @@
+# This workflow uses actions that are not certified by GitHub. They are provided
+# by a third-party and are governed by separate terms of service, privacy
+# policy, and support documentation.
+
+name: Scorecard supply-chain security
+
+on:
+  # For Branch-Protection check. Only the default branch is supported. See
+  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
+  branch_protection_rule:
+  # To guarantee Maintained check is occasionally updated. See
+  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
+  schedule:
+    - cron: "16 21 * * 1"
+  push:
+    branches: ["master"]
+
+# Declare default permissions as read only.
+permissions:
+  contents: read
+
+jobs:
+  analysis:
+    name: Scorecard analysis
+    runs-on: ubuntu-latest
+    permissions:
+      # Needed to upload the results to code-scanning dashboard.
+      security-events: write
+      # Needed to publish results and get a badge (see publish_results below).
+      id-token: write
+      # Uncomment the permissions below if installing in a private repository.
+      # contents: read
+      # actions: read
+
+    steps:
+      - name: "Checkout code"
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.2
+        with:
+          persist-credentials: false
+
+      - name: "Run analysis"
+        uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
+        with:
+          results_file: results.sarif
+          results_format: sarif
+          # (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
+          # - you want to enable the Branch-Protection check on a *public* repository, or
+          # - you are installing Scorecard on a *private* repository
+          # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
+          # repo_token: ${{ secrets.SCORECARD_TOKEN }}
+
+          # Public repositories:
+          #   - Publish results to OpenSSF REST API for easy access by consumers
+          #   - Allows the repository to include the Scorecard badge.
+          #   - See https://github.com/ossf/scorecard-action#publishing-results.
+          # For private repositories:
+          #   - `publish_results` will always be set to `false`, regardless
+          #     of the value entered here.
+          publish_results: true
+
+      # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
+      # format to the repository Actions tab.
+      - name: "Upload artifact"
+        uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
+        with:
+          name: SARIF file
+          path: results.sarif
+          retention-days: 5
+
+      # Upload the results to GitHub's code scanning dashboard.
+      - name: "Upload to code-scanning"
+        uses: github/codeql-action/upload-sarif@2f93e4319b2f04a2efc38fa7f78bd681bc3f7b2f # v2.23.2
+        with:
+          sarif_file: results.sarif
diff --git .gitignore .gitignore
index d1c46d7..6352b21 100644
--- .gitignore
+++ .gitignore
@@ -1,4 +1,5 @@
-node_modules
-coverage
-components
-typings
\ No newline at end of file
+.vscode/
+node_modules/
+coverage/
+dist/
+dist.es2015/
diff --git .travis.yml .travis.yml
deleted file mode 100644
index 716f9d7..0000000
--- .travis.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-language: node_js
-
-node_js:
-  - "0.10"
-  - "0.12"
-  - "4.0"
-  - "4.1"
-
-after_script: "npm install coveralls@2 && cat ./coverage/lcov.info | coveralls"
diff --git History.md History.md
index abe4a65..fedbb81 100644
--- History.md
+++ History.md
@@ -1,158 +1,171 @@
-1.7.0 / 2016-11-08
-==================
+# Moved to [GitHub Releases](https://github.com/pillarjs/path-to-regexp/releases)
 
-  * Allow a `delimiter` option to be passed in with `tokensToRegExp` which will be used for "non-ending" token match situations
+## 3.0.0 / 2019-01-13
 
-1.6.0 / 2016-10-03
-==================
+- Always use prefix character as delimiter token, allowing any character to be a delimiter (e.g. `/:att1-:att2-:att3-:att4-:att5`)
+- Remove `partial` support, prefer escaping the prefix delimiter explicitly (e.g. `\\/(apple-)?icon-:res(\\d+).png`)
 
-  * Populate `RegExp.keys` when using the `tokensToRegExp` method (making it consistent with the main export)
-  * Allow a `delimiter` option to be passed in with `parse`
-  * Updated TypeScript definition with `Keys` and `Options` updated
+## 2.4.0 / 2018-08-26
 
-1.5.3 / 2016-06-15
-==================
+- Support `start` option to disable anchoring from beginning of the string
 
-  * Add `\\` to the ignore character group to avoid backtracking on mismatched parens
+## 2.3.0 / 2018-08-20
 
-1.5.2 / 2016-06-15
-==================
+- Use `delimiter` when processing repeated matching groups (e.g. `foo/bar` has no prefix, but has a delimiter)
 
-  * Escape `\\` in string segments of regexp
+## 2.2.1 / 2018-04-24
 
-1.5.1 / 2016-06-08
-==================
+- Allow empty string with `end: false` to match both relative and absolute paths
 
-  * Add `index.d.ts` to NPM package
+## 2.2.0 / 2018-03-06
 
-1.5.0 / 2016-05-20
-==================
+- Pass `token` as second argument to `encode` option (e.g. `encode(value, token)`)
 
-  * Handle partial token segments (better)
-  * Allow compile to handle asterisk token segments
+## 2.1.0 / 2017-10-20
 
-1.4.0 / 2016-05-18
-==================
+- Handle non-ending paths where the final character is a delimiter
+  - E.g. `/foo/` before required either `/foo/` or `/foo//` to match in non-ending mode
 
-  * Handle RegExp unions in path matching groups
+## 2.0.0 / 2017-08-23
 
-1.3.0 / 2016-05-08
-==================
+- New option! Ability to set `endsWith` to match paths like `/test?query=string` up to the query string
+- New option! Set `delimiters` for specific characters to be treated as parameter prefixes (e.g. `/:test`)
+- Remove `isarray` dependency
+- Explicitly handle trailing delimiters instead of trimming them (e.g. `/test/` is now treated as `/test/` instead of `/test` when matching)
+- Remove overloaded `keys` argument that accepted `options`
+- Remove `keys` list attached to the `RegExp` output
+- Remove asterisk functionality (it's a real pain to properly encode)
+- Change `tokensToFunction` (e.g. `compile`) to accept an `encode` function for pretty encoding (e.g. pass your own implementation)
 
-  * Clarify README language and named parameter token support
-  * Support advanced Closure Compiler with type annotations
-  * Add pretty paths options to compiled function output
-  * Add TypeScript definition to project
-  * Improved prefix handling with non-complete segment parameters (E.g. `/:foo?-bar`)
+## 1.7.0 / 2016-11-08
 
-1.2.1 / 2015-08-17
-==================
+- Allow a `delimiter` option to be passed in with `tokensToRegExp` which will be used for "non-ending" token match situations
 
-  * Encode values before validation with path compilation function
-  * More examples of using compilation in README
+## 1.6.0 / 2016-10-03
 
-1.2.0 / 2015-05-20
-==================
+- Populate `RegExp.keys` when using the `tokensToRegExp` method (making it consistent with the main export)
+- Allow a `delimiter` option to be passed in with `parse`
+- Updated TypeScript definition with `Keys` and `Options` updated
 
-  * Add support for matching an asterisk (`*`) as an unnamed match everything group (`(.*)`)
+## 1.5.3 / 2016-06-15
 
-1.1.1 / 2015-05-11
-==================
+- Add `\\` to the ignore character group to avoid backtracking on mismatched parens
 
-  * Expose methods for working with path tokens
+## 1.5.2 / 2016-06-15
 
-1.1.0 / 2015-05-09
-==================
+- Escape `\\` in string segments of regexp
 
-  * Expose the parser implementation to consumers
-  * Implement a compiler function to generate valid strings
-  * Huge refactor of tests to be more DRY and cover new parse and compile functions
-  * Use chai in tests
-  * Add .editorconfig
+## 1.5.1 / 2016-06-08
 
-1.0.3 / 2015-01-17
-==================
+- Add `index.d.ts` to NPM package
 
-  * Optimised function runtime
-  * Added `files` to `package.json`
+## 1.5.0 / 2016-05-20
 
-1.0.2 / 2014-12-17
-==================
+- Handle partial token segments (better)
+- Allow compile to handle asterisk token segments
 
-  * Use `Array.isArray` shim
-  * Remove ES5 incompatible code
-  * Fixed repository path
-  * Added new readme badges
+## 1.4.0 / 2016-05-18
 
-1.0.1 / 2014-08-27
-==================
+- Handle RegExp unions in path matching groups
 
-  * Ensure installation works correctly on 0.8
+## 1.3.0 / 2016-05-08
 
-1.0.0 / 2014-08-17
-==================
+- Clarify README language and named parameter token support
+- Support advanced Closure Compiler with type annotations
+- Add pretty paths options to compiled function output
+- Add TypeScript definition to project
+- Improved prefix handling with non-complete segment parameters (E.g. `/:foo?-bar`)
 
-  * No more API changes
+## 1.2.1 / 2015-08-17
 
-0.2.5 / 2014-08-07
-==================
+- Encode values before validation with path compilation function
+- More examples of using compilation in README
 
-  * Allow keys parameter to be omitted
+## 1.2.0 / 2015-05-20
 
-0.2.4 / 2014-08-02
-==================
+- Add support for matching an asterisk (`*`) as an unnamed match everything group (`(.*)`)
 
-  * Code coverage badge
-  * Updated readme
-  * Attach keys to the generated regexp
+## 1.1.1 / 2015-05-11
 
-0.2.3 / 2014-07-09
-==================
+- Expose methods for working with path tokens
 
-  * Add MIT license
+## 1.1.0 / 2015-05-09
 
-0.2.2 / 2014-07-06
-==================
+- Expose the parser implementation to consumers
+- Implement a compiler function to generate valid strings
+- Huge refactor of tests to be more DRY and cover new parse and compile functions
+- Use chai in tests
+- Add .editorconfig
 
-  * A passed in trailing slash in non-strict mode will become optional
-  * In non-end mode, the optional trailing slash will only match at the end
+## 1.0.3 / 2015-01-17
 
-0.2.1 / 2014-06-11
-==================
+- Optimised function runtime
+- Added `files` to `package.json`
 
-  * Fixed a major capturing group regexp regression
+## 1.0.2 / 2014-12-17
 
-0.2.0 / 2014-06-09
-==================
+- Use `Array.isArray` shim
+- Remove ES5 incompatible code
+- Fixed repository path
+- Added new readme badges
 
-  * Improved support for arrays
-  * Improved support for regexps
-  * Better support for non-ending strict mode matches with a trailing slash
-  * Travis CI support
-  * Block using regexp special characters in the path
-  * Removed support for the asterisk to match all
-  * New support for parameter suffixes - `*`, `+` and `?`
-  * Updated readme
-  * Provide delimiter information with keys array
+## 1.0.1 / 2014-08-27
 
-0.1.2 / 2014-03-10
-==================
+- Ensure installation works correctly on 0.8
 
-  * Move testing dependencies to `devDependencies`
+## 1.0.0 / 2014-08-17
 
-0.1.1 / 2014-03-10
-==================
+- No more API changes
 
-  * Match entire substring with `options.end`
-  * Properly handle ending and non-ending matches
+## 0.2.5 / 2014-08-07
 
-0.1.0 / 2014-03-06
-==================
+- Allow keys parameter to be omitted
 
-  * Add `options.end`
+## 0.2.4 / 2014-08-02
 
-0.0.2 / 2013-02-10
-==================
+- Code coverage badge
+- Updated readme
+- Attach keys to the generated regexp
 
-  * Update to match current express
-  * Add .license property to component.json
+## 0.2.3 / 2014-07-09
+
+- Add MIT license
+
+## 0.2.2 / 2014-07-06
+
+- A passed in trailing slash in non-strict mode will become optional
+- In non-end mode, the optional trailing slash will only match at the end
+
+## 0.2.1 / 2014-06-11
+
+- Fixed a major capturing group regexp regression
+
+## 0.2.0 / 2014-06-09
+
+- Improved support for arrays
+- Improved support for regexps
+- Better support for non-ending strict mode matches with a trailing slash
+- Travis CI support
+- Block using regexp special characters in the path
+- Removed support for the asterisk to match all
+- New support for parameter suffixes - `*`, `+` and `?`
+- Updated readme
+- Provide delimiter information with keys array
+
+## 0.1.2 / 2014-03-10
+
+- Move testing dependencies to `devDependencies`
+
+## 0.1.1 / 2014-03-10
+
+- Match entire substring with `options.end`
+- Properly handle ending and non-ending matches
+
+## 0.1.0 / 2014-03-06
+
+- Add `options.end`
+
+## 0.0.2 / 2013-02-10
+
+- Update to match current express
+- Add .license property to component.json
diff --git Readme.md Readme.md
index 379ecf4..263c0f7 100644
--- Readme.md
+++ Readme.md
@@ -1,13 +1,12 @@
 # Path-to-RegExp
 
-> Turn an Express-style path string such as `/user/:name` into a regular expression.
+> Turn a path string such as `/user/:name` into a regular expression.
 
 [![NPM version][npm-image]][npm-url]
-[![Build status][travis-image]][travis-url]
-[![Test coverage][coveralls-image]][coveralls-url]
-[![Dependency Status][david-image]][david-url]
+[![NPM downloads][downloads-image]][downloads-url]
+[![Build status][build-image]][build-url]
+[![Build coverage][coverage-image]][coverage-url]
 [![License][license-image]][license-url]
-[![Downloads][downloads-image]][downloads-url]
 
 ## Installation
 
@@ -17,241 +16,171 @@ npm install path-to-regexp --save
 
 ## Usage
 
-\`\`\`javascript
-var pathToRegexp = require('path-to-regexp')
-
-// pathToRegexp(path, keys, options)
-// pathToRegexp.parse(path)
-// pathToRegexp.compile(path)
-```
+```js
+const { match, compile, parse } = require("path-to-regexp");
 
-- **path** An Express-style string, an array of strings, or a regular expression.
-- **keys** An array to be populated with the keys found in the path.
-- **options**
-  - **sensitive** When `true` the route will be case sensitive. (default: `false`)
-  - **strict** When `false` the trailing slash is optional. (default: `false`)
-  - **end** When `false` the path will match at the beginning. (default: `true`)
-  - **delimiter** Set the default delimiter for repeat parameters. (default: `'/'`)
-
-```javascript
-var keys = []
-var re = pathToRegexp('/foo/:bar', keys)
-// re = /^\/foo\/([^\/]+?)\/?$/i
-// keys = [{ name: 'bar', prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '[^\\/]+?' }]
+// match(path, options?)
+// compile(path, options?)
+// parse(path, options?)

-Please note: The RegExp returned by path-to-regexp is intended for use with pathnames or hostnames. It can not handle the query strings or fragments of a URL.

Parameters

-The path string can be used to define parameters and populate the keys.

-#### Named Parameters

-Named parameters are defined by prefixing a colon to the parameter name (:foo). By default, the parameter will match until the following path segment.

-```js
-var re = pathToRegexp('/:foo/:bar', keys)
-// keys = [{ name: 'foo', prefix: '/', ... }, { name: 'bar', prefix: '/', ... }]

-re.exec('/test/route')
-//=> ['/test/route', 'test', 'route']
-```

-Please note: Named parameters must be made up of "word characters" ([A-Za-z0-9_]).
+Parameters match arbitrary strings in a path by matching up to the end of the segment, or up to any proceeding tokens. They are defined by prefixing a colon to the parameter name (:foo). Parameter names can use any valid JavaScript identifier, or be double quoted to use other characters (:"param-name").

-var re = pathToRegexp('/(apple-)?icon-:res(\\d+).png', keys)
-// keys = [{ name: 0, prefix: '/', ... }, { name: 'res', prefix: '', ... }]
+const fn = match("/:foo/:bar");

-re.exec('/icon-76.png')
-//=> ['/icon-76.png', undefined, '76']
+fn("/test/route");
+//=> { path: '/test/route', params: { foo: 'test', bar: 'route' } }

-#### Modified Parameters

-##### Optional
+### Wildcard

-Parameters can be suffixed with a question mark (?) to make the parameter optional. This will also make the prefix optional.
+Wildcard parameters match one or more characters across multiple segments. They are defined the same way as regular parameters, but are prefixed with an asterisk (*foo).

-var re = pathToRegexp('/:foo/:bar?', keys)
-// keys = [{ name: 'foo', ... }, { name: 'bar', delimiter: '/', optional: true, repeat: false }]
+const fn = match("/*splat");

-re.exec('/test')
-//=> ['/test', 'test', undefined]
-
-re.exec('/test/route')
-//=> ['/test', 'test', 'route']
+fn("/bar/baz");
+//=> { path: '/bar/baz', params: { splat: [ 'bar', 'baz' ] } }

-##### Zero or more
+### Optional

-Parameters can be suffixed with an asterisk (*) to denote a zero or more parameter matches. The prefix is taken into account for each match.
+Braces can be used to define parts of the path that are optional.

-var re = pathToRegexp('/:foo*', keys)
-// keys = [{ name: 'foo', delimiter: '/', optional: true, repeat: true }]
+const fn = match("/users{/:id}/delete");

-re.exec('/')
-//=> ['/', undefined]
+fn("/users/delete");
+//=> { path: '/users/delete', params: {} }

-re.exec('/bar/baz')
-//=> ['/bar/baz', 'bar/baz']
+fn("/users/123/delete");
+//=> { path: '/users/123/delete', params: { id: '123' } }

-##### One or more

-Parameters can be suffixed with a plus sign (+) to denote a one or more parameter matches. The prefix is taken into account for each match.
+## Match

-```js
-var re = pathToRegexp('/:foo+', keys)
-// keys = [{ name: 'foo', delimiter: '/', optional: false, repeat: true }]

-re.exec('/')
-//=> null

-re.exec('/bar/baz')
-//=> ['/bar/baz', 'bar/baz']
-```
+The match function returns a function for matching strings against a path:

-#### Custom Match Parameters

-All parameters can be provided a custom regexp, which overrides the default ([^\/]+).
+- path String or array of strings.
+- options (optional) (See parse for more options)

    • sensitive Regexp will be case sensitive. (default: false)
    • end Validate the match reaches the end of the string. (default: true)
    • trailing Allows optional trailing delimiter to match. (default: true)
    • decode Function for decoding strings to params, or false to disable all processing. (default: decodeURIComponent)
-var re = pathToRegexp('/:foo(\\d+)', keys)
-// keys = [{ name: 'foo', ... }]
-
-re.exec('/123')
-//=> ['/123', '123']
-
-re.exec('/abc')
-//=> null
+const fn = match("/foo/:bar");

-Please note: Backslashes need to be escaped with another backslash in strings.
+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).

-#### Unnamed Parameters
+## Compile ("Reverse" Path-To-RegExp)

-It is possible to write an unnamed parameter that only consists of a matching group. It works the same as a named parameter, except it will be numerically indexed.
+The compile function will return a function for transforming parameters into a valid path:
+
+- path A string.
+- options (See parse for more options)

    • encode Function for encoding input strings for output into the path, or false to disable entirely. (default: encodeURIComponent)
-var re = pathToRegexp('/:foo/(.*)', keys)
-// keys = [{ name: 'foo', ... }, { name: 0, ... }]
+const toPath = compile("/user/:id");

-re.exec('/test/route')
-//=> ['/test/route', 'test', 'route']
-```
+toPath({ id: "name" }); //=> "/user/name"
+toPath({ id: "café" }); //=> "/user/caf%C3%A9"

-#### Asterisk
+const toPathRepeated = compile("/*segment");

-An asterisk can be used for matching everything. It is equivalent to an unnamed matching group of `(.*)`.
+toPathRepeated({ segment: ["foo"] }); //=> "/foo"
+toPathRepeated({ segment: ["a", "b", "c"] }); //=> "/a/b/c"

-```js
-var re = pathToRegexp('/foo/*', keys)
-// keys = [{ name: '0', ... }]
+// When disabling `encode`, you need to make sure inputs are encoded correctly. No arrays are accepted.
+const toPathRaw = compile("/user/:id", { encode: false });

-re.exec('/foo/bar/baz')
-//=> ['/foo/bar/baz', 'bar/baz']
+toPathRaw({ id: "%3A%2F" }); //=> "/user/%3A%2F"

-### Parse
+## Developers

-The parse function is exposed via pathToRegexp.parse. This will return an array of strings and keys.
+- If you are rewriting paths with match and compile, consider using encode: false and decode: false to keep raw paths passed around.
+- To ensure matches work on paths containing characters usually encoded, such as emoji, consider using encodeurl for encodePath.

-```js
-var tokens = pathToRegexp.parse('/route/:foo/(.*)')
+### Parse

-console.log(tokens[0])
-//=> "/route"
+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.

-console.log(tokens[1])
-//=> { name: 'foo', prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '[^\\/]+?' }
+- path A string.
+- options (optional)

    • delimiter The default delimiter for segments, e.g. [^/] for :named parameters. (default: '/')
    • encodePath A function for encoding input strings. (default: x => x, recommended: encodeurl)

-console.log(tokens[2])
-//=> { name: 0, prefix: '/', delimiter: '/', optional: false, repeat: false, pattern: '.*' }
-```
+### Tokens

-Note: This method only works with Express-style strings.
+TokenData is a sequence of tokens, currently of types text, parameter, wildcard, or group.

-### Compile ("Reverse" Path-To-RegExp)
+### Custom path

-Path-To-RegExp exposes a compile function for transforming an Express-style path into a valid path.
+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:

-var toPath = pathToRegexp.compile('/user/:id')
-
-toPath({ id: 123 }) //=> "/user/123"
-toPath({ id: 'café' }) //=> "/user/caf%C3%A9"
-toPath({ id: '/' }) //=> "/user/%2F"
-
-toPath({ id: ':' }) //=> "/user/%3A"
-toPath({ id: ':' }, { pretty: true }) //=> "/user/:"
-
-var toPathRepeated = pathToRegexp.compile('/:segment+')
+import { TokenData, match } from "path-to-regexp";

-toPathRepeated({ segment: 'foo' }) //=> "/foo"
-toPathRepeated({ segment: ['a', 'b', 'c'] }) //=> "/a/b/c"
+const tokens = [
+  { type: "text", value: "/" },
+  { type: "parameter", name: "foo" },
+];
+const path = new TokenData(tokens);
+const fn = match(path);

-var toPathRegexp = pathToRegexp.compile('/user/:id(\\d+)')
-
-toPathRegexp({ id: 123 }) //=> "/user/123"
-toPathRegexp({ id: '123' }) //=> "/user/123"
-toPathRegexp({ id: 'abc' }) //=> Throws `TypeError`.
+fn("/test"); //=> { path: '/test', index: 0, params: { foo: 'test' } }

-Note: The generated function will throw on invalid input. It will do all necessary checks to ensure the generated path is valid. This method only works with strings.
+## Errors
+
+An effort has been made to ensure ambiguous paths from previous releases throw an error. This means you might be seeing an error when things worked before.

-### Working with Tokens
+### Unexpected ? or +

-Path-To-RegExp exposes the two functions used internally that accept an array of tokens.
+In past releases, ?, *, and + were used to denote optional or repeating parameters. As an alternative, try these:

-* pathToRegexp.tokensToRegExp(tokens, options) Transform an array of tokens into a matching regular expression.
-* pathToRegexp.tokensToFunction(tokens) Transform an array of tokens into a path generator function.
+- For optional (?), use an empty segment in a group such as /:file{.:ext}.
+- For repeating (+), only wildcard matching is supported, such as /*path.
+- For optional repeating (*), use a group and a wildcard parameter such as /files{/*path}.

-#### Token Information
+### Unexpected (, ), [, ], etc.

-* name The name of the token (string for named or number for index)
-* prefix The prefix character for the segment (/ or .)
-* delimiter The delimiter for the segment (same as prefix or /)
-* optional Indicates the token is optional (boolean)
-* repeat Indicates the token is repeated (boolean)
-* partial Indicates this token is a partial path segment (boolean)
-* pattern The RegExp used to match this token (string)
-* asterisk Indicates the token is an * match (boolean)
+Previous versions of Path-to-RegExp used these for RegExp features. This version no longer supports them so they've been reserved to avoid ambiguity. To use these characters literally, escape them with a backslash, e.g. "\\(".

-## Compatibility with Express <= 4.x
+### Missing parameter name

-Path-To-RegExp breaks compatibility with Express <= 4.x:
+Parameter names, the part after : or *, must be a valid JavaScript identifier. For example, it cannot start with a number or contain a dash. If you want a parameter name that uses these characters you can wrap the name in quotes, e.g. :"my-name".

-* No longer a direct conversion to a RegExp with sugar on top - it's a path matcher with named and unnamed matching groups

    • It's unlikely you previously abused this feature, it's rare and you could always use a RegExp instead
      -* All matching RegExp special characters can be used in a matching group. E.g. /:user(.*)
    • Other RegExp features are not support - no nested matching groups, non-capturing groups or look aheads
      -* Parameters have suffixes that augment meaning - *, + and ?. E.g. /:user*
      +### Unterminated quote

-## TypeScript
+Parameter names can be wrapped in double quote characters, and this error means you forgot to close the quote character.

-Includes a .d.ts file for TypeScript users.
+### Express <= 4.x

-## Live Demo
+Path-To-RegExp breaks compatibility with Express <= 4.x in the following ways:

-You can see a live demo of this library in use at express-route-tester.
+- Regexp characters can no longer be provided.
+- The optional character ? is no longer supported, use braces instead: /:file{.:ext}.
+- Some characters have new meaning or have been reserved ({}?*+@!;).
+- The parameter name now supports all JavaScript identifier characters, previously it was only [a-z0-9].

License

MIT

-[npm-image]: https://img.shields.io/npm/v/path-to-regexp.svg?style=flat
+[npm-image]: https://img.shields.io/npm/v/path-to-regexp
[npm-url]: https://npmjs.org/package/path-to-regexp
-[travis-image]: https://img.shields.io/travis/pillarjs/path-to-regexp.svg?style=flat
-[travis-url]: https://travis-ci.org/pillarjs/path-to-regexp
-[coveralls-image]: https://img.shields.io/coveralls/pillarjs/path-to-regexp.svg?style=flat
-[coveralls-url]: https://coveralls.io/r/pillarjs/path-to-regexp?branch=master
-[david-image]: http://img.shields.io/david/pillarjs/path-to-regexp.svg?style=flat
-[david-url]: https://david-dm.org/pillarjs/path-to-regexp
+[downloads-image]: https://img.shields.io/npm/dm/path-to-regexp
+[downloads-url]: https://npmjs.org/package/path-to-regexp
+[build-image]: https://img.shields.io/github/actions/workflow/status/pillarjs/path-to-regexp/ci.yml?branch=master
+[build-url]: https://github.com/pillarjs/path-to-regexp/actions/workflows/ci.yml?query=branch%3Amaster
+[coverage-image]: https://img.shields.io/codecov/c/gh/pillarjs/path-to-regexp
+[coverage-url]: https://codecov.io/gh/pillarjs/path-to-regexp
[license-image]: http://img.shields.io/npm/l/path-to-regexp.svg?style=flat
[license-url]: LICENSE.md
-[downloads-image]: http://img.shields.io/npm/dm/path-to-regexp.svg?style=flat
-[downloads-url]: https://npmjs.org/package/path-to-regexp
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..4aa608f
--- /dev/null
+++ SECURITY.md
@@ -0,0 +1,5 @@
+# Security Policy
+
+## Security contact information
+
+To report a security vulnerability, please use the Tidelift security contact. Tidelift will coordinate the fix and disclosure.
diff --git component.json component.json
deleted file mode 100644
index 2a4bb2e..0000000
--- component.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{

  • "name": "path-to-regexp",
  • "description": "Express style path to RegExp utility",
  • "version": "1.7.0",
  • "keywords": [
  • "express",
  • "regexp",
  • "route",
  • "routing"
  • ],
  • "scripts": [
  • "index.js"
  • ],
  • "license": "MIT",
  • "dependencies": {
  • "juliangruber/isarray": "0.0.1"
  • }
    -}
    \ No newline at end of file
    diff --git index.d.ts index.d.ts
    deleted file mode 100644
    index 705da2d..0000000
    --- index.d.ts
    +++ /dev/null
    @@ -1,77 +0,0 @@
    -declare function pathToRegexp (path: pathToRegexp.Path, options?: pathToRegexp.RegExpOptions & pathToRegexp.ParseOptions): pathToRegexp.PathRegExp;
    -declare function pathToRegexp (path: pathToRegexp.Path, keys?: pathToRegexp.Key[], options?: pathToRegexp.RegExpOptions & pathToRegexp.ParseOptions): pathToRegexp.PathRegExp;

-declare namespace pathToRegexp {

  • export interface PathRegExp extends RegExp {
  • // An array to be populated with the keys found in the path.
  • keys: Key[];
  • }
  • export interface RegExpOptions {
  • /**
  • * When `true` the route will be case sensitive. (default: `false`)
    
  • */
    
  • sensitive?: boolean;
  • /**
  • * When `false` the trailing slash is optional. (default: `false`)
    
  • */
    
  • strict?: boolean;
  • /**
  • * When `false` the path will match at the beginning. (default: `true`)
    
  • */
    
  • end?: boolean;
  • /**
  • * Sets the final character for non-ending optimistic matches. (default: `/`)
    
  • */
    
  • delimiter?: string;
  • }
  • export interface ParseOptions {
  • /**
  • * Set the default delimiter for repeat parameters. (default: `'/'`)
    
  • */
    
  • delimiter?: string;
  • }
  • /**
    • Parse an Express-style path into an array of tokens.
  • */
  • export function parse (path: string, options?: ParseOptions): Token[];
  • /**
    • Transforming an Express-style path into a valid path.
  • */
  • export function compile (path: string, options?: ParseOptions): PathFunction;
  • /**
    • Transform an array of tokens into a path generator function.
  • */
  • export function tokensToFunction (tokens: Token[]): PathFunction;
  • /**
    • Transform an array of tokens into a matching regular expression.
  • */
  • export function tokensToRegExp (tokens: Token[], options?: RegExpOptions): PathRegExp;
  • export function tokensToRegExp (tokens: Token[], keys?: Key[], options?: RegExpOptions): PathRegExp;
  • export interface Key {
  • name: string | number;
  • prefix: string;
  • delimiter: string;
  • optional: boolean;
  • repeat: boolean;
  • pattern: string;
  • partial: boolean;
  • asterisk: boolean;
  • }
  • interface PathFunctionOptions {
  • pretty?: boolean;
  • }
  • export type Token = string | Key;
  • export type Path = string | RegExp | Array<string | RegExp>;
  • export type PathFunction = (data?: Object, options?: PathFunctionOptions) => string;
    -}

-export = pathToRegexp;
diff --git index.js index.js
deleted file mode 100644
index 63e3416..0000000
--- index.js
+++ /dev/null
@@ -1,426 +0,0 @@
-var isarray = require('isarray')

-/**

    • Expose pathToRegexp.
  • */
    -module.exports = pathToRegexp
    -module.exports.parse = parse
    -module.exports.compile = compile
    -module.exports.tokensToFunction = tokensToFunction
    -module.exports.tokensToRegExp = tokensToRegExp

-/**

    • The main path matching regexp utility.
  • */
    -var PATH_REGEXP = new RegExp([
  • // Match escaped characters that would otherwise appear in future matches.
  • // This allows the user to escape special characters that won't transform.
  • '(\\.)',
  • // Match Express-style parameters and un-named parameters with a prefix
  • // and optional suffixes. Matches appear as:
  • //
  • // "/:test(\d+)?" => ["/", "test", "\d+", undefined, "?", undefined]
  • // "/route(\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined]
  • // "/" => ["/", undefined, undefined, undefined, undefined, ""]
  • '([\/.])?(?:(?:\:(\w+)(?:\(((?:\\.|[^\\\\()])+)\))?|\(((?:\\.|[^\\\\()])+)\))([+?])?|(\))'
    -].join('|'), 'g')

-/**

    • Parse a string for the raw tokens.
  • */
    -function parse (str, options) {
  • var tokens = []
  • var key = 0
  • var index = 0
  • var path = ''
  • var defaultDelimiter = options && options.delimiter || '/'
  • var res
  • while ((res = PATH_REGEXP.exec(str)) != null) {
  • var m = res[0]
  • var escaped = res[1]
  • var offset = res.index
  • path += str.slice(index, offset)
  • index = offset + m.length
  • // Ignore already escaped sequences.
  • if (escaped) {
  •  path += escaped[1]
    
  •  continue
    
  • }
  • var next = str[index]
  • var prefix = res[2]
  • var name = res[3]
  • var capture = res[4]
  • var group = res[5]
  • var modifier = res[6]
  • var asterisk = res[7]
  • // Push the current path onto the tokens.
  • if (path) {
  •  tokens.push(path)
    
  •  path = ''
    
  • }
  • var partial = prefix != null && next != null && next !== prefix
  • var repeat = modifier === '+' || modifier === '*'
  • var optional = modifier === '?' || modifier === '*'
  • var delimiter = res[2] || defaultDelimiter
  • var pattern = capture || group
  • tokens.push({
  •  name: name || key++,
    
  •  prefix: prefix || '',
    
  •  delimiter: delimiter,
    
  •  optional: optional,
    
  •  repeat: repeat,
    
  •  partial: partial,
    
  •  asterisk: !!asterisk,
    
  •  pattern: pattern ? escapeGroup(pattern) : (asterisk ? '.*' : '[^' + escapeString(delimiter) + ']+?')
    
  • })
  • }
  • // Match any characters still remaining.
  • if (index < str.length) {
  • path += str.substr(index)
  • }
  • // If the path exists, push it onto the end.
  • if (path) {
  • tokens.push(path)
  • }
  • return tokens
    -}

-/**

    • Compile a string to a template function for the path.
    • @return {!function(Object=, Object=)}
  • */
    -function compile (str, options) {
  • return tokensToFunction(parse(str, options))
    -}

-/**

    • Prettier encoding of URI path segments.
  • */
    -function encodeURIComponentPretty (str) {
  • return encodeURI(str).replace(/[/?#]/g, function (c) {
  • return '%' + c.charCodeAt(0).toString(16).toUpperCase()
  • })
    -}

-/**

    • Encode the asterisk parameter. Similar to pretty, but allows slashes.
  • */
    -function encodeAsterisk (str) {
  • return encodeURI(str).replace(/[?#]/g, function (c) {
  • return '%' + c.charCodeAt(0).toString(16).toUpperCase()
  • })
    -}

-/**

    • Expose a method for transforming tokens into the path function.
  • */
    -function tokensToFunction (tokens) {
  • // Compile all the tokens into regexps.
  • var matches = new Array(tokens.length)
  • // Compile all the patterns before compilation.
  • for (var i = 0; i < tokens.length; i++) {
  • if (typeof tokens[i] === 'object') {
  •  matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$')
    
  • }
  • }
  • return function (obj, opts) {
  • var path = ''
  • var data = obj || {}
  • var options = opts || {}
  • var encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent
  • for (var i = 0; i < tokens.length; i++) {
  •  var token = tokens[i]
    
  •  if (typeof token === 'string') {
    
  •    path += token
    
  •    continue
    
  •  }
    
  •  var value = data[token.name]
    
  •  var segment
    
  •  if (value == null) {
    
  •    if (token.optional) {
    
  •      // Prepend partial segment prefixes.
    
  •      if (token.partial) {
    
  •        path += token.prefix
    
  •      }
    
  •      continue
    
  •    } else {
    
  •      throw new TypeError('Expected "' + token.name + '" to be defined')
    
  •    }
    
  •  }
    
  •  if (isarray(value)) {
    
  •    if (!token.repeat) {
    
  •      throw new TypeError('Expected "' + token.name + '" to not repeat, but received `' + JSON.stringify(value) + '`')
    
  •    }
    
  •    if (value.length === 0) {
    
  •      if (token.optional) {
    
  •        continue
    
  •      } else {
    
  •        throw new TypeError('Expected "' + token.name + '" to not be empty')
    
  •      }
    
  •    }
    
  •    for (var j = 0; j < value.length; j++) {
    
  •      segment = encode(value[j])
    
  •      if (!matches[i].test(segment)) {
    
  •        throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received `' + JSON.stringify(segment) + '`')
    
  •      }
    
  •      path += (j === 0 ? token.prefix : token.delimiter) + segment
    
  •    }
    
  •    continue
    
  •  }
    
  •  segment = token.asterisk ? encodeAsterisk(value) : encode(value)
    
  •  if (!matches[i].test(segment)) {
    
  •    throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"')
    
  •  }
    
  •  path += token.prefix + segment
    
  • }
  • return path
  • }
    -}

-/**

    • Escape a regular expression string.
  • */
    -function escapeString (str) {
  • return str.replace(/([.+*?=^!:${}()[]|/\])/g, '\$1')
    -}

-/**

    • Escape the capturing group by escaping special characters and meaning.
  • */
    -function escapeGroup (group) {
  • return group.replace(/([=!:$/()])/g, '\$1')
    -}

-/**

    • Attach the keys as a property of the regexp.
  • */
    -function attachKeys (re, keys) {
  • re.keys = keys
  • return re
    -}

-/**

    • Get the flags for a regexp from the options.
  • */
    -function flags (options) {
  • return options.sensitive ? '' : 'i'
    -}

-/**

    • Pull out keys from a regexp.
  • */
    -function regexpToRegexp (path, keys) {
  • // Use a negative lookahead to match only capturing groups.
  • var groups = path.source.match(/((?!?)/g)
  • if (groups) {
  • for (var i = 0; i < groups.length; i++) {
  •  keys.push({
    
  •    name: i,
    
  •    prefix: null,
    
  •    delimiter: null,
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: null
    
  •  })
    
  • }
  • }
  • return attachKeys(path, keys)
    -}

-/**

    • Transform an array into a regexp.
  • */
    -function arrayToRegexp (path, keys, options) {
  • var parts = []
  • for (var i = 0; i < path.length; i++) {
  • parts.push(pathToRegexp(path[i], keys, options).source)
  • }
  • var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options))
  • return attachKeys(regexp, keys)
    -}

-/**

    • Create a path regexp from string input.
  • */
    -function stringToRegexp (path, keys, options) {
  • return tokensToRegExp(parse(path, options), keys, options)
    -}

-/**

    • Expose a function for taking tokens and returning a RegExp.
    • @param {(Array|Object)=} keys
  • */
    -function tokensToRegExp (toke,ns, keys, options) {
  • if (!isarray(keys)) {
  • options = /** @type {!Object} */ (keys || options)
  • keys = []
  • }
  • options = options || {}
  • var strict = options.strict
  • var end = options.end !== false
  • var route = ''
  • // Iterate over the tokens and create our regexp string.
  • for (var i = 0; i < tokens.length; i++) {
  • var token = tokens[i]
  • if (typeof token === 'string') {
  •  route += escapeString(token)
    
  • } else {
  •  var prefix = escapeString(token.prefix)
    
  •  var capture = '(?:' + token.pattern + ')'
    
  •  keys.push(token)
    
  •  if (token.repeat) {
    
  •    capture += '(?:' + prefix + capture + ')*'
    
  •  }
    
  •  if (token.optional) {
    
  •    if (!token.partial) {
    
  •      capture = '(?:' + prefix + '(' + capture + '))?'
    
  •    } else {
    
  •      capture = prefix + '(' + capture + ')?'
    
  •    }
    
  •  } else {
    
  •    capture = prefix + '(' + capture + ')'
    
  •  }
    
  •  route += capture
    
  • }
  • }
  • var delimiter = escapeString(options.delimiter || '/')
  • var endsWithDelimiter = route.slice(-delimiter.length) === delimiter
  • // In non-strict mode we allow a slash at the end of match. If the path to
  • // match already ends with a slash, we remove it for consistency. The slash
  • // is valid at the end of a path match, not in the middle. This is important
  • // in non-ending mode, where "/test/" shouldn't match "/test//route".
  • if (!strict) {
  • route = (endsWithDelimiter ? route.slice(0, -delimiter.length) : route) + '(?:' + delimiter + '(?=$))?'
  • }
  • if (end) {
  • route += '$'
  • } else {
  • // In non-ending mode, we need the capturing groups to match as much as
  • // possible by using a positive lookahead to the end or next path segment.
  • route += strict && endsWithDelimiter ? '' : '(?=' + delimiter + '|$)'
  • }
  • return attachKeys(new RegExp('^' + route, flags(options)), keys)
    -}

-/**

    • Normalize the given path string, returning a regular expression.
    • An empty array can be passed in for the keys, which will hold the
    • placeholder key descriptions. For example, using /user/:id, keys will
    • contain [{ name: 'id', delimiter: '/', optional: false, repeat: false }].
    • @param {(string|RegExp|Array)} path
    • @param {(Array|Object)=} keys
  • */
    -function pathToRegexp (path, keys, options) {
  • if (!isarray(keys)) {
  • options = /** @type {!Object} */ (keys || options)
  • keys = []
  • }
  • options = options || {}
  • if (path instanceof RegExp) {
  • return regexpToRegexp(path, /** @type {!Array} */ (keys))
  • }
  • if (isarray(path)) {
  • return arrayToRegexp(/** @type {!Array} / (path), /* @type {!Array} */ (keys), options)
  • }
  • return stringToRegexp(/** @type {string} / (path), /* @type {!Array} */ (keys), options)
    -}
    diff --git a/package-lock.json b/package-lock.json
    new file mode 100644
    index 0000000..a582d4b
    diff --git package.json package.json
    index e8908f7..f6806a5 100644
    --- package.json
    +++ package.json
    @@ -1,47 +1,62 @@
    {
    "name": "path-to-regexp",
  • "version": "8.0.0",
    "description": "Express style path to RegExp utility",
  • "version": "1.7.0",
  • "main": "index.js",
  • "typings": "index.d.ts",
  • "files": [
  • "index.js",
  • "index.d.ts",
  • "LICENSE"
  • ],
  • "scripts": {
  • "lint": "standard",
  • "test-spec": "mocha --require ts-node/register -R spec --bail test.ts",
  • "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require ts-node/register -R spec test.ts",
  • "prepublish": "typings install",
  • "test": "npm run lint && npm run test-cov"
  • },
    "keywords": [
    "express",
    "regexp",
    "route",
    "routing"
    ],
  • "component": {
  • "scripts": {
  •  "path-to-regexp": "index.js"
    
  • }
  • },
  • "license": "MIT",
    "repository": {
    "type": "git",
    "url": "https://github.com/pillarjs/path-to-regexp.git"
    },
  • "license": "MIT",
  • "exports": "./dist/index.js",
  • "main": "dist/index.js",
  • "typings": "dist/index.d.ts",
  • "files": [
  • "dist/"
  • ],
  • "scripts": {
  • "bench": "vitest bench",
  • "build": "ts-scripts build",
  • "format": "ts-scripts format",
  • "lint": "ts-scripts lint",
  • "prepare": "ts-scripts install && npm run build",
  • "size": "size-limit",
  • "specs": "ts-scripts specs",
  • "test": "ts-scripts test && npm run size"
  • },
    "devDependencies": {
  • "chai": "^2.3.0",
  • "istanbul": "~0.3.0",
  • "mocha": "~2.2.4",
  • "standard": "~3.7.3",
  • "ts-node": "^0.5.5",
  • "typescript": "^1.8.7",
  • "typings": "^1.0.4"
  • "@borderless/ts-scripts": "^0.15.0",
  • "@size-limit/preset-small-lib": "^11.1.2",
  • "@types/node": "^20.4.9",
  • "@types/semver": "^7.3.1",
  • "@vitest/coverage-v8": "^1.4.0",
  • "recheck": "^4.4.5",
  • "size-limit": "^11.1.2",
  • "typescript": "^5.5.3"
    },
  • "dependencies": {
  • "isarray": "0.0.1"
  • "engines": {
  • "node": ">=16"
  • },
  • "publishConfig": {
  • "access": "public"
  • },
  • "size-limit": [
  • {
  •  "path": "dist/index.js",
    
  •  "limit": "2.2 kB"
    
  • }
  • ],
  • "ts-scripts": {
  • "dist": [
  •  "dist"
    
  • ],
  • "project": [
  •  "tsconfig.build.json"
    
  • ]
    }
    }
    diff --git a/scripts/redos.ts b/scripts/redos.ts
    new file mode 100644
    index 0000000..841cd07
    --- /dev/null
    +++ scripts/redos.ts
    @@ -0,0 +1,22 @@
    +import { checkSync } from "recheck";
    +import { match } from "../src/index.js";
    +import { MATCH_TESTS } from "../src/cases.spec.js";

+let safe = 0;
+let fail = 0;
+
+const TESTS = MATCH_TESTS.map((x) => x.path);
+
+for (const path of TESTS) {

  • const { re } = match(path) as any;
  • const result = checkSync(re.source, re.flags);
  • if (result.status === "safe") {
  • safe++;
  • console.log("Safe:", path, String(re));
  • } else {
  • fail++;
  • console.log("Fail:", path, String(re));
  • }
    +}

+console.log("Safe:", safe, "Fail:", fail);
diff --git a/src/cases.spec.ts b/src/cases.spec.ts
new file mode 100644
index 0000000..ef06e1f
--- /dev/null
+++ src/cases.spec.ts
@@ -0,0 +1,1525 @@
+import type {

  • MatchOptions,
  • Match,
  • ParseOptions,
  • Token,
  • CompileOptions,
  • ParamData,
    +} from "./index.js";

+export interface ParserTestSet {

  • path: string;
  • options?: ParseOptions;
  • expected: Token[];
    +}

+export interface CompileTestSet {

  • path: string;
  • options?: CompileOptions & ParseOptions;
  • tests: Array<{
  • input: ParamData | undefined;
  • expected: string | null;
  • }>;
    +}

+export interface MatchTestSet {

  • path: string;
  • options?: MatchOptions & ParseOptions;
  • tests: Array<{
  • input: string;
  • expected: Match;
  • }>;
    +}

+export const PARSER_TESTS: ParserTestSet[] = [

  • {
  • path: "/",
  • expected: [{ type: "text", value: "/" }],
  • },
  • {
  • path: "/:test",
  • expected: [
  •  { type: "text", value: "/" },
    
  •  { type: "param", name: "test" },
    
  • ],
  • },
  • {
  • path: '/:"0"',
  • expected: [
  •  { type: "text", value: "/" },
    
  •  { type: "param", name: "0" },
    
  • ],
  • },
  • {
  • path: "/:_",
  • expected: [
  •  { type: "text", value: "/" },
    
  •  { type: "param", name: "_" },
    
  • ],
  • },
  • {
  • path: "/:café",
  • expected: [
  •  { type: "text", value: "/" },
    
  •  { type: "param", name: "café" },
    
  • ],
  • },
  • {
  • path: '/:"123"',
  • expected: [
  •  { type: "text", value: "/" },
    
  •  { type: "param", name: "123" },
    
  • ],
  • },
  • {
  • path: '/:"1\"\2\"3"',
  • expected: [
  •  { type: "text", value: "/" },
    
  •  { type: "param", name: '1"2"3' },
    
  • ],
  • },
  • {
  • path: "/*path",
  • expected: [
  •  { type: "text", value: "/" },
    
  •  { type: "wildcard", name: "path" },
    
  • ],
  • },
    +];

+export const COMPILE_TESTS: CompileTestSet[] = [

  • {
  • path: "/",
  • tests: [
  •  { input: undefined, expected: "/" },
    
  •  { input: {}, expected: "/" },
    
  •  { input: { id: "123" }, expected: "/" },
    
  • ],
  • },
  • {
  • path: "/test",
  • tests: [
  •  { input: undefined, expected: "/test" },
    
  •  { input: {}, expected: "/test" },
    
  •  { input: { id: "123" }, expected: "/test" },
    
  • ],
  • },
  • {
  • path: "/test/",
  • tests: [
  •  { input: undefined, expected: "/test/" },
    
  •  { input: {}, expected: "/test/" },
    
  •  { input: { id: "123" }, expected: "/test/" },
    
  • ],
  • },
  • {
  • path: '/:"0"',
  • tests: [
  •  { input: undefined, expected: null },
    
  •  { input: {}, expected: null },
    
  •  { input: { 0: "123" }, expected: "/123" },
    
  • ],
  • },
  • {
  • path: "/:test",
  • tests: [
  •  { input: undefined, expected: null },
    
  •  { input: {}, expected: null },
    
  •  { input: { test: "123" }, expected: "/123" },
    
  •  { input: { test: "123/xyz" }, expected: "/123%2Fxyz" },
    
  • ],
  • },
  • {
  • path: "/:test",
  • tests: [
  •  { input: undefined, expected: null },
    
  •  { input: {}, expected: null },
    
  •  { input: { test: "123" }, expected: "/123" },
    
  •  { input: { test: "123/xyz" }, expected: "/123%2Fxyz" },
    
  • ],
  • },
  • {
  • path: "/:test",
  • options: { encode: false },
  • tests: [
  •  { input: undefined, expected: null },
    
  •  { input: {}, expected: null },
    
  •  { input: { test: "123" }, expected: "/123" },
    
  •  { input: { test: "123/xyz" }, expected: "/123/xyz" },
    
  • ],
  • },
  • {
  • path: "/:test",
  • options: { encode: () => "static" },
  • tests: [
  •  { input: undefined, expected: null },
    
  •  { input: {}, expected: null },
    
  •  { input: { test: "123" }, expected: "/static" },
    
  •  { input: { test: "123/xyz" }, expected: "/static" },
    
  • ],
  • },
  • {
  • path: "{/:test}",
  • options: { encode: false },
  • tests: [
  •  { input: undefined, expected: "" },
    
  •  { input: {}, expected: "" },
    
  •  { input: { test: undefined }, expected: "" },
    
  •  { input: { test: "123" }, expected: "/123" },
    
  •  { input: { test: "123/xyz" }, expected: "/123/xyz" },
    
  • ],
  • },
  • {
  • path: "/*test",
  • tests: [
  •  { input: undefined, expected: null },
    
  •  { input: {}, expected: null },
    
  •  { input: { test: [] }, expected: null },
    
  •  { input: { test: ["123"] }, expected: "/123" },
    
  •  { input: { test: ["123", "xyz"] }, expected: "/123/xyz" },
    
  • ],
  • },
  • {
  • path: "/*test",
  • options: { encode: false },
  • tests: [
  •  { input: { test: "123" }, expected: "/123" },
    
  •  { input: { test: "123/xyz" }, expected: "/123/xyz" },
    
  • ],
  • },
    +];

+/**

    • An array of test cases with expected inputs and outputs.
  • */
    +export const MATCH_TESTS: MatchTestSet[] = [
  • /**
    • Simple paths.
  • */
  • {
  • path: "/",
  • tests: [
  •  {
    
  •    input: "/",
    
  •    expected: { path: "/", params: {} },
    
  •  },
    
  •  { input: "/route", expected: false },
    
  • ],
  • },
  • {
  • path: "/test",
  • tests: [
  •  {
    
  •    input: "/test",
    
  •    expected: { path: "/test", params: {} },
    
  •  },
    
  •  { input: "/route", expected: false },
    
  •  { input: "/test/route", expected: false },
    
  •  {
    
  •    input: "/test/",
    
  •    expected: { path: "/test/", params: {} },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/test/",
  • tests: [
  •  {
    
  •    input: "/test/",
    
  •    expected: { path: "/test/", params: {} },
    
  •  },
    
  •  { input: "/route", expected: false },
    
  •  { input: "/test", expected: false },
    
  •  {
    
  •    input: "/test//",
    
  •    expected: { path: "/test//", params: {} },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/:test",
  • tests: [
  •  {
    
  •    input: "/route",
    
  •    expected: { path: "/route", params: { test: "route" } },
    
  •  },
    
  •  {
    
  •    input: "/route/",
    
  •    expected: { path: "/route/", params: { test: "route" } },
    
  •  },
    
  •  {
    
  •    input: "/route.json",
    
  •    expected: {
    
  •      path: "/route.json",
    
  •      params: { test: "route.json" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/route.json/",
    
  •    expected: {
    
  •      path: "/route.json/",
    
  •      params: { test: "route.json" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/route/test",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "/caf%C3%A9",
    
  •    expected: {
    
  •      path: "/caf%C3%A9",
    
  •      params: { test: "café" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/;,:@&=+$-_.!~*()",
    
  •    expected: {
    
  •      path: "/;,:@&=+$-_.!~*()",
    
  •      params: { test: ";,:@&=+$-_.!~*()" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/param%2523",
    
  •    expected: {
    
  •      path: "/param%2523",
    
  •      params: { test: "param%23" },
    
  •    },
    
  •  },
    
  • ],
  • },
  • /**
    • Case-sensitive paths.
  • */
  • {
  • path: "/test",
  • options: {
  •  sensitive: true,
    
  • },
  • tests: [
  •  {
    
  •    input: "/test",
    
  •    expected: { path: "/test", params: {} },
    
  •  },
    
  •  { input: "/TEST", expected: false },
    
  • ],
  • },
  • {
  • path: "/TEST",
  • options: {
  •  sensitive: true,
    
  • },
  • tests: [
  •  {
    
  •    input: "/TEST",
    
  •    expected: { path: "/TEST", params: {} },
    
  •  },
    
  •  { input: "/test", expected: false },
    
  • ],
  • },
  • /**
    • Non-ending mode.
  • */
  • {
  • path: "/test",
  • options: {
  •  end: false,
    
  • },
  • tests: [
  •  {
    
  •    input: "/test",
    
  •    expected: { path: "/test", params: {} },
    
  •  },
    
  •  {
    
  •    input: "/test/",
    
  •    expected: { path: "/test/", params: {} },
    
  •  },
    
  •  {
    
  •    input: "/test////",
    
  •    expected: { path: "/test", params: {} },
    
  •  },
    
  •  {
    
  •    input: "/route/test",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "/test/route",
    
  •    expected: { path: "/test", params: {} },
    
  •  },
    
  •  {
    
  •    input: "/route",
    
  •    expected: false,
    
  •  },
    
  • ],
  • },
  • {
  • path: "/test/",
  • options: {
  •  end: false,
    
  • },
  • tests: [
  •  {
    
  •    input: "/test",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "/test/",
    
  •    expected: { path: "/test/", params: {} },
    
  •  },
    
  •  {
    
  •    input: "/test//",
    
  •    expected: { path: "/test//", params: {} },
    
  •  },
    
  •  {
    
  •    input: "/test/route",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "/route/test/deep",
    
  •    expected: false,
    
  •  },
    
  • ],
  • },
  • {
  • path: "/:test",
  • options: {
  •  end: false,
    
  • },
  • tests: [
  •  {
    
  •    input: "/route",
    
  •    expected: { path: "/route", params: { test: "route" } },
    
  •  },
    
  •  {
    
  •    input: "/route/",
    
  •    expected: { path: "/route/", params: { test: "route" } },
    
  •  },
    
  •  {
    
  •    input: "/route.json",
    
  •    expected: {
    
  •      path: "/route.json",
    
  •      params: { test: "route.json" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/route.json/",
    
  •    expected: {
    
  •      path: "/route.json/",
    
  •      params: { test: "route.json" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/route/test",
    
  •    expected: { path: "/route", params: { test: "route" } },
    
  •  },
    
  •  {
    
  •    input: "/route.json/test",
    
  •    expected: {
    
  •      path: "/route.json",
    
  •      params: { test: "route.json" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/caf%C3%A9",
    
  •    expected: {
    
  •      path: "/caf%C3%A9",
    
  •      params: { test: "café" },
    
  •    },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/:test/",
  • options: {
  •  end: false,
    
  • },
  • tests: [
  •  {
    
  •    input: "/route",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "/route/",
    
  •    expected: { path: "/route/", params: { test: "route" } },
    
  •  },
    
  •  {
    
  •    input: "/route/test",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "/route/test/",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "/route//test",
    
  •    expected: { path: "/route/", params: { test: "route" } },
    
  •  },
    
  • ],
  • },
  • {
  • path: "",
  • options: {
  •  end: false,
    
  • },
  • tests: [
  •  {
    
  •    input: "",
    
  •    expected: { path: "", params: {} },
    
  •  },
    
  •  {
    
  •    input: "/",
    
  •    expected: { path: "/", params: {} },
    
  •  },
    
  •  {
    
  •    input: "route",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "/route",
    
  •    expected: { path: "", params: {} },
    
  •  },
    
  •  {
    
  •    input: "/route/",
    
  •    expected: { path: "", params: {} },
    
  •  },
    
  • ],
  • },
  • /**
    • Optional.
  • */
  • {
  • path: "{/route}",
  • tests: [
  •  {
    
  •    input: "",
    
  •    expected: { path: "", params: {} },
    
  •  },
    
  •  {
    
  •    input: "/",
    
  •    expected: { path: "/", params: {} },
    
  •  },
    
  •  {
    
  •    input: "/foo",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "/route",
    
  •    expected: { path: "/route", params: {} },
    
  •  },
    
  • ],
  • },
  • {
  • path: "{/:test}",
  • tests: [
  •  {
    
  •    input: "/route",
    
  •    expected: { path: "/route", params: { test: "route" } },
    
  •  },
    
  •  {
    
  •    input: "",
    
  •    expected: { path: "", params: {} },
    
  •  },
    
  •  {
    
  •    input: "/",
    
  •    expected: { path: "/", params: {} },
    
  •  },
    
  • ],
  • },
  • {
  • path: "{/:test}/bar",
  • tests: [
  •  {
    
  •    input: "/bar",
    
  •    expected: { path: "/bar", params: {} },
    
  •  },
    
  •  {
    
  •    input: "/foo/bar",
    
  •    expected: { path: "/foo/bar", params: { test: "foo" } },
    
  •  },
    
  •  {
    
  •    input: "/foo/bar/",
    
  •    expected: { path: "/foo/bar/", params: { test: "foo" } },
    
  •  },
    
  • ],
  • },
  • {
  • path: "{/:test}-bar",
  • tests: [
  •  {
    
  •    input: "-bar",
    
  •    expected: { path: "-bar", params: {} },
    
  •  },
    
  •  {
    
  •    input: "/foo-bar",
    
  •    expected: { path: "/foo-bar", params: { test: "foo" } },
    
  •  },
    
  •  {
    
  •    input: "/foo-bar/",
    
  •    expected: { path: "/foo-bar/", params: { test: "foo" } },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/{:test}-bar",
  • tests: [
  •  {
    
  •    input: "/-bar",
    
  •    expected: { path: "/-bar", params: {} },
    
  •  },
    
  •  {
    
  •    input: "/foo-bar",
    
  •    expected: { path: "/foo-bar", params: { test: "foo" } },
    
  •  },
    
  •  {
    
  •    input: "/foo-bar/",
    
  •    expected: { path: "/foo-bar/", params: { test: "foo" } },
    
  •  },
    
  • ],
  • },
  • /**
    • No prefix characters.
  • */
  • {
  • path: "test",
  • tests: [
  •  {
    
  •    input: "test",
    
  •    expected: { path: "test", params: {} },
    
  •  },
    
  •  {
    
  •    input: "/test",
    
  •    expected: false,
    
  •  },
    
  • ],
  • },
  • {
  • path: ":test",
  • tests: [
  •  {
    
  •    input: "route",
    
  •    expected: { path: "route", params: { test: "route" } },
    
  •  },
    
  •  {
    
  •    input: "/route",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "route/",
    
  •    expected: { path: "route/", params: { test: "route" } },
    
  •  },
    
  • ],
  • },
  • {
  • path: "{:test}",
  • tests: [
  •  {
    
  •    input: "test",
    
  •    expected: { path: "test", params: { test: "test" } },
    
  •  },
    
  •  {
    
  •    input: "",
    
  •    expected: { path: "", params: {} },
    
  •  },
    
  • ],
  • },
  • /**
    • Formats.
  • */
  • {
  • path: "/test.json",
  • tests: [
  •  {
    
  •    input: "/test.json",
    
  •    expected: { path: "/test.json", params: {} },
    
  •  },
    
  •  {
    
  •    input: "/test",
    
  •    expected: false,
    
  •  },
    
  • ],
  • },
  • {
  • path: "/:test.json",
  • tests: [
  •  {
    
  •    input: "/.json",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "/test.json",
    
  •    expected: { path: "/test.json", params: { test: "test" } },
    
  •  },
    
  •  {
    
  •    input: "/route.json",
    
  •    expected: { path: "/route.json", params: { test: "route" } },
    
  •  },
    
  •  {
    
  •    input: "/route.json.json",
    
  •    expected: { path: "/route.json.json", params: { test: "route.json" } },
    
  •  },
    
  • ],
  • },
  • /**
    • Format and path params.
  • */
  • {
  • path: "/:test.:format",
  • tests: [
  •  {
    
  •    input: "/route.html",
    
  •    expected: {
    
  •      path: "/route.html",
    
  •      params: { test: "route", format: "html" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/route",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "/route.html.json",
    
  •    expected: {
    
  •      path: "/route.html.json",
    
  •      params: { test: "route.html", format: "json" },
    
  •    },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/:test{.:format}",
  • tests: [
  •  {
    
  •    input: "/route",
    
  •    expected: { path: "/route", params: { test: "route" } },
    
  •  },
    
  •  {
    
  •    input: "/route.json",
    
  •    expected: {
    
  •      path: "/route.json",
    
  •      params: { test: "route", format: "json" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/route.json.html",
    
  •    expected: {
    
  •      path: "/route.json.html",
    
  •      params: { test: "route.json", format: "html" },
    
  •    },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/:test.:format\z",
  • tests: [
  •  {
    
  •    input: "/route.htmlz",
    
  •    expected: {
    
  •      path: "/route.htmlz",
    
  •      params: { test: "route", format: "html" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/route.html",
    
  •    expected: false,
    
  •  },
    
  • ],
  • },
  • /**
    • Escaped characters.
  • */
  • {
  • path: "/\(testing\)",
  • tests: [
  •  {
    
  •    input: "/testing",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "/(testing)",
    
  •    expected: { path: "/(testing)", params: {} },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/.\+\*\?\{\}=^\!\:$\[\]\|",
  • tests: [
  •  {
    
  •    input: "/.+*?{}=^!:$[]|",
    
  •    expected: { path: "/.+*?{}=^!:$[]|", params: {} },
    
  •  },
    
  • ],
  • },
  • /**
    • Random examples.
  • */
  • {
  • path: "/:foo/:bar",
  • tests: [
  •  {
    
  •    input: "/match/route",
    
  •    expected: {
    
  •      path: "/match/route",
    
  •      params: { foo: "match", bar: "route" },
    
  •    },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/:foo\(test\)/bar",
  • tests: [
  •  {
    
  •    input: "/foo(test)/bar",
    
  •    expected: { path: "/foo(test)/bar", params: { foo: "foo" } },
    
  •  },
    
  •  {
    
  •    input: "/foo/bar",
    
  •    expected: false,
    
  •  },
    
  • ],
  • },
  • {
  • path: "/:foo\?",
  • tests: [
  •  {
    
  •    input: "/route?",
    
  •    expected: { path: "/route?", params: { foo: "route" } },
    
  •  },
    
  •  {
    
  •    input: "/route",
    
  •    expected: false,
    
  •  },
    
  • ],
  • },
  • {
  • path: "/{:pre}baz",
  • tests: [
  •  {
    
  •    input: "/foobaz",
    
  •    expected: { path: "/foobaz", params: { pre: "foo" } },
    
  •  },
    
  •  {
    
  •    input: "/baz",
    
  •    expected: { path: "/baz", params: { pre: undefined } },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/:foo\(:bar\)",
  • tests: [
  •  {
    
  •    input: "/hello(world)",
    
  •    expected: {
    
  •      path: "/hello(world)",
    
  •      params: { foo: "hello", bar: "world" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/hello()",
    
  •    expected: false,
    
  •  },
    
  • ],
  • },
  • {
  • path: "/:foo\({:bar}\)",
  • tests: [
  •  {
    
  •    input: "/hello(world)",
    
  •    expected: {
    
  •      path: "/hello(world)",
    
  •      params: { foo: "hello", bar: "world" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/hello()",
    
  •    expected: {
    
  •      path: "/hello()",
    
  •      params: { foo: "hello", bar: undefined },
    
  •    },
    
  •  },
    
  • ],
  • },
  • {
  • path: "{/:foo}{/:bar}-ext",
  • tests: [
  •  {
    
  •    input: "/-ext",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "-ext",
    
  •    expected: {
    
  •      path: "-ext",
    
  •      params: { foo: undefined, bar: undefined },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/foo-ext",
    
  •    expected: { path: "/foo-ext", params: { foo: "foo" } },
    
  •  },
    
  •  {
    
  •    input: "/foo/bar-ext",
    
  •    expected: {
    
  •      path: "/foo/bar-ext",
    
  •      params: { foo: "foo", bar: "bar" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/foo/-ext",
    
  •    expected: false,
    
  •  },
    
  • ],
  • },
  • {
  • path: "/:required{/:optional}-ext",
  • tests: [
  •  {
    
  •    input: "/foo-ext",
    
  •    expected: { path: "/foo-ext", params: { required: "foo" } },
    
  •  },
    
  •  {
    
  •    input: "/foo/bar-ext",
    
  •    expected: {
    
  •      path: "/foo/bar-ext",
    
  •      params: { required: "foo", optional: "bar" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/foo/-ext",
    
  •    expected: false,
    
  •  },
    
  • ],
  • },
  • /**
    • Unicode matches.
  • */
  • {
  • path: "/:foo",
  • tests: [
  •  {
    
  •    input: "/café",
    
  •    expected: { path: "/café", params: { foo: "café" } },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/:foo",
  • options: {
  •  decode: false,
    
  • },
  • tests: [
  •  {
    
  •    input: "/caf%C3%A9",
    
  •    expected: {
    
  •      path: "/caf%C3%A9",
    
  •      params: { foo: "caf%C3%A9" },
    
  •    },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/café",
  • tests: [
  •  {
    
  •    input: "/café",
    
  •    expected: { path: "/café", params: {} },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/café",
  • options: {
  •  encodePath: encodeURI,
    
  • },
  • tests: [
  •  {
    
  •    input: "/caf%C3%A9",
    
  •    expected: { path: "/caf%C3%A9", params: {} },
    
  •  },
    
  • ],
  • },
  • /**
    • Hostnames.
  • */
  • {
  • path: ":domain.com",
  • options: {
  •  delimiter: ".",
    
  • },
  • tests: [
  •  {
    
  •    input: "example.com",
    
  •    expected: {
    
  •      path: "example.com",
    
  •      params: { domain: "example" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "github.com",
    
  •    expected: {
    
  •      path: "github.com",
    
  •      params: { domain: "github" },
    
  •    },
    
  •  },
    
  • ],
  • },
  • {
  • path: "mail.:domain.com",
  • options: {
  •  delimiter: ".",
    
  • },
  • tests: [
  •  {
    
  •    input: "mail.example.com",
    
  •    expected: {
    
  •      path: "mail.example.com",
    
  •      params: { domain: "example" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "mail.github.com",
    
  •    expected: {
    
  •      path: "mail.github.com",
    
  •      params: { domain: "github" },
    
  •    },
    
  •  },
    
  • ],
  • },
  • {
  • path: "mail{.:domain}.com",
  • options: {
  •  delimiter: ".",
    
  • },
  • tests: [
  •  {
    
  •    input: "mail.com",
    
  •    expected: { path: "mail.com", params: { domain: undefined } },
    
  •  },
    
  •  {
    
  •    input: "mail.example.com",
    
  •    expected: {
    
  •      path: "mail.example.com",
    
  •      params: { domain: "example" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "mail.github.com",
    
  •    expected: {
    
  •      path: "mail.github.com",
    
  •      params: { domain: "github" },
    
  •    },
    
  •  },
    
  • ],
  • },
  • {
  • path: "example.:ext",
  • options: {
  •  delimiter: ".",
    
  • },
  • tests: [
  •  {
    
  •    input: "example.com",
    
  •    expected: { path: "example.com", params: { ext: "com" } },
    
  •  },
    
  •  {
    
  •    input: "example.org",
    
  •    expected: { path: "example.org", params: { ext: "org" } },
    
  •  },
    
  • ],
  • },
  • {
  • path: "this is",
  • options: {
  •  delimiter: " ",
    
  •  end: false,
    
  • },
  • tests: [
  •  {
    
  •    input: "this is a test",
    
  •    expected: { path: "this is", params: {} },
    
  •  },
    
  •  {
    
  •    input: "this isn't",
    
  •    expected: false,
    
  •  },
    
  • ],
  • },
  • /**
    • Prefixes.
  • */
  • {
  • path: "$:foo{$:bar}",
  • tests: [
  •  {
    
  •    input: "$x",
    
  •    expected: { path: "$x", params: { foo: "x" } },
    
  •  },
    
  •  {
    
  •    input: "$x$y",
    
  •    expected: { path: "$x$y", params: { foo: "x", bar: "y" } },
    
  •  },
    
  • ],
  • },
  • {
  • path: "name{/:attr1}{-:attr2}{-:attr3}",
  • tests: [
  •  {
    
  •    input: "name",
    
  •    expected: { path: "name", params: {} },
    
  •  },
    
  •  {
    
  •    input: "name/test",
    
  •    expected: {
    
  •      path: "name/test",
    
  •      params: { attr1: "test" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "name/1",
    
  •    expected: {
    
  •      path: "name/1",
    
  •      params: { attr1: "1" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "name/1-2",
    
  •    expected: {
    
  •      path: "name/1-2",
    
  •      params: { attr1: "1", attr2: "2" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "name/1-2-3",
    
  •    expected: {
    
  •      path: "name/1-2-3",
    
  •      params: { attr1: "1", attr2: "2", attr3: "3" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "name/foo-bar/route",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "name/test/route",
    
  •    expected: false,
    
  •  },
    
  • ],
  • },
  • /**
  • */
  • {
  • path: "/user{s}/:user",
  • tests: [
  •  {
    
  •    input: "/user/123",
    
  •    expected: { path: "/user/123", params: { user: "123" } },
    
  •  },
    
  •  {
    
  •    input: "/users/123",
    
  •    expected: { path: "/users/123", params: { user: "123" } },
    
  •  },
    
  • ],
  • },
  • /**
    • Wildcard.
  • */
  • {
  • path: "/*path",
  • tests: [
  •  {
    
  •    input: "/",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "/route",
    
  •    expected: { path: "/route", params: { path: ["route"] } },
    
  •  },
    
  •  {
    
  •    input: "/route/nested",
    
  •    expected: {
    
  •      path: "/route/nested",
    
  •      params: { path: ["route", "nested"] },
    
  •    },
    
  •  },
    
  • ],
  • },
  • {
  • path: "*path",
  • tests: [
  •  {
    
  •    input: "/",
    
  •    expected: { path: "/", params: { path: ["", ""] } },
    
  •  },
    
  •  {
    
  •    input: "/test",
    
  •    expected: { path: "/test", params: { path: ["", "test"] } },
    
  •  },
    
  • ],
  • },
  • {
  • path: "*path",
  • options: { decode: false },
  • tests: [
  •  {
    
  •    input: "/",
    
  •    expected: { path: "/", params: { path: "/" } },
    
  •  },
    
  •  {
    
  •    input: "/test",
    
  •    expected: { path: "/test", params: { path: "/test" } },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/*path.:ext",
  • tests: [
  •  {
    
  •    input: "/test.html",
    
  •    expected: {
    
  •      path: "/test.html",
    
  •      params: { path: ["test"], ext: "html" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/test.html/nested",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "/test.html/nested.json",
    
  •    expected: {
    
  •      path: "/test.html/nested.json",
    
  •      params: { path: ["test.html", "nested"], ext: "json" },
    
  •    },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/:path.*ext",
  • tests: [
  •  {
    
  •    input: "/test.html",
    
  •    expected: {
    
  •      path: "/test.html",
    
  •      params: { path: "test", ext: ["html"] },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/test.html/nested",
    
  •    expected: {
    
  •      path: "/test.html/nested",
    
  •      params: { path: "test", ext: ["html", "nested"] },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/test.html/nested.json",
    
  •    expected: {
    
  •      path: "/test.html/nested.json",
    
  •      params: { path: "test", ext: ["html", "nested.json"] },
    
  •    },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/*path{.:ext}",
  • tests: [
  •  {
    
  •    input: "/test.html",
    
  •    expected: {
    
  •      path: "/test.html",
    
  •      params: { path: ["test"], ext: "html" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/test.html/nested",
    
  •    expected: {
    
  •      params: {
    
  •        path: ["test.html", "nested"],
    
  •      },
    
  •      path: "/test.html/nested",
    
  •    },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/entity/:id/*path",
  • tests: [
  •  {
    
  •    input: "/entity/foo",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "/entity/foo/path",
    
  •    expected: {
    
  •      path: "/entity/foo/path",
    
  •      params: { id: "foo", path: ["path"] },
    
  •    },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/*foo/:bar/*baz",
  • tests: [
  •  {
    
  •    input: "/x/y/z",
    
  •    expected: {
    
  •      path: "/x/y/z",
    
  •      params: { foo: ["x"], bar: "y", baz: ["z"] },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/1/2/3/4/5",
    
  •    expected: {
    
  •      path: "/1/2/3/4/5",
    
  •      params: { foo: ["1", "2", "3"], bar: "4", baz: ["5"] },
    
  •    },
    
  •  },
    
  • ],
  • },
  • /**
    • Longer prefix.
  • */
  • {
  • path: "/:foo{/test/:bar}",
  • tests: [
  •  {
    
  •    input: "/route",
    
  •    expected: { path: "/route", params: { foo: "route" } },
    
  •  },
    
  •  {
    
  •    input: "/route/test/again",
    
  •    expected: {
    
  •      path: "/route/test/again",
    
  •      params: { foo: "route", bar: "again" },
    
  •    },
    
  •  },
    
  • ],
  • },
  • /**
    • Backtracking tests.
  • */
  • {
  • path: "{:foo/}{:bar.}",
  • tests: [
  •  {
    
  •    input: "",
    
  •    expected: { path: "", params: {} },
    
  •  },
    
  •  {
    
  •    input: "test/",
    
  •    expected: {
    
  •      path: "test/",
    
  •      params: { foo: "test" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "a/b.",
    
  •    expected: { path: "a/b.", params: { foo: "a", bar: "b" } },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/abc{abc:foo}",
  • tests: [
  •  {
    
  •    input: "/abc",
    
  •    expected: { path: "/abc", params: {} },
    
  •  },
    
  •  {
    
  •    input: "/abcabc",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "/abcabc123",
    
  •    expected: { path: "/abcabc123", params: { foo: "123" } },
    
  •  },
    
  •  {
    
  •    input: "/abcabcabc123",
    
  •    expected: {
    
  •      path: "/abcabcabc123",
    
  •      params: { foo: "abc123" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/abcabcabc",
    
  •    expected: { path: "/abcabcabc", params: { foo: "abc" } },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/:foo{abc:bar}",
  • tests: [
  •  {
    
  •    input: "/abc",
    
  •    expected: {
    
  •      params: { foo: "abc" },
    
  •      path: "/abc",
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/abcabc",
    
  •    expected: {
    
  •      params: { foo: "abcabc" },
    
  •      path: "/abcabc",
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/abcabc123",
    
  •    expected: {
    
  •      params: { foo: "abc", bar: "123" },
    
  •      path: "/abcabc123",
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/acb",
    
  •    expected: {
    
  •      path: "/acb",
    
  •      params: { foo: "acb" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/123",
    
  •    expected: {
    
  •      path: "/123",
    
  •      params: { foo: "123" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/123abcabc",
    
  •    expected: {
    
  •      path: "/123abcabc",
    
  •      params: { foo: "123abcabc" },
    
  •    },
    
  •  },
    
  • ],
  • },
  • {
  • path: "/:foo\abc:bar",
  • tests: [
  •  {
    
  •    input: "/abc",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "/abcabc",
    
  •    expected: false,
    
  •  },
    
  •  {
    
  •    input: "/abcabc123",
    
  •    expected: {
    
  •      path: "/abcabc123",
    
  •      params: { foo: "abc", bar: "123" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/123abcabc",
    
  •    expected: false,
    
  •  },
    
  • ],
  • },
  • {
  • path: "/route|:param|",
  • tests: [
  •  {
    
  •    input: "/route|world|",
    
  •    expected: {
    
  •      path: "/route|world|",
    
  •      params: { param: "world" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/route||",
    
  •    expected: false,
    
  •  },
    
  • ],
  • },
  • {
  • path: "/:foo|:bar|",
  • tests: [
  •  {
    
  •    input: "/hello|world|",
    
  •    expected: {
    
  •      path: "/hello|world|",
    
  •      params: { foo: "hello", bar: "world" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/hello||",
    
  •    expected: false,
    
  •  },
    
  • ],
  • },
  • {
  • path: "/:foo{|:bar|}",
  • tests: [
  •  {
    
  •    input: "/hello|world|",
    
  •    expected: {
    
  •      path: "/hello|world|",
    
  •      params: { foo: "hello", bar: "world" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "/hello||",
    
  •    expected: { path: "/hello||", params: { foo: "hello||" } },
    
  •  },
    
  • ],
  • },
  • {
  • path: ":foo\@:bar",
  • tests: [
  •  {
    
  •    input: "x@y",
    
  •    expected: { path: "x@y", params: { foo: "x", bar: "y" } },
    
  •  },
    
  •  {
    
  •    input: "x@",
    
  •    expected: false,
    
  •  },
    
  • ],
  • },
  • /**
    • Multi character delimiters.
  • */
  • {
  • path: "%25:foo{%25:bar}",
  • options: {
  •  delimiter: "%25",
    
  • },
  • tests: [
  •  {
    
  •    input: "%25hello",
    
  •    expected: { path: "%25hello", params: { foo: "hello" } },
    
  •  },
    
  •  {
    
  •    input: "%25hello%25world",
    
  •    expected: {
    
  •      path: "%25hello%25world",
    
  •      params: { foo: "hello", bar: "world" },
    
  •    },
    
  •  },
    
  •  {
    
  •    input: "%25555%25222",
    
  •    expected: {
    
  •      path: "%25555%25222",
    
  •      params: { foo: "555", bar: "222" },
    
  •    },
    
  •  },
    
  • ],
  • },
    +];
    diff --git a/src/index.bench.ts b/src/index.bench.ts
    new file mode 100644
    index 0000000..9d39219
    --- /dev/null
    +++ src/index.bench.ts
    @@ -0,0 +1,42 @@
    +import { bench } from "vitest";
    +import { match } from "./index.js";

+const PATHS: string[] = [

  • "/xyz",
  • "/user",
  • "/user/123",
  • "/" + "a".repeat(32_000),
  • "/-" + "-a".repeat(8_000) + "/-",
  • "/||||\x00|" + "||".repeat(27387) + "|\x00".repeat(27387) + "/||/",
    +];

+const STATIC_PATH_MATCH = match("/user");
+const SIMPLE_PATH_MATCH = match("/user/:id");
+const MULTI_SEGMENT_MATCH = match("/:x/:y");
+const MULTI_PATTERN_MATCH = match("/:x-:y");
+const TRICKY_PATTERN_MATCH = match("/:foo|:bar|");
+const ASTERISK_MATCH = match("/*foo");
+
+bench("static path", () => {

  • for (const path of PATHS) STATIC_PATH_MATCH(path);
    +});

+bench("simple path", () => {

  • for (const path of PATHS) SIMPLE_PATH_MATCH(path);
    +});

+bench("multi segment", () => {

  • for (const path of PATHS) MULTI_SEGMENT_MATCH(path);
    +});

+bench("multi pattern", () => {

  • for (const path of PATHS) MULTI_PATTERN_MATCH(path);
    +});

+bench("tricky pattern", () => {

  • for (const path of PATHS) TRICKY_PATTERN_MATCH(path);
    +});

+bench("asterisk", () => {

  • for (const path of PATHS) ASTERISK_MATCH(path);
    +});
    diff --git a/src/index.spec.ts b/src/index.spec.ts
    new file mode 100644
    index 0000000..c6da631
    --- /dev/null
    +++ src/index.spec.ts
    @@ -0,0 +1,126 @@
    +import { describe, it, expect } from "vitest";
    +import { parse, compile, match } from "./index.js";
    +import { PARSER_TESTS, COMPILE_TESTS, MATCH_TESTS } from "./cases.spec.js";

+/**

    • Dynamically generate the entire test suite.
  • */
    +describe("path-to-regexp", () => {
  • describe("parse errors", () => {
  • it("should throw on unbalanced group", () => {
  •  expect(() => parse("/{:foo,")).toThrow(
    
  •    new TypeError(
    
  •      "Unexpected END at 7, expected }: https://git.new/pathToRegexpError",
    
  •    ),
    
  •  );
    
  • });
  • it("should throw on nested unbalanced group", () => {
  •  expect(() => parse("/{:foo/{x,y}")).toThrow(
    
  •    new TypeError(
    
  •      "Unexpected END at 12, expected }: https://git.new/pathToRegexpError",
    
  •    ),
    
  •  );
    
  • });
  • it("should throw on missing param name", () => {
  •  expect(() => parse("/:/")).toThrow(
    
  •    new TypeError(
    
  •      "Missing parameter name at 2: https://git.new/pathToRegexpError",
    
  •    ),
    
  •  );
    
  • });
  • it("should throw on missing wildcard name", () => {
  •  expect(() => parse("/*/")).toThrow(
    
  •    new TypeError(
    
  •      "Missing parameter name at 2: https://git.new/pathToRegexpError",
    
  •    ),
    
  •  );
    
  • });
  • it("should throw on unterminated quote", () => {
  •  expect(() => parse('/:"foo')).toThrow(
    
  •    new TypeError(
    
  •      "Unterminated quote at 2: https://git.new/pathToRegexpError",
    
  •    ),
    
  •  );
    
  • });
  • });
  • describe("compile errors", () => {
  • it("should throw when a param is missing", () => {
  •  const toPath = compile("/a/:b/c");
    
  •  expect(() => {
    
  •    toPath();
    
  •  }).toThrow(new TypeError("Missing parameters: b"));
    
  • });
  • it("should throw when expecting a repeated value", () => {
  •  const toPath = compile("/*foo");
    
  •  expect(() => {
    
  •    toPath({ foo: [] });
    
  •  }).toThrow(new TypeError('Expected "foo" to be a non-empty array'));
    
  • });
  • it("should throw when param gets an array", () => {
  •  const toPath = compile("/:foo");
    
  •  expect(() => {
    
  •    toPath({ foo: [] });
    
  •  }).toThrow(new TypeError('Expected "foo" to be a string'));
    
  • });
  • it("should throw when a wildcard is not an array", () => {
  •  const toPath = compile("/*foo");
    
  •  expect(() => {
    
  •    toPath({ foo: "a" });
    
  •  }).toThrow(new TypeError('Expected "foo" to be a non-empty array'));
    
  • });
  • it("should throw when a wildcard array value is not a string", () => {
  •  const toPath = compile("/*foo");
    
  •  expect(() => {
    
  •    toPath({ foo: [1, "a"] as any });
    
  •  }).toThrow(new TypeError('Expected "foo/0" to be a string'));
    
  • });
  • });
  • describe.each(PARSER_TESTS)(
  • "parse $path with $options",
  • ({ path, options, expected }) => {
  •  it("should parse the path", () => {
    
  •    const data = parse(path, options);
    
  •    expect(data.tokens).toEqual(expected);
    
  •  });
    
  • },
  • );
  • describe.each(COMPILE_TESTS)(
  • "compile $path with $options",
  • ({ path, options, tests }) => {
  •  it.each(tests)("should compile $input", ({ input, expected }) => {
    
  •    const toPath = compile(path, options);
    
  •    if (expected === null) {
    
  •      expect(() => toPath(input)).toThrow();
    
  •    } else {
    
  •      expect(toPath(input)).toEqual(expected);
    
  •    }
    
  •  });
    
  • },
  • );
  • describe.each(MATCH_TESTS)(
  • "match $path with $options",
  • ({ path, options, tests }) => {
  •  it.each(tests)("should match $input", ({ input, expected }) => {
    
  •    const fn = match(path, options);
    
  •    expect(fn(input)).toEqual(expected);
    
  •  });
    
  • },
  • );
    +});
    diff --git a/src/index.ts b/src/index.ts
    new file mode 100644
    index 0000000..a63e365
    --- /dev/null
    +++ src/index.ts
    @@ -0,0 +1,597 @@
    +const DEFAULT_DELIMITER = "/";
    +const NOOP_VALUE = (value: string) => value;
    +const ID_START = /^[$_\p{ID_Start}]$/u;
    +const ID_CONTINUE = /^[$\u200c\u200d\p{ID_Continue}]$/u;
    +const DEBUG_URL = "https://git.new/pathToRegexpError";

+/**

    • Encode a string into another string.
  • */
    +export type Encode = (value: string) => string;

+/**

    • Decode a string into another string.
  • */
    +export type Decode = (value: string) => string;

+export interface ParseOptions {

  • /**
    • A function for encoding input strings.
  • */
  • encodePath?: Encode;
    +}

+export interface MatchOptions {

  • /**
    • Function for decoding strings for params, or false to disable entirely. (default: decodeURIComponent)
  • */
  • decode?: Decode | false;
  • /**
    • Matches the path completely without trailing characters. (default: true)
  • */
  • end?: boolean;
  • /**
    • Allows optional trailing delimiter to match. (default: true)
  • */
  • trailing?: boolean;
  • /**
    • Match will be case sensitive. (default: false)
  • */
  • sensitive?: boolean;
  • /**
    • The default delimiter for segments. (default: '/')
  • */
  • delimiter?: string;
    +}

+export interface CompileOptions {

  • /**
    • Function for encoding input strings for output into the path, or false to disable entirely. (default: encodeURIComponent)
  • */
  • encode?: Encode | false;
  • /**
    • The default delimiter for segments. (default: '/')
  • */
  • delimiter?: string;
    +}

+type TokenType =

  • | "{"
  • | "}"
  • | "WILDCARD"
  • | "PARAM"
  • | "CHAR"
  • | "ESCAPED"
  • | "END"
  • // Reserved for use or ambiguous due to past use.
  • | "("
  • | ")"
  • | "["
  • | "]"
  • | "+"
  • | "?"
  • | "!";

+/**

    • Tokenizer results.
  • */
    +interface LexToken {
  • type: TokenType;
  • index: number;
  • value: string;
    +}

+const SIMPLE_TOKENS: Record<string, TokenType> = {

  • // Groups.
  • "{": "{",
  • "}": "}",
  • // Reserved.
  • "(": "(",
  • ")": ")",
  • "[": "[",
  • "]": "]",
  • "+": "+",
  • "?": "?",
  • "!": "!",
    +};

+/**

    • Escape a regular expression string.
  • */
    +function escape(str: string) {
  • return str.replace(/[.+*?^${}()[]|/\]/g, "\$&");
    +}

+/**

    • Get the flags for a regexp from the options.
  • */
    +function toFlags(options: { sensitive?: boolean }) {
  • return options.sensitive ? "s" : "is";
    +}

+/**

    • Tokenize input string.
  • /
    +function
    lexer(str: string): Generator<LexToken, LexToken> {
  • const chars = [...str];
  • let i = 0;
  • function name() {
  • let value = "";
  • if (ID_START.test(chars[++i])) {
  •  value += chars[i];
    
  •  while (ID_CONTINUE.test(chars[++i])) {
    
  •    value += chars[i];
    
  •  }
    
  • } else if (chars[i] === '"') {
  •  let pos = i;
    
  •  while (i < chars.length) {
    
  •    if (chars[++i] === '"') {
    
  •      i++;
    
  •      pos = 0;
    
  •      break;
    
  •    }
    
  •    if (chars[i] === "\\") {
    
  •      value += chars[++i];
    
  •    } else {
    
  •      value += chars[i];
    
  •    }
    
  •  }
    
  •  if (pos) {
    
  •    throw new TypeError(`Unterminated quote at ${pos}: ${DEBUG_URL}`);
    
  •  }
    
  • }
  • if (!value) {
  •  throw new TypeError(`Missing parameter name at ${i}: ${DEBUG_URL}`);
    
  • }
  • return value;
  • }
  • while (i < chars.length) {
  • const value = chars[i];
  • const type = SIMPLE_TOKENS[value];
  • if (type) {
  •  yield { type, index: i++, value };
    
  • } else if (value === "\") {
  •  yield { type: "ESCAPED", index: i++, value: chars[i++] };
    
  • } else if (value === ":") {
  •  const value = name();
    
  •  yield { type: "PARAM", index: i, value };
    
  • } else if (value === "*") {
  •  const value = name();
    
  •  yield { type: "WILDCARD", index: i, value };
    
  • } else {
  •  yield { type: "CHAR", index: i, value: chars[i++] };
    
  • }
  • }
  • return { type: "END", index: i, value: "" };
    +}

+class Iter {

  • #peek?: LexToken;
  • constructor(private tokens: Generator<LexToken, LexToken>) {}
  • peek(): LexToken {
  • if (!this.#peek) {
  •  const next = this.tokens.next();
    
  •  this.#peek = next.value;
    
  • }
  • return this.#peek;
  • }
  • tryConsume(type: TokenType): string | undefined {
  • const token = this.peek();
  • if (token.type !== type) return;
  • this.#peek = undefined; // Reset after consumed.
  • return token.value;
  • }
  • consume(type: TokenType): string {
  • const value = this.tryConsume(type);
  • if (value !== undefined) return value;
  • const { type: nextType, index } = this.peek();
  • throw new TypeError(
  •  `Unexpected ${nextType} at ${index}, expected ${type}: ${DEBUG_URL}`,
    
  • );
  • }
  • text(): string {
  • let result = "";
  • let value: string | undefined;
  • while ((value = this.tryConsume("CHAR") || this.tryConsume("ESCAPED"))) {
  •  result += value;
    
  • }
  • return result;
  • }
    +}

+/**

    • Plain text.
  • */
    +export interface Text {
  • type: "text";
  • value: string;
    +}

+/**

    • A parameter designed to match arbitrary text within a segment.
  • */
    +export interface Parameter {
  • type: "param";
  • name: string;
    +}

+/**

    • A wildcard parameter designed to match multiple segments.
  • */
    +export interface Wildcard {
  • type: "wildcard";
  • name: string;
    +}

+/**

    • A set of possible tokens to expand when matching.
  • */
    +export interface Group {
  • type: "group";
  • tokens: Token[];
    +}

+/**

    • A sequence of path match characters.
  • */
    +export type Token = Text | Parameter | Wildcard | Group;

+/**

    • Tokenized path instance.
  • */
    +export class TokenData {
  • constructor(public readonly tokens: Token[]) {}
    +}

+/**

    • Parse a string for the raw tokens.
  • */
    +export function parse(str: string, options: ParseOptions = {}): TokenData {
  • const { encodePath = NOOP_VALUE } = options;
  • const it = new Iter(lexer(str));
  • function consume(endType: TokenType): Token[] {
  • const tokens: Token[] = [];
  • while (true) {
  •  const path = it.text();
    
  •  if (path) tokens.push({ type: "text", value: encodePath(path) });
    
  •  const param = it.tryConsume("PARAM");
    
  •  if (param) {
    
  •    tokens.push({
    
  •      type: "param",
    
  •      name: param,
    
  •    });
    
  •    continue;
    
  •  }
    
  •  const wildcard = it.tryConsume("WILDCARD");
    
  •  if (wildcard) {
    
  •    tokens.push({
    
  •      type: "wildcard",
    
  •      name: wildcard,
    
  •    });
    
  •    continue;
    
  •  }
    
  •  const open = it.tryConsume("{");
    
  •  if (open) {
    
  •    tokens.push({
    
  •      type: "group",
    
  •      tokens: consume("}"),
    
  •    });
    
  •    continue;
    
  •  }
    
  •  it.consume(endType);
    
  •  return tokens;
    
  • }
  • }
  • const tokens = consume("END");
  • return new TokenData(tokens);
    +}

+/**

    • Transform tokens into a path building function.
  • */
    +function $compile

    (

  • data: TokenData,
  • options: CompileOptions,
    +): PathFunction

    {

  • const { encode = encodeURIComponent, delimiter = DEFAULT_DELIMITER } =
  • options;
  • const fn = tokensToFunction(data.tokens, delimiter, encode);
  • return function path(data: P = {} as P) {
  • const [path, ...missing] = fn(data);
  • if (missing.length) {
  •  throw new TypeError(`Missing parameters: ${missing.join(", ")}`);
    
  • }
  • return path;
  • };
    +}

+/**

    • Compile a string to a template function for the path.
  • */
    +export function compile

    (

  • path: Path,
  • options: CompileOptions & ParseOptions = {},
    +) {
  • return $compile

    (

  • path instanceof TokenData ? path : parse(path, options),
  • options,
  • );
    +}

+export type ParamData = Partial<Record<string, string | string[]>>;
+export type PathFunction

= (data?: P) => string;
+
+function tokensToFunction(

  • tokens: Token[],
  • delimiter: string,
  • encode: Encode | false,
    +) {
  • const encoders = tokens.map((token) =>
  • tokenToFunction(token, delimiter, encode),
  • );
  • return (data: ParamData) => {
  • const result: string[] = [""];
  • for (const encoder of encoders) {
  •  const [value, ...extras] = encoder(data);
    
  •  result[0] += value;
    
  •  result.push(...extras);
    
  • }
  • return result;
  • };
    +}

+/**

    • Convert a single token into a path building function.
  • */
    +function tokenToFunction(
  • token: Token,
  • delimiter: string,
  • encode: Encode | false,
    +): (data: ParamData) => string[] {
  • if (token.type === "text") return () => [token.value];
  • if (token.type === "group") {
  • const fn = tokensToFunction(token.tokens, delimiter, encode);
  • return (data) => {
  •  const [value, ...missing] = fn(data);
    
  •  if (!missing.length) return [value];
    
  •  return [""];
    
  • };
  • }
  • const encodeValue = encode || NOOP_VALUE;
  • if (token.type === "wildcard" && encode !== false) {
  • return (data) => {
  •  const value = data[token.name];
    
  •  if (value == null) return ["", token.name];
    
  •  if (!Array.isArray(value) || value.length === 0) {
    
  •    throw new TypeError(`Expected "${token.name}" to be a non-empty array`);
    
  •  }
    
  •  return [
    
  •    value
    
  •      .map((value, index) => {
    
  •        if (typeof value !== "string") {
    
  •          throw new TypeError(
    
  •            `Expected "${token.name}/${index}" to be a string`,
    
  •          );
    
  •        }
    
  •        return encodeValue(value);
    
  •      })
    
  •      .join(delimiter),
    
  •  ];
    
  • };
  • }
  • return (data) => {
  • const value = data[token.name];
  • if (value == null) return ["", token.name];
  • if (typeof value !== "string") {
  •  throw new TypeError(`Expected "${token.name}" to be a string`);
    
  • }
  • return [encodeValue(value)];
  • };
    +}

+/**

    • A match result contains data about the path match.
  • */
    +export interface MatchResult

    {

  • path: string;
  • params: P;
    +}

+/**

    • A match is either false (no match) or a match result.
  • */
    +export type Match

    = false | MatchResult

    ;

+/**

    • The match function takes a string and returns whether it matched the path.
  • */
    +export type MatchFunction

    = (path: string) => Match

    ;

+/**

    • Create path match function from path-to-regexp spec.
  • */
    +function $match

    (

  • data: TokenData[],
  • options: MatchOptions = {},
    +): MatchFunction

    {

  • const {
  • decode = decodeURIComponent,
  • delimiter = DEFAULT_DELIMITER,
  • end = true,
  • trailing = true,
  • } = options;
  • const flags = toFlags(options);
  • const sources: string[] = [];
  • const keys: Array<Parameter | Wildcard> = [];
  • for (const { tokens } of data) {
  • for (const seq of flatten(tokens, 0, [])) {
  •  const regexp = sequenceToRegExp(seq, delimiter, keys);
    
  •  sources.push(regexp);
    
  • }
  • }
  • let pattern = ^(?:${sources.join("|")});
  • if (trailing) pattern += (?:${escape(delimiter)}$)?;
  • pattern += end ? "$" : (?=${escape(delimiter)}|$);
  • const re = new RegExp(pattern, flags);
  • const decoders = keys.map((key) => {
  • if (decode === false) return NOOP_VALUE;
  • if (key.type === "param") return decode;
  • return (value: string) => value.split(delimiter).map(decode);
  • });
  • return Object.assign(
  • function match(input: string) {
  •  const m = re.exec(input);
    
  •  if (!m) return false;
    
  •  const { 0: path } = m;
    
  •  const params = Object.create(null);
    
  •  for (let i = 1; i < m.length; i++) {
    
  •    if (m[i] === undefined) continue;
    
  •    const key = keys[i - 1];
    
  •    const decoder = decoders[i - 1];
    
  •    params[key.name] = decoder(m[i]);
    
  •  }
    
  •  return { path, params };
    
  • },
  • { re },
  • );
    +}

+export type Path = string | TokenData;
+
+export function match

(

  • path: Path | Path[],
  • options: MatchOptions & ParseOptions = {},
    +): MatchFunction

    {

  • const paths = Array.isArray(path) ? path : [path];
  • const items = paths.map((path) =>
  • path instanceof TokenData ? path : parse(path, options),
  • );
  • return $match(items, options);
    +}

+/**

    • Flattened token set.
  • */
    +type Flattened = Text | Parameter | Wildcard;

+/**

    • Generate a flat list of sequence tokens from the given tokens.
  • /
    +function
    flatten(
  • tokens: Token[],
  • index: number,
  • init: Flattened[],
    +): Generator<Flattened[]> {
  • if (index === tokens.length) {
  • return yield init;
  • }
  • const token = tokens[index];
  • if (token.type === "group") {
  • const fork = init.slice();
  • for (const seq of flatten(token.tokens, 0, fork)) {
  •  yield* flatten(tokens, index + 1, seq);
    
  • }
  • } else {
  • init.push(token);
  • }
  • yield* flatten(tokens, index + 1, init);
    +}

+/**

    • Transform a flat sequence of tokens into a regular expression.
  • */
    +function sequenceToRegExp(
  • tokens: Flattened[],
  • delimiter: string,
  • keys: Array<Parameter | Wildcard>,
    +): string {
  • let result = "";
  • let backtrack = "";
  • let isSafeSegmentParam = true;
  • for (let i = 0; i < tokens.length; i++) {
  • const token = tokens[i];
  • if (token.type === "text") {
  •  result += escape(token.value);
    
  •  backtrack = token.value;
    
  •  isSafeSegmentParam ||= token.value.includes(delimiter);
    
  •  continue;
    
  • }
  • if (token.type === "param" || token.type === "wildcard") {
  •  if (!isSafeSegmentParam && !backtrack) {
    
  •    throw new TypeError(`Missing text after "${token.name}": ${DEBUG_URL}`);
    
  •  }
    
  •  if (token.type === "param") {
    
  •    result += `(${negate(delimiter, isSafeSegmentParam ? "" : backtrack)}+)`;
    
  •  } else {
    
  •    result += `(.+)`;
    
  •  }
    
  •  keys.push(token);
    
  •  backtrack = "";
    
  •  isSafeSegmentParam = false;
    
  •  continue;
    
  • }
  • }
  • return result;
    +}

+function negate(delimiter: string, backtrack: string) {

  • const values = [delimiter, backtrack].filter(Boolean);
  • const isSimple = values.every((value) => value.length === 1);
  • if (isSimple) return [^${escape(values.join(""))}];
  • return (?:(?!${values.map(escape).join("|")}).);
    +}
    diff --git test.ts test.ts
    deleted file mode 100644
    index 19b0d42..0000000
    --- test.ts
    +++ /dev/null
    @@ -1,2438 +0,0 @@
    -/* global describe, it */

-///

-import util = require('util')
-import chai = require('chai')
-import pathToRegexp = require('./index')

-const expect = chai.expect

-type Test = [

  • pathToRegexp.Path,
  • pathToRegexp.RegExpOptions & pathToRegexp.ParseOptions,
  • pathToRegexp.Token[],
  • Array<[string, string[]]>,
  • Array<[any, string]>
    -]

-/**

    • An array of test cases with expected inputs and outputs.
  • */
    -var TESTS: Test[] = [
  • /**
    • Simple paths.
  • */
  • [
  • '/',
  • null,
  • [
  •  '/'
    
  • ],
  • [
  •  ['/', ['/']],
    
  •  ['/route', null]
    
  • ],
  • [
  •  [null, '/'],
    
  •  [{}, '/'],
    
  •  [{ id: 123 }, '/']
    
  • ]
  • ],
  • [
  • '/test',
  • null,
  • [
  •  '/test'
    
  • ],
  • [
  •  ['/test', ['/test']],
    
  •  ['/route', null],
    
  •  ['/test/route', null],
    
  •  ['/test/', ['/test/']]
    
  • ],
  • [
  •  [null, '/test'],
    
  •  [{}, '/test']
    
  • ]
  • ],
  • [
  • '/test/',
  • null,
  • [
  •  '/test/'
    
  • ],
  • [
  •  ['/test', ['/test']],
    
  •  ['/test/', ['/test/']],
    
  •  ['/test//', null]
    
  • ],
  • [
  •  [null, '/test/']
    
  • ]
  • ],
  • /**
    • Case-sensitive paths.
  • */
  • [
  • '/test',
  • {
  •  sensitive: true
    
  • },
  • [
  •  '/test'
    
  • ],
  • [
  •  ['/test', ['/test']],
    
  •  ['/TEST', null]
    
  • ],
  • [
  •  [null, '/test']
    
  • ]
  • ],
  • [
  • '/TEST',
  • {
  •  sensitive: true
    
  • },
  • [
  •  '/TEST'
    
  • ],
  • [
  •  ['/test', null],
    
  •  ['/TEST', ['/TEST']]
    
  • ],
  • [
  •  [null, '/TEST']
    
  • ]
  • ],
  • /**
    • Strict mode.
  • */
  • [
  • '/test',
  • {
  •  strict: true
    
  • },
  • [
  •  '/test'
    
  • ],
  • [
  •  ['/test', ['/test']],
    
  •  ['/test/', null],
    
  •  ['/TEST', ['/TEST']]
    
  • ],
  • [
  •  [null, '/test']
    
  • ]
  • ],
  • [
  • '/test/',
  • {
  •  strict: true
    
  • },
  • [
  •  '/test/'
    
  • ],
  • [
  •  ['/test', null],
    
  •  ['/test/', ['/test/']],
    
  •  ['/test//', null]
    
  • ],
  • [
  •  [null, '/test/']
    
  • ]
  • ],
  • /**
    • Non-ending mode.
  • */
  • [
  • '/test',
  • {
  •  end: false
    
  • },
  • [
  •  '/test'
    
  • ],
  • [
  •  ['/test', ['/test']],
    
  •  ['/test/', ['/test/']],
    
  •  ['/test/route', ['/test']],
    
  •  ['/route', null]
    
  • ],
  • [
  •  [null, '/test']
    
  • ]
  • ],
  • [
  • '/test/',
  • {
  •  end: false
    
  • },
  • [
  •  '/test/'
    
  • ],
  • [
  •  ['/test/route', ['/test']],
    
  •  ['/test//', ['/test']],
    
  •  ['/test//route', ['/test']]
    
  • ],
  • [
  •  [null, '/test/']
    
  • ]
  • ],
  • [
  • '/:test',
  • {
  •  end: false
    
  • },
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  }
    
  • ],
  • [
  •  ['/route', ['/route', 'route']]
    
  • ],
  • [
  •  [{}, null],
    
  •  [{ test: 'abc' }, '/abc']
    
  • ]
  • ],
  • [
  • '/:test/',
  • {
  •  end: false
    
  • },
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  },
    
  •  '/'
    
  • ],
  • [
  •  ['/route', ['/route', 'route']]
    
  • ],
  • [
  •  [{ test: 'abc' }, '/abc/']
    
  • ]
  • ],
  • /**
    • Combine modes.
  • */
  • [
  • '/test',
  • {
  •  end: false,
    
  •  strict: true
    
  • },
  • [
  •  '/test'
    
  • ],
  • [
  •  ['/test', ['/test']],
    
  •  ['/test/', ['/test']],
    
  •  ['/test/route', ['/test']]
    
  • ],
  • [
  •  [null, '/test']
    
  • ]
  • ],
  • [
  • '/test/',
  • {
  •  end: false,
    
  •  strict: true
    
  • },
  • [
  •  '/test/'
    
  • ],
  • [
  •  ['/test', null],
    
  •  ['/test/', ['/test/']],
    
  •  ['/test//', ['/test/']],
    
  •  ['/test/route', ['/test/']]
    
  • ],
  • [
  •  [null, '/test/']
    
  • ]
  • ],
  • [
  • '/test.json',
  • {
  •  end: false,
    
  •  strict: true
    
  • },
  • [
  •  '/test.json'
    
  • ],
  • [
  •  ['/test.json', ['/test.json']],
    
  •  ['/test.json.hbs', null],
    
  •  ['/test.json/route', ['/test.json']]
    
  • ],
  • [
  •  [null, '/test.json']
    
  • ]
  • ],
  • [
  • '/:test',
  • {
  •  end: false,
    
  •  strict: true
    
  • },
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  }
    
  • ],
  • [
  •  ['/route', ['/route', 'route']],
    
  •  ['/route/', ['/route', 'route']]
    
  • ],
  • [
  •  [{}, null],
    
  •  [{ test: 'abc' }, '/abc']
    
  • ]
  • ],
  • [
  • '/:test/',
  • {
  •  end: false,
    
  •  strict: true
    
  • },
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  },
    
  •  '/'
    
  • ],
  • [
  •  ['/route', null],
    
  •  ['/route/', ['/route/', 'route']]
    
  • ],
  • [
  •  [{ test: 'foobar' }, '/foobar/']
    
  • ]
  • ],
  • /**
    • Arrays of simple paths.
  • */
  • [
  • ['/one', '/two'],
  • null,
  • [],
  • [
  •  ['/one', ['/one']],
    
  •  ['/two', ['/two']],
    
  •  ['/three', null],
    
  •  ['/one/two', null]
    
  • ],
  • []
  • ],
  • /**
    • Non-ending simple path.
  • */
  • [
  • '/test',
  • {
  •  end: false
    
  • },
  • [
  •  '/test'
    
  • ],
  • [
  •  ['/test/route', ['/test']]
    
  • ],
  • [
  •  [null, '/test']
    
  • ]
  • ],
  • /**
    • Single named parameter.
  • */
  • [
  • '/:test',
  • null,
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  }
    
  • ],
  • [
  •  ['/route', ['/route', 'route']],
    
  •  ['/another', ['/another', 'another']],
    
  •  ['/something/else', null],
    
  •  ['/route.json', ['/route.json', 'route.json']],
    
  •  ['/something%2Felse', ['/something%2Felse', 'something%2Felse']],
    
  •  ['/something%2Felse%2Fmore', ['/something%2Felse%2Fmore', 'something%2Felse%2Fmore']],
    
  •  ['/;,:@&=+$-_.!~*()', ['/;,:@&=+$-_.!~*()', ';,:@&=+$-_.!~*()']]
    
  • ],
  • [
  •  [{ test: 'route' }, '/route'],
    
  •  [{ test: 'something/else' }, '/something%2Felse'],
    
  •  [{ test: 'something/else/more' }, '/something%2Felse%2Fmore']
    
  • ]
  • ],
  • [
  • '/:test',
  • {
  •  strict: true
    
  • },
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  }
    
  • ],
  • [
  •  ['/route', ['/route', 'route']],
    
  •  ['/route/', null]
    
  • ],
  • [
  •  [{ test: 'route' }, '/route']
    
  • ]
  • ],
  • [
  • '/:test/',
  • {
  •  strict: true
    
  • },
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  },
    
  •  '/'
    
  • ],
  • [
  •  ['/route/', ['/route/', 'route']],
    
  •  ['/route//', null]
    
  • ],
  • [
  •  [{ test: 'route' }, '/route/']
    
  • ]
  • ],
  • [
  • '/:test',
  • {
  •  end: false
    
  • },
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  }
    
  • ],
  • [
  •  ['/route.json', ['/route.json', 'route.json']],
    
  •  ['/route//', ['/route', 'route']]
    
  • ],
  • [
  •  [{ test: 'route' }, '/route']
    
  • ]
  • ],
  • /**
    • Optional named parameter.
  • */
  • [
  • '/:test?',
  • null,
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: true,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  }
    
  • ],
  • [
  •  ['/route', ['/route', 'route']],
    
  •  ['/route/nested', null],
    
  •  ['/', ['/', undefined]],
    
  •  ['//', null]
    
  • ],
  • [
  •  [null, ''],
    
  •  [{ test: 'foobar' }, '/foobar']
    
  • ]
  • ],
  • [
  • '/:test?',
  • {
  •  strict: true
    
  • },
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: true,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  }
    
  • ],
  • [
  •  ['/route', ['/route', 'route']],
    
  •  ['/', null], // Questionable behaviour.
    
  •  ['//', null]
    
  • ],
  • [
  •  [null, ''],
    
  •  [{ test: 'foobar' }, '/foobar'],
    
  • ]
  • ],
  • [
  • '/:test?/',
  • {
  •  strict: true
    
  • },
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: true,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  },
    
  •  '/'
    
  • ],
  • [
  •  ['/route', null],
    
  •  ['/route/', ['/route/', 'route']],
    
  •  ['/', ['/', undefined]],
    
  •  ['//', null]
    
  • ],
  • [
  •  [null, '/'],
    
  •  [{ test: 'foobar' }, '/foobar/']
    
  • ]
  • ],
  • [
  • '/:test?/bar',
  • null,
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: true,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  },
    
  •  '/bar'
    
  • ],
  • [
  •  ['/foo/bar', ['/foo/bar', 'foo']]
    
  • ],
  • [
  •  [{ test: 'foo' }, '/foo/bar']
    
  • ]
  • ],
  • [
  • '/:test?-bar',
  • null,
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: true,
    
  •    repeat: false,
    
  •    partial: true,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  },
    
  •  '-bar'
    
  • ],
  • [
  •  ['/-bar', ['/-bar', undefined]],
    
  •  ['/foo-bar', ['/foo-bar', 'foo']]
    
  • ],
  • [
  •  [{ test: 'foo' }, '/foo-bar']
    
  • ]
  • ],
  • /**
    • Repeated one or more times parameters.
  • */
  • [
  • '/:test+',
  • null,
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: true,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  }
    
  • ],
  • [
  •  ['/', null],
    
  •  ['/route', ['/route', 'route']],
    
  •  ['/some/basic/route', ['/some/basic/route', 'some/basic/route']],
    
  •  ['//', null]
    
  • ],
  • [
  •  [{}, null],
    
  •  [{ test: 'foobar' }, '/foobar'],
    
  •  [{ test: ['a', 'b', 'c'] }, '/a/b/c']
    
  • ]
  • ],
  • [
  • '/:test(\d+)+',
  • null,
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: true,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '\\d+'
    
  •  }
    
  • ],
  • [
  •  ['/abc/456/789', null],
    
  •  ['/123/456/789', ['/123/456/789', '123/456/789']]
    
  • ],
  • [
  •  [{ test: 'abc' }, null],
    
  •  [{ test: 123 }, '/123'],
    
  •  [{ test: [1, 2, 3] }, '/1/2/3']
    
  • ]
  • ],
  • [
  • '/route.:ext(json|xml)+',
  • null,
  • [
  •  '/route',
    
  •  {
    
  •    name: 'ext',
    
  •    prefix: '.',
    
  •    delimiter: '.',
    
  •    optional: false,
    
  •    repeat: true,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: 'json|xml'
    
  •  }
    
  • ],
  • [
  •  ['/route', null],
    
  •  ['/route.json', ['/route.json', 'json']],
    
  •  ['/route.xml.json', ['/route.xml.json', 'xml.json']],
    
  •  ['/route.html', null]
    
  • ],
  • [
  •  [{ ext: 'foobar' }, null],
    
  •  [{ ext: 'xml' }, '/route.xml'],
    
  •  [{ ext: ['xml', 'json'] }, '/route.xml.json']
    
  • ]
  • ],
  • /**
    • Repeated zero or more times parameters.
  • */
  • [
  • '/:test*',
  • null,
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: true,
    
  •    repeat: true,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  }
    
  • ],
  • [
  •  ['/', ['/', undefined]],
    
  •  ['//', null],
    
  •  ['/route', ['/route', 'route']],
    
  •  ['/some/basic/route', ['/some/basic/route', 'some/basic/route']]
    
  • ],
  • [
  •  [{}, ''],
    
  •  [{ test: 'foobar' }, '/foobar'],
    
  •  [{ test: ['foo', 'bar'] }, '/foo/bar']
    
  • ]
  • ],
  • [
  • '/route.:ext([a-z]+)*',
  • null,
  • [
  •  '/route',
    
  •  {
    
  •    name: 'ext',
    
  •    prefix: '.',
    
  •    delimiter: '.',
    
  •    optional: true,
    
  •    repeat: true,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[a-z]+'
    
  •  }
    
  • ],
  • [
  •  ['/route', ['/route', undefined]],
    
  •  ['/route.json', ['/route.json', 'json']],
    
  •  ['/route.json.xml', ['/route.json.xml', 'json.xml']],
    
  •  ['/route.123', null]
    
  • ],
  • [
  •  [{}, '/route'],
    
  •  [{ ext: [] }, '/route'],
    
  •  [{ ext: '123' }, null],
    
  •  [{ ext: 'foobar' }, '/route.foobar'],
    
  •  [{ ext: ['foo', 'bar'] }, '/route.foo.bar']
    
  • ]
  • ],
  • /**
    • Custom named parameters.
  • */
  • [
  • '/:test(\d+)',
  • null,
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '\\d+'
    
  •  }
    
  • ],
  • [
  •  ['/123', ['/123', '123']],
    
  •  ['/abc', null],
    
  •  ['/123/abc', null]
    
  • ],
  • [
  •  [{ test: 'abc' }, null],
    
  •  [{ test: '123' }, '/123']
    
  • ]
  • ],
  • [
  • '/:test(\d+)',
  • {
  •  end: false
    
  • },
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '\\d+'
    
  •  }
    
  • ],
  • [
  •  ['/123', ['/123', '123']],
    
  •  ['/abc', null],
    
  •  ['/123/abc', ['/123', '123']]
    
  • ],
  • [
  •  [{ test: '123' }, '/123']
    
  • ]
  • ],
  • [
  • '/:test(.*)',
  • null,
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '.*'
    
  •  }
    
  • ],
  • [
  •  ['/anything/goes/here', ['/anything/goes/here', 'anything/goes/here']],
    
  •  ['/;,:@&=/+$-_.!/~*()', ['/;,:@&=/+$-_.!/~*()', ';,:@&=/+$-_.!/~*()']]
    
  • ],
  • [
  •  [{ test: '' }, '/'],
    
  •  [{ test: 'abc' }, '/abc'],
    
  •  [{ test: 'abc/123' }, '/abc%2F123'],
    
  •  [{ test: 'abc/123/456' }, '/abc%2F123%2F456']
    
  • ]
  • ],
  • [
  • '/:route([a-z]+)',
  • null,
  • [
  •  {
    
  •    name: 'route',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[a-z]+'
    
  •  }
    
  • ],
  • [
  •  ['/abcde', ['/abcde', 'abcde']],
    
  •  ['/12345', null]
    
  • ],
  • [
  •  [{ route: '' }, null],
    
  •  [{ route: '123' }, null],
    
  •  [{ route: 'abc' }, '/abc']
    
  • ]
  • ],
  • [
  • '/:route(this|that)',
  • null,
  • [
  •  {
    
  •    name: 'route',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: 'this|that'
    
  •  }
    
  • ],
  • [
  •  ['/this', ['/this', 'this']],
    
  •  ['/that', ['/that', 'that']],
    
  •  ['/foo', null]
    
  • ],
  • [
  •  [{ route: 'this' }, '/this'],
    
  •  [{ route: 'foo' }, null],
    
  •  [{ route: 'that' }, '/that']
    
  • ]
  • ],
  • [
  • '/:path(abc|xyz)*',
  • null,
  • [
  •  {
    
  •    name: 'path',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: true,
    
  •    repeat: true,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: 'abc|xyz'
    
  •  }
    
  • ],
  • [
  •  ['/abc', ['/abc', 'abc']],
    
  •  ['/abc/abc', ['/abc/abc', 'abc/abc']],
    
  •  ['/xyz/xyz', ['/xyz/xyz', 'xyz/xyz']],
    
  •  ['/abc/xyz', ['/abc/xyz', 'abc/xyz']],
    
  •  ['/abc/xyz/abc/xyz', ['/abc/xyz/abc/xyz', 'abc/xyz/abc/xyz']],
    
  •  ['/xyzxyz', null]
    
  • ],
  • [
  •  [{ path: 'abc' }, '/abc'],
    
  •  [{ path: ['abc', 'xyz'] }, '/abc/xyz'],
    
  •  [{ path: ['xyz', 'abc', 'xyz'] }, '/xyz/abc/xyz'],
    
  •  [{ path: 'abc123' }, null],
    
  •  [{ path: 'abcxyz' }, null]
    
  • ]
  • ],
  • /**
    • Prefixed slashes could be omitted.
  • */
  • [
  • 'test',
  • null,
  • [
  •  'test'
    
  • ],
  • [
  •  ['test', ['test']],
    
  •  ['/test', null]
    
  • ],
  • [
  •  [null, 'test']
    
  • ]
  • ],
  • [
  • ':test',
  • null,
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  }
    
  • ],
  • [
  •  ['route', ['route', 'route']],
    
  •  ['/route', null],
    
  •  ['route/', ['route/', 'route']]
    
  • ],
  • [
  •  [{ test: '' }, null],
    
  •  [{}, null],
    
  •  [{ test: null }, null],
    
  •  [{ test: 'route' }, 'route']
    
  • ]
  • ],
  • [
  • ':test',
  • {
  •  strict: true
    
  • },
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  }
    
  • ],
  • [
  •  ['route', ['route', 'route']],
    
  •  ['/route', null],
    
  •  ['route/', null]
    
  • ],
  • [
  •  [{ test: 'route' }, 'route']
    
  • ]
  • ],
  • [
  • ':test',
  • {
  •  end: false
    
  • },
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  }
    
  • ],
  • [
  •  ['route', ['route', 'route']],
    
  •  ['/route', null],
    
  •  ['route/', ['route/', 'route']],
    
  •  ['route/foobar', ['route', 'route']]
    
  • ],
  • [
  •  [{ test: 'route' }, 'route']
    
  • ]
  • ],
  • [
  • ':test?',
  • null,
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '',
    
  •    delimiter: '/',
    
  •    optional: true,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  }
    
  • ],
  • [
  •  ['route', ['route', 'route']],
    
  •  ['/route', null],
    
  •  ['', ['', undefined]],
    
  •  ['route/foobar', null]
    
  • ],
  • [
  •  [{}, ''],
    
  •  [{ test: '' }, null],
    
  •  [{ test: 'route' }, 'route']
    
  • ]
  • ],
  • /**
    • Formats.
  • */
  • [
  • '/test.json',
  • null,
  • [
  •  '/test.json'
    
  • ],
  • [
  •  ['/test.json', ['/test.json']],
    
  •  ['/route.json', null]
    
  • ],
  • [
  •  [{}, '/test.json']
    
  • ]
  • ],
  • [
  • '/:test.json',
  • null,
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: true,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  },
    
  •  '.json'
    
  • ],
  • [
  •  ['/.json', null],
    
  •  ['/test.json', ['/test.json', 'test']],
    
  •  ['/route.json', ['/route.json', 'route']],
    
  •  ['/route.json.json', ['/route.json.json', 'route.json']]
    
  • ],
  • [
  •  [{ test: '' }, null],
    
  •  [{ test: 'foo' }, '/foo.json']
    
  • ]
  • ],
  • /**
    • Format params.
  • */
  • [
  • '/test.:format',
  • null,
  • [
  •  '/test',
    
  •  {
    
  •    name: 'format',
    
  •    prefix: '.',
    
  •    delimiter: '.',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\.]+?'
    
  •  }
    
  • ],
  • [
  •  ['/test.html', ['/test.html', 'html']],
    
  •  ['/test.hbs.html', null]
    
  • ],
  • [
  •  [{}, null],
    
  •  [{ format: '' }, null],
    
  •  [{ format: 'foo' }, '/test.foo']
    
  • ]
  • ],
  • [
  • '/test.:format.:format',
  • null,
  • [
  •  '/test',
    
  •  {
    
  •    name: 'format',
    
  •    prefix: '.',
    
  •    delimiter: '.',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\.]+?'
    
  •  },
    
  •  {
    
  •    name: 'format',
    
  •    prefix: '.',
    
  •    delimiter: '.',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\.]+?'
    
  •  }
    
  • ],
  • [
  •  ['/test.html', null],
    
  •  ['/test.hbs.html', ['/test.hbs.html', 'hbs', 'html']]
    
  • ],
  • [
  •  [{ format: 'foo.bar' }, null],
    
  •  [{ format: 'foo' }, '/test.foo.foo']
    
  • ]
  • ],
  • [
  • '/test.:format+',
  • null,
  • [
  •  '/test',
    
  •  {
    
  •    name: 'format',
    
  •    prefix: '.',
    
  •    delimiter: '.',
    
  •    optional: false,
    
  •    repeat: true,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\.]+?'
    
  •  }
    
  • ],
  • [
  •  ['/test.html', ['/test.html', 'html']],
    
  •  ['/test.hbs.html', ['/test.hbs.html', 'hbs.html']]
    
  • ],
  • [
  •  [{ format: [] }, null],
    
  •  [{ format: 'foo' }, '/test.foo'],
    
  •  [{ format: ['foo', 'bar'] }, '/test.foo.bar']
    
  • ]
  • ],
  • [
  • '/test.:format',
  • {
  •  end: false
    
  • },
  • [
  •  '/test',
    
  •  {
    
  •    name: 'format',
    
  •    prefix: '.',
    
  •    delimiter: '.',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\.]+?'
    
  •  }
    
  • ],
  • [
  •  ['/test.html', ['/test.html', 'html']],
    
  •  ['/test.hbs.html', null]
    
  • ],
  • [
  •  [{ format: 'foo' }, '/test.foo']
    
  • ]
  • ],
  • [
  • '/test.:format.',
  • null,
  • [
  •  '/test',
    
  •  {
    
  •    name: 'format',
    
  •    prefix: '.',
    
  •    delimiter: '.',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\.]+?'
    
  •  },
    
  •  '.'
    
  • ],
  • [
  •  ['/test.html.', ['/test.html.', 'html']],
    
  •  ['/test.hbs.html', null]
    
  • ],
  • [
  •  [{ format: '' }, null],
    
  •  [{ format: 'foo' }, '/test.foo.']
    
  • ]
  • ],
  • /**
    • Format and path params.
  • */
  • [
  • '/:test.:format',
  • null,
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: true,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  },
    
  •  {
    
  •    name: 'format',
    
  •    prefix: '.',
    
  •    delimiter: '.',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\.]+?'
    
  •  }
    
  • ],
  • [
  •  ['/route.html', ['/route.html', 'route', 'html']],
    
  •  ['/route', null],
    
  •  ['/route.html.json', ['/route.html.json', 'route.html', 'json']]
    
  • ],
  • [
  •  [{}, null],
    
  •  [{ test: 'route', format: 'foo' }, '/route.foo']
    
  • ]
  • ],
  • [
  • '/:test.:format?',
  • null,
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: true,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  },
    
  •  {
    
  •    name: 'format',
    
  •    prefix: '.',
    
  •    delimiter: '.',
    
  •    optional: true,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\.]+?'
    
  •  }
    
  • ],
  • [
  •  ['/route', ['/route', 'route', undefined]],
    
  •  ['/route.json', ['/route.json', 'route', 'json']],
    
  •  ['/route.json.html', ['/route.json.html', 'route.json', 'html']]
    
  • ],
  • [
  •  [{ test: 'route' }, '/route'],
    
  •  [{ test: 'route', format: '' }, null],
    
  •  [{ test: 'route', format: 'foo' }, '/route.foo']
    
  • ]
  • ],
  • [
  • '/:test.:format?',
  • {
  •  end: false
    
  • },
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: true,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  },
    
  •  {
    
  •    name: 'format',
    
  •    prefix: '.',
    
  •    delimiter: '.',
    
  •    optional: true,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\.]+?'
    
  •  }
    
  • ],
  • [
  •  ['/route', ['/route', 'route', undefined]],
    
  •  ['/route.json', ['/route.json', 'route', 'json']],
    
  •  ['/route.json.html', ['/route.json.html', 'route.json', 'html']]
    
  • ],
  • [
  •  [{ test: 'route' }, '/route'],
    
  •  [{ test: 'route', format: undefined }, '/route'],
    
  •  [{ test: 'route', format: '' }, null],
    
  •  [{ test: 'route', format: 'foo' }, '/route.foo']
    
  • ]
  • ],
  • [
  • '/test.:format(.*)z',
  • {
  •  end: false
    
  • },
  • [
  •  '/test',
    
  •  {
    
  •    name: 'format',
    
  •    prefix: '.',
    
  •    delimiter: '.',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: true,
    
  •    asterisk: false,
    
  •    pattern: '.*'
    
  •  },
    
  •  'z'
    
  • ],
  • [
  •  ['/test.abc', null],
    
  •  ['/test.z', ['/test.z', '']],
    
  •  ['/test.abcz', ['/test.abcz', 'abc']]
    
  • ],
  • [
  •  [{}, null],
    
  •  [{ format: '' }, '/test.z'],
    
  •  [{ format: 'foo' }, '/test.fooz']
    
  • ]
  • ],
  • /**
    • Unnamed params.
  • */
  • [
  • '/(\d+)',
  • null,
  • [
  •  {
    
  •    name: 0,
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '\\d+'
    
  •  }
    
  • ],
  • [
  •  ['/123', ['/123', '123']],
    
  •  ['/abc', null],
    
  •  ['/123/abc', null]
    
  • ],
  • [
  •  [{}, null],
    
  •  [{ '0': '123' }, '/123']
    
  • ]
  • ],
  • [
  • '/(\d+)',
  • {
  •  end: false
    
  • },
  • [
  •  {
    
  •    name: 0,
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '\\d+'
    
  •  }
    
  • ],
  • [
  •  ['/123', ['/123', '123']],
    
  •  ['/abc', null],
    
  •  ['/123/abc', ['/123', '123']],
    
  •  ['/123/', ['/123/', '123']]
    
  • ],
  • [
  •  [{ '0': '123' }, '/123']
    
  • ]
  • ],
  • [
  • '/(\d+)?',
  • null,
  • [
  •  {
    
  •    name: 0,
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: true,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '\\d+'
    
  •  }
    
  • ],
  • [
  •  ['/', ['/', undefined]],
    
  •  ['/123', ['/123', '123']]
    
  • ],
  • [
  •  [{}, ''],
    
  •  [{ '0': '123' }, '/123']
    
  • ]
  • ],
  • [
  • '/(.*)',
  • null,
  • [
  •  {
    
  •    name: 0,
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '.*'
    
  •  }
    
  • ],
  • [
  •  ['/', ['/', '']],
    
  •  ['/route', ['/route', 'route']],
    
  •  ['/route/nested', ['/route/nested', 'route/nested']]
    
  • ],
  • [
  •  [{ '0': '' }, '/'],
    
  •  [{ '0': '123' }, '/123']
    
  • ]
  • ],
  • [
  • '/route\(\\(\d+\\)\)',
  • null,
    
  • [
  •  '/route(\\',
    
  •  {
    
  •    name: 0,
    
  •    prefix: '',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '\\d+\\\\'
    
  •  },
    
  •  ')'
    
  • ],
  • [
  •  ['/route(\\123\\)', ['/route(\\123\\)', '123\\']]
    
  • ],
  • []
  • ],
  • /**
    • Regexps.
  • */
  • [
  • /.*/,
  • null,
  • [],
  • [
  •  ['/match/anything', ['/match/anything']]
    
  • ],
  • []
  • ],
  • [
  • /(.*)/,
  • null,
  • [
  •  {
    
  •    name: 0,
    
  •    prefix: null,
    
  •    delimiter: null,
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: null
    
  •  }
    
  • ],
  • [
  •  ['/match/anything', ['/match/anything', '/match/anything']]
    
  • ],
  • []
  • ],
  • [
  • //(\d+)/,
  • null,
  • [
  •  {
    
  •    name: 0,
    
  •    prefix: null,
    
  •    delimiter: null,
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: null
    
  •  }
    
  • ],
  • [
  •  ['/abc', null],
    
  •  ['/123', ['/123', '123']]
    
  • ],
  • []
  • ],
  • /**
    • Mixed arrays.
  • */
  • [
  • ['/test', //(\d+)/],
  • null,
  • [
  •  {
    
  •    name: 0,
    
  •    prefix: null,
    
  •    delimiter: null,
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: null
    
  •  }
    
  • ],
  • [
  •  ['/test', ['/test', undefined]]
    
  • ],
  • []
  • ],
  • [
  • ['/:test(\d+)', /(.*)/],
  • null,
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '\\d+'
    
  •  },
    
  •  {
    
  •    name: 0,
    
  •    prefix: null,
    
  •    delimiter: null,
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: null
    
  •  }
    
  • ],
  • [
  •  ['/123', ['/123', '123', undefined]],
    
  •  ['/abc', ['/abc', undefined, '/abc']]
    
  • ],
  • []
  • ],
  • /**
    • Correct names and indexes.
  • */
  • [
  • ['/:test', '/route/:test'],
  • null,
  • [
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  },
    
  •  {
    
  •    name: 'test',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  }
    
  • ],
  • [
  •  ['/test', ['/test', 'test', undefined]],
    
  •  ['/route/test', ['/route/test', undefined, 'test']]
    
  • ],
  • []
  • ],
  • [
  • [/^/([^\/]+)$/, /^/route/([^\/]+)$/],
  • null,
  • [
  •  {
    
  •    name: 0,
    
  •    prefix: null,
    
  •    delimiter: null,
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: null
    
  •  },
    
  •  {
    
  •    name: 0,
    
  •    prefix: null,
    
  •    delimiter: null,
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: null
    
  •  }
    
  • ],
  • [
  •  ['/test', ['/test', 'test', undefined]],
    
  •  ['/route/test', ['/route/test', undefined, 'test']]
    
  • ],
  • []
  • ],
  • /**
    • Ignore non-matching groups in regexps.
  • */
  • [
  • /(?:.*)/,
  • null,
  • [],
  • [
  •  ['/anything/you/want', ['/anything/you/want']]
    
  • ],
  • []
  • ],
  • /**
    • Respect escaped characters.
  • */
  • [
  • '/\(testing\)',
  • null,
  • [
  •  '/(testing)'
    
  • ],
  • [
  •  ['/testing', null],
    
  •  ['/(testing)', ['/(testing)']]
    
  • ],
  • [
  •  [null, '/(testing)']
    
  • ]
  • ],
  • [
  • '/.+\*?=^!:${}[]|',
  • null,
  • [
  •  '/.+*?=^!:${}[]|'
    
  • ],
  • [
  •  ['/.+*?=^!:${}[]|', ['/.+*?=^!:${}[]|']]
    
  • ],
  • [
  •  [null, '/.+*?=^!:${}[]|']
    
  • ]
  • ],
  • /**
    • Asterisk functionality.
  • */
  • [
  • '/*',
  • null,
  • [
  •  {
    
  •    name: 0,
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: true,
    
  •    pattern: '.*'
    
  •  }
    
  • ],
  • [
  •  ['', null],
    
  •  ['/', ['/', '']],
    
  •  ['/foo/bar', ['/foo/bar', 'foo/bar']]
    
  • ],
  • [
  •  [null, null],
    
  •  [{ '0': '' }, '/'],
    
  •  [{ '0': 'foobar' }, '/foobar'],
    
  •  [{ '0': 'foo/bar' }, '/foo/bar'],
    
  •  [{ '0': ['foo', 'bar'] }, null],
    
  •  [{ '0': 'foo/bar?baz' }, '/foo/bar%3Fbaz']
    
  • ]
  • ],
  • [
  • '/foo/*',
  • null,
  • [
  •  '/foo',
    
  •  {
    
  •    name: 0,
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: true,
    
  •    pattern: '.*'
    
  •  }
    
  • ],
  • [
  •  ['', null],
    
  •  ['/test', null],
    
  •  ['/foo', null],
    
  •  ['/foo/', ['/foo/', '']],
    
  •  ['/foo/bar', ['/foo/bar', 'bar']]
    
  • ],
  • [
  •  [{ '0': 'bar' }, '/foo/bar']
    
  • ]
  • ],
  • [
  • '/:foo/*',
  • null,
  • [
  •  {
    
  •    name: 'foo',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  },
    
  •  {
    
  •    name: 0,
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: true,
    
  •    pattern: '.*'
    
  •  }
    
  • ],
  • [
  •  ['', null],
    
  •  ['/test', null],
    
  •  ['/foo', null],
    
  •  ['/foo/', ['/foo/', 'foo', '']],
    
  •  ['/foo/bar', ['/foo/bar', 'foo', 'bar']]
    
  • ],
  • [
  •  [{ foo: 'foo' }, null],
    
  •  [{ '0': 'bar' }, null],
    
  •  [{ foo: 'foo', '0': 'bar' }, '/foo/bar'],
    
  •  [{ foo: 'a', '0': 'b/c' }, '/a/b/c']
    
  • ]
  • ],
  • /**
    • Unnamed group prefix.
  • */
  • [
  • '/(apple-)?icon-:res(\d+).png',
  • null,
  • [
  •  {
    
  •    name: 0,
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: true,
    
  •    repeat: false,
    
  •    partial: true,
    
  •    asterisk: false,
    
  •    pattern: 'apple-'
    
  •  },
    
  •  'icon-',
    
  •  {
    
  •    name: 'res',
    
  •    prefix: '',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '\\d+'
    
  •  },
    
  •  '.png'
    
  • ],
  • [
  •  ['/icon-240.png', ['/icon-240.png', undefined, '240']],
    
  •  ['/apple-icon-240.png', ['/apple-icon-240.png', 'apple-', '240']]
    
  • ],
  • []
  • ],
  • /**
    • Random examples.
  • */
  • [
  • '/:foo/:bar',
  • null,
  • [
  •  {
    
  •    name: 'foo',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  },
    
  •  {
    
  •    name: 'bar',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  }
    
  • ],
  • [
  •  ['/match/route', ['/match/route', 'match', 'route']]
    
  • ],
  • [
  •  [{ foo: 'a', bar: 'b' }, '/a/b']
    
  • ]
  • ],
  • [
  • '/:foo(test\)/bar',
  • null,
  • [
  •  {
    
  •    name: 'foo',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: true,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  },
    
  •  '(test)/bar'
    
  • ],
  • [],
  • []
  • ],
  • [
  • '/:remote([\w-.]+)/:user([\w-]+)',
  • null,
  • [
  •  {
    
  •    name: 'remote',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[\\w-.]+'
    
  •  },
    
  •  {
    
  •    name: 'user',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[\\w-]+'
    
  •  }
    
  • ],
  • [
  •  ['/endpoint/user', ['/endpoint/user', 'endpoint', 'user']],
    
  •  ['/endpoint/user-name', ['/endpoint/user-name', 'endpoint', 'user-name']],
    
  •  ['/foo.bar/user-name', ['/foo.bar/user-name', 'foo.bar', 'user-name']]
    
  • ],
  • [
  •  [{ remote: 'foo', user: 'bar' }, '/foo/bar'],
    
  •  [{ remote: 'foo.bar', user: 'uno' }, '/foo.bar/uno']
    
  • ]
  • ],
  • [
  • '/:foo\?',
  • null,
  • [
  •  {
    
  •    name: 'foo',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: true,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  },
    
  •  '?'
    
  • ],
  • [
  •  ['/route?', ['/route?', 'route']]
    
  • ],
  • [
  •  [{ foo: 'bar' }, '/bar?']
    
  • ]
  • ],
  • [
  • '/:foo+baz',
  • null,
  • [
  •  {
    
  •    name: 'foo',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: true,
    
  •    partial: true,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  },
    
  •  'baz'
    
  • ],
  • [
  •  ['/foobaz', ['/foobaz', 'foo']],
    
  •  ['/foo/barbaz', ['/foo/barbaz', 'foo/bar']],
    
  •  ['/baz', null]
    
  • ],
  • [
  •  [{ foo: 'foo' }, '/foobaz'],
    
  •  [{ foo: 'foo/bar' }, '/foo%2Fbarbaz'],
    
  •  [{ foo: ['foo', 'bar'] }, '/foo/barbaz']
    
  • ]
  • ],
  • [
  • '/:pre?baz',
  • null,
  • [
  •  {
    
  •    name: 'pre',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: true,
    
  •    repeat: false,
    
  •    partial: true,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  },
    
  •  'baz'
    
  • ],
  • [
  •  ['/foobaz', ['/foobaz', 'foo']],
    
  •  ['/baz', ['/baz', undefined]]
    
  • ],
  • [
  •  [{}, '/baz'],
    
  •  [{ pre: 'foo' }, '/foobaz']
    
  • ]
  • ],
  • [
  • '/:foo\(:bar?\)',
  • null,
  • [
  •  {
    
  •    name: 'foo',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: true,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  },
    
  •  '(',
    
  •  {
    
  •    name: 'bar',
    
  •    prefix: '',
    
  •    delimiter: '/',
    
  •    optional: true,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  },
    
  •  ')'
    
  • ],
  • [
  •  ['/hello(world)', ['/hello(world)', 'hello', 'world']],
    
  •  ['/hello()', ['/hello()', 'hello', undefined]]
    
  • ],
  • [
  •  [{ foo: 'hello', bar: 'world' }, '/hello(world)'],
    
  •  [{ foo: 'hello' }, '/hello()']
    
  • ]
  • ],
  • [
  • '/:postType(video|audio|text)(\+.+)?',
  • null,
  • [
  •  {
    
  •    name: 'postType',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: true,
    
  •    asterisk: false,
    
  •    pattern: 'video|audio|text'
    
  •  },
    
  •  {
    
  •    name: 0,
    
  •    prefix: '',
    
  •    delimiter: '/',
    
  •    optional: true,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '\\+.+'
    
  •  }
    
  • ],
  • [
  •  ['/video', ['/video', 'video', undefined]],
    
  •  ['/video+test', ['/video+test', 'video', '+test']],
    
  •  ['/video+', null]
    
  • ],
  • [
  •  [{ postType: 'video' }, '/video'],
    
  •  [{ postType: 'random' }, null]
    
  • ]
  • ],
  • /**
    • Unicode characters.
  • */
  • [
  • '/:foo',
  • null,
  • [
  •  {
    
  •    name: 'foo',
    
  •    prefix: '/',
    
  •    delimiter: '/',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\/]+?'
    
  •  }
    
  • ],
  • [
  •  ['/café', ['/café', 'café']]
    
  • ],
  • [
  •  [{ foo: 'café' }, '/caf%C3%A9']
    
  • ]
  • ],
  • /**
    • Hostnames.
  • */
  • [
  • ':domain.com',
  • {
  •  delimiter: '.'
    
  • },
  • [
  •  {
    
  •    name: 'domain',
    
  •    prefix: '',
    
  •    delimiter: '.',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\.]+?'
    
  •  },
    
  •  '.com'
    
  • ],
  • [
  •  ['example.com', ['example.com', 'example']],
    
  •  ['github.com', ['github.com', 'github']],
    
  • ],
  • [
  •  [{ domain: 'example' }, 'example.com'],
    
  •  [{ domain: 'github' }, 'github.com']
    
  • ]
  • ],
  • [
  • 'mail.:domain.com',
  • {
  •  delimiter: '.'
    
  • },
  • [
  •  'mail',
    
  •  {
    
  •    name: 'domain',
    
  •    prefix: '.',
    
  •    delimiter: '.',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\.]+?'
    
  •  },
    
  •  '.com'
    
  • ],
  • [
  •  ['mail.example.com', ['mail.example.com', 'example']],
    
  •  ['mail.github.com', ['mail.github.com', 'github']]
    
  • ],
  • [
  •  [{ domain: 'example' }, 'mail.example.com'],
    
  •  [{ domain: 'github' }, 'mail.github.com']
    
  • ]
  • ],
  • [
  • 'example.:ext',
  • {
  •  delimiter: '.'
    
  • },
  • [
  •  'example',
    
  •  {
    
  •    name: 'ext',
    
  •    prefix: '.',
    
  •    delimiter: '.',
    
  •    optional: false,
    
  •    repeat: false,
    
  •    partial: false,
    
  •    asterisk: false,
    
  •    pattern: '[^\\.]+?'
    
  •  }
    
  • ],
  • [
  •  ['example.com', ['example.com', 'com']],
    
  •  ['example.org', ['example.org', 'org']],
    
  • ],
  • [
  •  [{ ext: 'com' }, 'example.com'],
    
  •  [{ ext: 'org' }, 'example.org']
    
  • ]
  • ],
  • [
  • 'this is',
  • {
  •  delimiter: ' ',
    
  •  end: false
    
  • },
  • [
  •  'this is'
    
  • ],
  • [
  •  ['this is a test', ['this is']],
    
  •  ['this isn\'t', null]
    
  • ],
  • [
  •  [null, 'this is']
    
  • ]
  • ]
    -]

-/**

    • Dynamically generate the entire test suite.
  • */
    -describe('path-to-regexp', function () {
  • var TEST_PATH = '/user/:id'
  • var TEST_PARAM = {
  • name: 'id',
  • prefix: '/',
  • delimiter: '/',
  • optional: false,
  • repeat: false,
  • partial: false,
  • asterisk: false,
  • pattern: '[^\\/]+?'
  • }
  • describe('arguments', function () {
  • it('should work without different call combinations', function () {
  •  pathToRegexp('/test')
    
  •  pathToRegexp('/test', [])
    
  •  pathToRegexp('/test', {})
    
  •  pathToRegexp('/test', [], {})
    
  •  pathToRegexp(/^\/test/)
    
  •  pathToRegexp(/^\/test/, [])
    
  •  pathToRegexp(/^\/test/, {})
    
  •  pathToRegexp(/^\/test/, [], {})
    
  •  pathToRegexp(['/a', '/b'])
    
  •  pathToRegexp(['/a', '/b'], [])
    
  •  pathToRegexp(['/a', '/b'], {})
    
  •  pathToRegexp(['/a', '/b'], [], {})
    
  • })
  • it('should accept an array of keys as the second argument', function () {
  •  var keys = []
    
  •  var re = pathToRegexp(TEST_PATH, keys, { end: false })
    
  •  expect(re.keys).to.equal(keys)
    
  •  expect(keys).to.deep.equal([TEST_PARAM])
    
  •  expect(exec(re, '/user/123/show')).to.deep.equal(['/user/123', '123'])
    
  • })
  • it('should work with keys as null', function () {
  •  var re = pathToRegexp(TEST_PATH, null, { end: false })
    
  •  expect(re.keys).to.deep.equal([TEST_PARAM])
    
  •  expect(exec(re, '/user/123/show')).to.deep.equal(['/user/123', '123'])
    
  • })
  • })
  • describe('tokens', function () {
  • var tokens = pathToRegexp.parse(TEST_PATH)
  • it('should expose method to compile tokens to regexp', function () {
  •  var re = pathToRegexp.tokensToRegExp(tokens)
    
  •  expect(exec(re, '/user/123')).to.deep.equal(['/user/123', '123'])
    
  • })
  • it('should expose method to compile tokens to a path function', function () {
  •  var fn = pathToRegexp.tokensToFunction(tokens)
    
  •  expect(fn({ id: 123 })).to.equal('/user/123')
    
  • })
  • })
  • describe('rules', function () {
  • TESTS.forEach(function (test) {
  •  var path = test[0]
    
  •  var opts = test[1]
    
  •  var tokens = test[2]
    
  •  var matchCases = test[3]
    
  •  var compileCases = test[4]
    
  •  var keys = tokens.filter(function (token) {
    
  •    return typeof token !== 'string'
    
  •  })
    
  •  describe(util.inspect(path), function () {
    
  •    var re = pathToRegexp(path, opts)
    
  •    // Parsing and compiling is only supported with string input.
    
  •    if (typeof path === 'string') {
    
  •      it('should parse', function () {
    
  •        expect(pathToRegexp.parse(path, opts)).to.deep.equal(tokens)
    
  •      })
    
  •      describe('compile', function () {
    
  •        var toPath = pathToRegexp.compile(path)
    
  •        compileCases.forEach(function (io) {
    
  •          var input = io[0]
    
  •          var output = io[1]
    
  •          if (output != null) {
    
  •            it('should compile using ' + util.inspect(input), function () {
    
  •              expect(toPath(input)).to.equal(output)
    
  •            })
    
  •          } else {
    
  •            it('should not compile using ' + util.inspect(input), function () {
    
  •              expect(function () {
    
  •                toPath(input)
    
  •              }).to.throw(TypeError)
    
  •            })
    
  •          }
    
  •        })
    
  •      })
    
  •    } else {
    
  •      it('should parse keys', function () {
    
  •        expect(re.keys).to.deep.equal(keys)
    
  •      })
    
  •    }
    
  •    describe('match' + (opts ? ' using ' + util.inspect(opts) : ''), function () {
    
  •      matchCases.forEach(function (io) {
    
  •        var input = io[0]
    
  •        var output = io[1]
    
  •        var message = 'should' + (output ? ' ' : ' not ') + 'match ' + util.inspect(input)
    
  •        it(message, function () {
    
  •          expect(exec(re, input)).to.deep.equal(output)
    
  •        })
    
  •      })
    
  •    })
    
  •  })
    
  • })
  • })
  • describe('compile', function () {
  • it('should allow pretty option', function () {
  •  var value = ';,:@&=+$-_.!~*()'
    
  •  var toPath = pathToRegexp.compile('/:value')
    
  •  var path = toPath({ value }, { pretty: true })
    
  •  expect(path).to.equal(`/${value}`)
    
  • })
  • })
  • describe('compile errors', function () {
  • it('should throw when a required param is undefined', function () {
  •  var toPath = pathToRegexp.compile('/a/:b/c')
    
  •  expect(function () {
    
  •    toPath()
    
  •  }).to.throw(TypeError, 'Expected "b" to be defined')
    
  • })
  • it('should throw when it does not match the pattern', function () {
  •  var toPath = pathToRegexp.compile('/:foo(\\d+)')
    
  •  expect(function () {
    
  •    toPath({ foo: 'abc' })
    
  •  }).to.throw(TypeError, 'Expected "foo" to match "\\d+"')
    
  • })
  • it('should throw when expecting a repeated value', function () {
  •  var toPath = pathToRegexp.compile('/:foo+')
    
  •  expect(function () {
    
  •    toPath({ foo: [] })
    
  •  }).to.throw(TypeError, 'Expected "foo" to not be empty')
    
  • })
  • it('should throw when not expecting a repeated value', function () {
  •  var toPath = pathToRegexp.compile('/:foo')
    
  •  expect(function () {
    
  •    toPath({ foo: [] })
    
  •  }).to.throw(TypeError, 'Expected "foo" to not repeat')
    
  • })
  • it('should throw when repeated value does not match', function () {
  •  var toPath = pathToRegexp.compile('/:foo(\\d+)+')
    
  •  expect(function () {
    
  •    toPath({ foo: [1, 2, 3, 'a'] })
    
  •  }).to.throw(TypeError, 'Expected all "foo" to match "\\d+"')
    
  • })
  • })
    -})

-/**

    • Execute a regular expression and return a flat array for comparison.
  • */
    -function exec (re, str) {
  • var match = re.exec(str)
  • return match && Array.prototype.slice.call(match)
    -}
    diff --git a/tsconfig.build.json b/tsconfig.build.json
    new file mode 100644
    index 0000000..3db8e88
    --- /dev/null
    +++ tsconfig.build.json
    @@ -0,0 +1,7 @@
    +{
  • "extends": "./tsconfig.json",
  • "compilerOptions": {
  • "types": []
  • },
  • "exclude": ["src//*.spec.ts", "src//*.bench.ts"]
    +}
    diff --git a/tsconfig.json b/tsconfig.json
    new file mode 100644
    index 0000000..83a86d2
    --- /dev/null
    +++ tsconfig.json
    @@ -0,0 +1,13 @@
    +{
  • "extends": "@borderless/ts-scripts/configs/tsconfig.json",
  • "compilerOptions": {
  • "target": "ES2020",
  • "lib": ["ES2020"],
  • "rootDir": "src",
  • "outDir": "dist",
  • "module": "NodeNext",
  • "moduleResolution": "NodeNext",
  • "types": ["node"]
  • },
  • "include": ["src/**/*"]
    +}
    diff --git typings.json typings.json
    deleted file mode 100644
    index 9993f2b..0000000
    --- typings.json
    +++ /dev/null
    @@ -1,9 +0,0 @@
    -{
  • "devDependencies": {
  • "chai": "registry:npm/chai#3.5.0+20160415060238"
  • },
  • "globalDevDependencies": {
  • "mocha": "registry:env/mocha#2.2.5+20160321223601",
  • "node": "registry:env/node#4.0.0+20160507210304"
  • }
    -}
    diff --git a/vitest.config.mts b/vitest.config.mts
    new file mode 100644
    index 0000000..f98f607
    --- /dev/null
    +++ vitest.config.mts
    @@ -0,0 +1,10 @@
    +///
    +import { defineConfig } from "vite";

+export default defineConfig({

  • test: {
  • coverage: {
  •  exclude: ["scripts/**"],
    
  • },
  • },
    +});

</details>

### Description
This PR updates the project infrastructure and modernizes the codebase by:
1. Moving from Travis CI to GitHub Actions
2. Adding security scanning with OSSF Scorecard
3. Modernizing the build tooling with TypeScript
4. Adding benchmarking capabilities
5. Moving documentation to GitHub Releases
6. Updating the .gitignore file 
7. Removing legacy files and dependencies

### Security Hotspots
1. **CI/CD Permissions**: While the workflows have defined permissions, validate that the specific permissions are the minimum required.

<details>
<summary><i>Changes</i></summary>

### Changes
By file category:

CI/CD:
- Added GitHub Actions workflow for CI (`ci.yml`)
- Added GitHub Actions workflow for security scanning (`scorecard.yml`) 
- Removed Travis CI config

Build:
- Added TypeScript configuration files
- Added Vitest configuration
- Added benchmark setup
- Updated package.json with modern build tooling

Documentation:
- Updated History.md to point to GitHub Releases
- Updated README.md with modern badges and improved documentation
- Added SECURITY.md for vulnerability reporting process

Source:
- Migrated source code to TypeScript
- Added new test and benchmark files
- Added type definitions
- Improved error handling

```mermaid
sequenceDiagram
    participant Build as Build System
    participant Test as Test Suite
    participant Security as Security Scan
    participant Deploy as Deployment

    Build->>Test: Run TypeScript build
    Test->>Test: Execute test suite
    Test->>Test: Run benchmarks
    Test-->>Security: Parallel security scan
    Test->>Deploy: Coverage reporting
    Deploy->>Deploy: Package release

@tackley
Copy link
Collaborator

tackley commented Feb 26, 2025

Closing as this will re-introduce the bug fixed by #1444.

Will take another look at how to stop renovate fiddling with overrides when I get a chance - this introduces bugs and should be used to avoid vulns only.

@tackley tackley closed this Feb 26, 2025
Copy link
Contributor Author

renovate bot commented Feb 26, 2025

Renovate Ignore Notification

Because you closed this PR without merging, Renovate will ignore this update. You will not get PRs for any future 8.x releases. But if you manually upgrade to 8.x then Renovate will re-enable minor and patch updates automatically.

If you accidentally closed this PR, or if you changed your mind: rename this PR to get a fresh replacement PR.

@renovate renovate bot deleted the renovate/path-to-regexp>=0.2.0-<1.9.0-8.x branch February 26, 2025 13:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dependencies Pull requests that update a dependency file puLL-Merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant