diff --git a/README.md b/README.md index 156c380..a599d99 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,60 @@ router.param('user_id', function (req, res, next, id) { }) ``` +### route.getRoutes() + +Returns an array of all the routes registered on this route, including +all the methods, key, and the options of instance of router. + +```js +const router = new Router({ strict: true, caseSensitive: true }) +const admin = new Router({ strict: true, caseSensitive: false }) + +admin.use((req, res, next) => { + // some middleware for admin routes + next() +}) + +admin.get('/', (req, res, next) => { + res.end('Hello') +}) + +router.use("/admin", admin) + +router.all('/:id', function (req, res) { + res.end('Hello') +}) + +console.log(router.getRoutes()) +// [ +// { +// name: 'router', +// path: '/admin', +// methods: undefined, +// keys: undefined, +// router: [ +// { +// name: 'handle', +// path: '/', +// methods: ['GET'], +// keys: undefined, +// router: undefined, +// options: { strict: true, caseSensitive: false, end: true }, +// } +// ], +// options: { strict: true, caseSensitive: true, end: false } +// }, +// { +// name: 'handle', +// path: '/:id', +// methods: ['_ALL'], +// keys: [{ name: 'id', type: "param" }], +// router: undefined, +// options: { strict: true, caseSensitive: true, end: true } +// } +// ] +``` + ### router.route(path) Creates an instance of a single `Route` for the given `path`. diff --git a/index.js b/index.js index 4358aeb..38cba4f 100644 --- a/index.js +++ b/index.js @@ -14,9 +14,11 @@ const isPromise = require('is-promise') const Layer = require('./lib/layer') +const { MATCHING_GROUP_REGEXP } = require('./lib/layer') const { METHODS } = require('node:http') const parseUrl = require('parseurl') const Route = require('./lib/route') +const pathRegexp = require('path-to-regexp') const debug = require('debug')('router') const deprecate = require('depd')('router') @@ -441,6 +443,23 @@ Router.prototype.route = function route (path) { return route } +/** + * List all registered routes. + * + * @return {Array} An array of route paths + * @public + */ +Router.prototype.getRoutes = function getRoutes () { + const stack = this.stack + + const options = { + strict: this.strict, + caseSensitive: this.caseSensitive + } + + return collectRoutes(stack, options) +} + // create Router#VERB functions methods.concat('all').forEach(function (method) { Router.prototype[method] = function (path) { @@ -450,6 +469,118 @@ methods.concat('all').forEach(function (method) { } }) +/** + * Collect routes from a router stack recursively. + * + * @param {Array} stack - The router stack to collect routes from + * @param {object} options - The router options + * @private + */ +function collectRoutes (stack, options) { + const routes = [] + + for (const layer of stack) { + // route layer (has methods) + if (layer.pathPatterns && layer.route) { + const methods = Object.keys(layer.route.methods).map((method) => method.toUpperCase()) + + if (Array.isArray(layer.pathPatterns)) { + for (const pathPattern of layer.pathPatterns) { + const keys = extractPatternKeys(pathPattern) + + routes.push({ + name: layer.name, + path: pathPattern, + keys, + methods, + router: undefined, + options: { ...options, end: layer.end } + }) + } + } else { + const keys = extractPatternKeys(layer.pathPatterns) + + routes.push({ + name: layer.name, + path: layer.pathPatterns, + keys, + methods, + router: undefined, + options: { ...options, end: layer.end } + }) + } + } + + // mounted router (use) + if (layer.pathPatterns && layer.handle && layer.handle.stack && !layer.route) { + if (Array.isArray(layer.pathPatterns)) { + for (const pathPattern of layer.pathPatterns) { + const inner = collectRoutes( + layer.handle.stack, + { strict: layer.handle.strict, caseSensitive: layer.handle.caseSensitive } + ) + const keys = extractPatternKeys(pathPattern) + + routes.push({ + name: layer.name, + path: pathPattern, + keys, + methods: undefined, + router: inner.length ? inner : undefined, + options: { ...options, end: layer.end } + }) + } + } else { + const inner = collectRoutes( + layer.handle.stack, + { strict: layer.handle.strict, caseSensitive: layer.handle.caseSensitive } + ) + const keys = extractPatternKeys(layer.pathPatterns) + + routes.push({ + name: layer.name, + path: layer.pathPatterns, + keys, + methods: undefined, + router: inner.length ? inner : undefined, + options: { ...options, end: layer.end } + }) + } + } + } + + return routes +} + +/** + * Extracts parameter/key descriptors from a route pattern. + * + * @param {string|RegExp} pattern - Route pattern to analyze (path string or RegExp). + * @returns {Array|undefined} Array of key descriptor objects (each with at least a `name` property), or `undefined` if none found. + * @private + */ +function extractPatternKeys (pattern) { + if (pattern instanceof RegExp) { + const keys = [] + let name = 0 + let m + // eslint-disable-next-line no-cond-assign + while (m = MATCHING_GROUP_REGEXP.exec(pattern.source)) { + keys.push({ name: m[1] || name++ }) + } + + return keys.length > 0 ? keys : undefined + } + + const pathKeys = pathRegexp.pathToRegexp(String(pattern)).keys + + if (pathKeys && pathKeys.length > 0) { + return pathKeys + } + + return undefined +} + /** * Generate a callback that will make an OPTIONS response. * diff --git a/lib/layer.js b/lib/layer.js index 6a4408f..a443584 100644 --- a/lib/layer.js +++ b/lib/layer.js @@ -30,6 +30,7 @@ const MATCHING_GROUP_REGEXP = /\((?:\?<(.*?)>)?(?!\?)/g */ module.exports = Layer +module.exports.MATCHING_GROUP_REGEXP = MATCHING_GROUP_REGEXP function Layer (path, options, fn) { if (!(this instanceof Layer)) { @@ -43,7 +44,11 @@ function Layer (path, options, fn) { this.keys = [] this.name = fn.name || '' this.params = undefined + // path is determinate in runtime execution this.path = undefined + this.end = opts.end + + this.pathPatterns = path this.slash = path === '/' && opts.end === false function matcher (_path) { diff --git a/package.json b/package.json index e465e6c..e6be557 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,8 @@ }, "scripts": { "lint": "standard", - "test": "mocha --reporter spec --bail --check-leaks test/", - "test:debug": "mocha --reporter spec --bail --check-leaks test/ --inspect --inspect-brk", + "test": "mocha --reporter spec --check-leaks test/", + "test:debug": "mocha --reporter spec --check-leaks test/ --inspect --inspect-brk", "test-ci": "nyc --reporter=lcov --reporter=text npm test", "test-cov": "nyc --reporter=text npm test", "version": "node scripts/version-history.js && git add HISTORY.md" diff --git a/test/getRoutes.js b/test/getRoutes.js new file mode 100644 index 0000000..b579092 --- /dev/null +++ b/test/getRoutes.js @@ -0,0 +1,633 @@ +const { it, describe } = require('mocha') +const Router = require('..') +const utils = require('./support/utils') + +const assert = utils.assert + +describe('getRoutes', function () { + it('should return an empty array when no routes are registered', function () { + const router = new Router() + + assert.deepStrictEqual(router.getRoutes(), []) + }) + + it('should return route information for various route types (strings, arrays, and parameterized paths)', function () { + const router = new Router() + + router.all('/', noop) + router.route('/test2/') + router.route('/test/').get(noop) + router.all(/^\/[a-z]oo$/, noop) + router.get(['/foo', '/bar'], noop) + router.post('/:id/setting/:thing', noop) + + assert.deepStrictEqual(router.getRoutes(), + [ + { name: 'handle', path: '/', methods: ['_ALL'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined }, + { name: 'handle', path: '/test2/', methods: [], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined }, // Todo: Investigate + { name: 'handle', path: '/test/', methods: ['GET'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined }, + { name: 'handle', path: /^\/[a-z]oo$/, methods: ['_ALL'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined }, + { name: 'handle', path: '/foo', methods: ['GET'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined }, + { name: 'handle', path: '/bar', methods: ['GET'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined }, + { name: 'handle', path: '/:id/setting/:thing', methods: ['POST'], keys: [{ name: 'id', type: 'param' }, { name: 'thing', type: 'param' }], options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined } + ]) + }) + + it('should track multiple registrations of the same route with different HTTP methods', function () { + const router = new Router() + + router.post(['/test', '/test2'], noop) + + for (let i = 0; i < 2; i++) { + router.get(['/test', '/test3'], noop) + } + + router.put('/test3', noop) + + assert.deepStrictEqual(router.getRoutes(), [ + { name: 'handle', path: '/test', methods: ['POST'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined }, + { name: 'handle', path: '/test2', methods: ['POST'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined }, + { name: 'handle', path: '/test', methods: ['GET'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined }, + { name: 'handle', path: '/test3', methods: ['GET'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined }, + { name: 'handle', path: '/test', methods: ['GET'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined }, + { name: 'handle', path: '/test3', methods: ['GET'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined }, + { name: 'handle', path: '/test3', methods: ['PUT'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined } + ]) + }) + + it('should properly handle nested routers and multiple mount points', function () { + const router = new Router() + const inner = new Router() + router.post('/test', noop) + + for (let i = 0; i < 2; i++) { + router.get('/test', noop) + } + + for (let i = 0; i < 2; i++) { + inner.get('/test', noop) + } + + router.use(['/test/', '/test2', '/test3'], inner) + router.use('/test4/', inner) + router.route('/test5').get(noop).post(noop) + + assert.deepStrictEqual(router.getRoutes(), [ + { name: 'handle', path: '/test', methods: ['POST'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined }, + { name: 'handle', path: '/test', methods: ['GET'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined }, + { name: 'handle', path: '/test', methods: ['GET'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined }, + { + name: 'router', + path: '/test/', + methods: undefined, + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: false }, + router: [ + { + name: 'handle', + keys: undefined, + methods: ['GET'], + options: { strict: undefined, caseSensitive: undefined, end: true }, + path: '/test', + router: undefined + }, { + name: 'handle', + keys: undefined, + methods: ['GET'], + options: { strict: undefined, caseSensitive: undefined, end: true }, + path: '/test', + router: undefined + }] + }, + { + name: 'router', + path: '/test2', + methods: undefined, + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: false }, + router: [ + { + name: 'handle', + keys: undefined, + methods: ['GET'], + options: { strict: undefined, caseSensitive: undefined, end: true }, + path: '/test', + router: undefined + }, { + name: 'handle', + keys: undefined, + methods: ['GET'], + options: { strict: undefined, caseSensitive: undefined, end: true }, + path: '/test', + router: undefined + }] + }, + { + name: 'router', + path: '/test3', + methods: undefined, + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: false }, + router: [ + { + name: 'handle', + keys: undefined, + methods: ['GET'], + options: { strict: undefined, caseSensitive: undefined, end: true }, + path: '/test', + router: undefined + }, { + name: 'handle', + keys: undefined, + methods: ['GET'], + options: { strict: undefined, caseSensitive: undefined, end: true }, + path: '/test', + router: undefined + }] + }, + { + name: 'router', + path: '/test4/', + methods: undefined, + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: false }, + router: [ + { + name: 'handle', + keys: undefined, + methods: ['GET'], + options: { strict: undefined, caseSensitive: undefined, end: true }, + path: '/test', + router: undefined + }, { + name: 'handle', + keys: undefined, + methods: ['GET'], + options: { strict: undefined, caseSensitive: undefined, end: true }, + path: '/test', + router: undefined + }] + }, + { name: 'handle', path: '/test5', methods: ['GET', 'POST'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined } + ]) + }) + + it('should correctly flatten deeply nested router hierarchies with multiple levels', function () { + const router = new Router() + const inner = new Router() + const subinner = new Router() + + subinner.put('/t5', noop) + subinner.all(/^\/[a-z]oo$/, noop) + subinner.use(noop) + + inner.use('/t3', subinner) + inner.all('/t4', noop) + inner.get('/', noop) + inner.use(noop) + + router.use('/t2', inner) + router.use(['/t5', '/t7'], inner) + + router.use(noop) + router.use('/test1', noop) + + assert.deepStrictEqual(router.getRoutes(), [ + { + name: 'router', + path: '/t2', + methods: undefined, + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: false }, + router: [ + { + name: 'router', + path: '/t3', + methods: undefined, + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: false }, + router: [ + { name: 'handle', path: '/t5', methods: ['PUT'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined }, + { name: 'handle', path: /^\/[a-z]oo$/, methods: ['_ALL'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined } + ] + }, + { + name: 'handle', + path: '/t4', + methods: ['_ALL'], + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: true }, + router: undefined + }, + { + name: 'handle', + path: '/', + methods: ['GET'], + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: true }, + router: undefined + } + ] + }, + { + name: 'router', + path: '/t5', + methods: undefined, + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: false }, + router: [{ + name: 'router', + path: '/t3', + methods: undefined, + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: false }, + router: [ + { name: 'handle', path: '/t5', methods: ['PUT'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined }, + { name: 'handle', path: /^\/[a-z]oo$/, methods: ['_ALL'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined } + ] + }, + { + name: 'handle', + path: '/t4', + methods: ['_ALL'], + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: true }, + router: undefined + }, + { + name: 'handle', + path: '/', + methods: ['GET'], + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: true }, + router: undefined + } + ] + }, + { + name: 'router', + path: '/t7', + methods: undefined, + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: false }, + router: [ + { + name: 'router', + path: '/t3', + methods: undefined, + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: false }, + router: [ + { name: 'handle', path: '/t5', methods: ['PUT'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined }, + { name: 'handle', path: /^\/[a-z]oo$/, methods: ['_ALL'], keys: undefined, options: { strict: undefined, caseSensitive: undefined, end: true }, router: undefined } + ] + }, + { + name: 'handle', + path: '/t4', + methods: ['_ALL'], + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: true }, + router: undefined + }, + { + name: 'handle', + path: '/', + methods: ['GET'], + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: true }, + router: undefined + } + ] + } + + ]) + }) + + it('should avoid double slashes when mounting routers at root path', function () { + const router = new Router() + const subRouter = new Router() + + subRouter.get('/api', () => {}) + router.use('/', subRouter) + + const routes = router.getRoutes() + + assert.deepStrictEqual(routes, [ + { + name: 'router', + path: '/', + methods: undefined, + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: false }, + router: [{ + name: 'handle', + path: '/api', + methods: ['GET'], + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: true }, + router: undefined + }] + } + ]) + }) + + it('should return keys for routes with regex', function () { + const router = new Router() + const subRouter = new Router() + + subRouter.get(/\/(?[0-9]+)/, () => {}) + + router.use(/\/page_([0-9]+)/, subRouter) + + const routes = router.getRoutes() + + assert.deepStrictEqual(routes, [ + { + name: 'router', + path: /\/page_([0-9]+)/, + methods: undefined, + keys: [{ name: 0 }], + options: { strict: undefined, caseSensitive: undefined, end: false }, + router: [{ + name: 'handle', + path: /\/(?[0-9]+)/, + methods: ['GET'], + keys: [{ name: 'foo' }], + options: { strict: undefined, caseSensitive: undefined, end: true }, + router: undefined + }] + } + ]) + }) + + it('should return keys for dynamic routes', function () { + const router = new Router() + const subRouter = new Router() + const anotherSubRouter = new Router() + + subRouter.get('/api', () => {}) + anotherSubRouter.get('/api2', () => {}) + + router.use('/:test', subRouter) + router.use(['/:lang', '/ls'], anotherSubRouter) + + const routes = router.getRoutes() + + assert.deepStrictEqual(routes, [ + { + name: 'router', + path: '/:test', + methods: undefined, + keys: [{ name: 'test', type: 'param' }], + options: { strict: undefined, caseSensitive: undefined, end: false }, + router: [{ + name: 'handle', + path: '/api', + methods: ['GET'], + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: true }, + router: undefined + }] + }, { + name: 'router', + path: '/:lang', + methods: undefined, + keys: [{ name: 'lang', type: 'param' }], + options: { strict: undefined, caseSensitive: undefined, end: false }, + router: [{ + name: 'handle', + path: '/api2', + methods: ['GET'], + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: true }, + router: undefined + }] + }, { + name: 'router', + path: '/ls', + methods: undefined, + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: false }, + router: [{ + name: 'handle', + path: '/api2', + methods: ['GET'], + keys: undefined, + options: { strict: undefined, caseSensitive: undefined, end: true }, + router: undefined + }] + } + ]) + }) + + it('should preserve router configuration options from parent to child routers', function () { + const router = new Router({ strict: true, caseSensitive: true }) + const inner = new Router({ strict: true, caseSensitive: false, end: false }) + const subinner = new Router({ strict: false, caseSensitive: false }) + + subinner.put('/t8', noop) + subinner.use(noop) + + inner.use('/t3', subinner) + inner.all('/t4', noop) + inner.get('/', noop) + inner.use(noop) + + router.use('/t2', inner) + router.use(['/t5', '/t7'], inner) + + router.use(noop) + router.get('/test', noop) + + assert.deepStrictEqual(router.getRoutes(), [ + { + name: 'router', + path: '/t2', + methods: undefined, + keys: undefined, + options: { strict: true, caseSensitive: true, end: false }, + router: [ + { + name: 'router', + path: '/t3', + methods: undefined, + keys: undefined, + options: { strict: true, caseSensitive: false, end: false }, + router: [ + { name: 'handle', path: '/t8', methods: ['PUT'], keys: undefined, options: { strict: false, caseSensitive: false, end: true }, router: undefined } + ] + }, { + name: 'handle', + path: '/t4', + methods: ['_ALL'], + keys: undefined, + options: { strict: true, caseSensitive: false, end: true }, + router: undefined + }, { + name: 'handle', + path: '/', + methods: ['GET'], + keys: undefined, + options: { strict: true, caseSensitive: false, end: true }, + router: undefined + } + ] + }, + { + name: 'router', + path: '/t5', + methods: undefined, + keys: undefined, + options: { strict: true, caseSensitive: true, end: false }, + router: [ + { + name: 'router', + path: '/t3', + methods: undefined, + keys: undefined, + options: { strict: true, caseSensitive: false, end: false }, + router: [ + { name: 'handle', path: '/t8', methods: ['PUT'], keys: undefined, options: { strict: false, caseSensitive: false, end: true }, router: undefined } + ] + }, { + name: 'handle', + path: '/t4', + methods: ['_ALL'], + keys: undefined, + options: { strict: true, caseSensitive: false, end: true }, + router: undefined + }, { + name: 'handle', + path: '/', + methods: ['GET'], + keys: undefined, + options: { strict: true, caseSensitive: false, end: true }, + router: undefined + } + ] + }, + { + name: 'router', + path: '/t7', + methods: undefined, + keys: undefined, + options: { strict: true, caseSensitive: true, end: false }, + router: [ + { + name: 'router', + path: '/t3', + methods: undefined, + keys: undefined, + options: { strict: true, caseSensitive: false, end: false }, + router: [ + { name: 'handle', path: '/t8', methods: ['PUT'], keys: undefined, options: { strict: false, caseSensitive: false, end: true }, router: undefined } + ] + }, { + name: 'handle', + path: '/t4', + methods: ['_ALL'], + keys: undefined, + options: { strict: true, caseSensitive: false, end: true }, + router: undefined + }, { + name: 'handle', + path: '/', + methods: ['GET'], + keys: undefined, + options: { strict: true, caseSensitive: false, end: true }, + router: undefined + } + ] + }, + { + name: 'handle', + path: '/test', + methods: ['GET'], + keys: undefined, + options: { strict: true, caseSensitive: true, end: true }, + router: undefined + } + ]) + }) + + it('should handle multiple routers with different configuration options mounted at the same path', function () { + const router = new Router({ strict: true, caseSensitive: true }) + const inner = new Router({ strict: true, caseSensitive: false, end: false }) + const otherInner = new Router({ strict: true, caseSensitive: true, end: false }) + const otherInner2 = new Router({ strict: true, caseSensitive: false }) + + otherInner2.put('/:t5', noop) + otherInner2.get('/:t6', noop) + + otherInner.put('/:t5', noop) + otherInner.post('/:t6', noop) + + inner.use('/t2', otherInner) + inner.use('/t2', otherInner2) + + router.use(inner) + + assert.deepStrictEqual(router.getRoutes(), [ + { + name: 'router', + path: '/', + methods: undefined, + keys: undefined, + options: { strict: true, caseSensitive: true, end: false }, + router: [ + { + name: 'router', + path: '/t2', + methods: undefined, + keys: undefined, + options: { strict: true, caseSensitive: false, end: false }, + router: [ + { + name: 'handle', + path: '/:t5', + methods: ['PUT'], + keys: [{ name: 't5', type: 'param' }], + options: { strict: true, caseSensitive: true, end: true }, + router: undefined + }, + { + name: 'handle', + path: '/:t6', + methods: ['POST'], + keys: [{ name: 't6', type: 'param' }], + options: { strict: true, caseSensitive: true, end: true }, + router: undefined + } + ] + }, + { + name: 'router', + path: '/t2', + methods: undefined, + keys: undefined, + options: { strict: true, caseSensitive: false, end: false }, + router: [{ + name: 'handle', + path: '/:t5', + methods: ['PUT'], + keys: [{ name: 't5', type: 'param' }], + options: { strict: true, caseSensitive: false, end: true }, + router: undefined + }, + { + name: 'handle', + path: '/:t6', + methods: ['GET'], + keys: [{ name: 't6', type: 'param' }], + options: { strict: true, caseSensitive: false, end: true }, + router: undefined + }] + } + ] + } + ]) + }) +}) + +function noop () {}