From 9a1dbd34a63e34c6084d7378bae3c6c24b1c934d Mon Sep 17 00:00:00 2001 From: Susisu Date: Mon, 20 Aug 2018 16:02:28 +0900 Subject: [PATCH 1/8] fix($route): correctly extract path params if path contains question mark or hash The `routeToRegExp()` function, introduced by 840b5f0, could not extract path params if the path contained question mark or hash. Although these characters would normally be encoded in the path, they are decoded by `$location.path()`, before being passed to the RegExp returned by `routeToRegExp()`. `routeToRegExp()` has to be able to deal with both encoded URL and decoded path, because it is being shared between `ngRoute` and `ngMocks`. This commit fixes the issue, by introducing an `isUrl` option that allows creating an appropriate RegExp for each usecase. --- src/ngMock/angular-mocks.js | 4 ++-- src/routeToRegExp.js | 10 +++++----- test/ngRoute/routeParamsSpec.js | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index f6e5cce71e96..e7f78fcccdbc 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -1771,7 +1771,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * See {@link ngMock.$httpBackend#when `when`} for more info. */ $httpBackend.whenRoute = function(method, url) { - var pathObj = routeToRegExp(url, {caseInsensitiveMatch: true, ignoreTrailingSlashes: true}); + var pathObj = routeToRegExp(url, {caseInsensitiveMatch: true, ignoreTrailingSlashes: true, isUrl: true}); return $httpBackend.when(method, pathObj.regexp, undefined, undefined, pathObj.keys); }; @@ -1955,7 +1955,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * See {@link ngMock.$httpBackend#expect `expect`} for more info. */ $httpBackend.expectRoute = function(method, url) { - var pathObj = routeToRegExp(url, {caseInsensitiveMatch: true, ignoreTrailingSlashes: true}); + var pathObj = routeToRegExp(url, {caseInsensitiveMatch: true, ignoreTrailingSlashes: true, isUrl: true}); return $httpBackend.expect(method, pathObj.regexp, undefined, undefined, pathObj.keys); }; diff --git a/src/routeToRegExp.js b/src/routeToRegExp.js index ea0b94efe7f3..d8edd4706359 100644 --- a/src/routeToRegExp.js +++ b/src/routeToRegExp.js @@ -3,7 +3,7 @@ /* global routeToRegExp: true */ /** - * @param path {string} path + * @param pathOrUrl {string} path or url * @param opts {Object} options * @return {?Object} * @@ -13,10 +13,10 @@ * * Inspired by pathRexp in visionmedia/express/lib/utils.js. */ -function routeToRegExp(path, opts) { +function routeToRegExp(pathOrUrl, opts) { var keys = []; - var pattern = path + var pattern = pathOrUrl .replace(/([().])/g, '\\$1') .replace(/(\/)?:(\w+)(\*\?|[?*])?/g, function(_, slash, key, option) { var optional = option === '?' || option === '*?'; @@ -25,7 +25,7 @@ function routeToRegExp(path, opts) { slash = slash || ''; return ( (optional ? '(?:' + slash : slash + '(?:') + - (star ? '([^?#]+?)' : '([^/?#]+)') + + (opts.isUrl ? (star ? '([^?#]+?)' : '([^/?#]+)') : (star ? '(.+?)' : '([^/]+)')) + (optional ? '?)?' : ')') ); }) @@ -36,7 +36,7 @@ function routeToRegExp(path, opts) { } return { - originalPath: path, + originalPath: pathOrUrl, keys: keys, regexp: new RegExp( '^' + pattern + '(?:[?#]|$)', diff --git a/test/ngRoute/routeParamsSpec.js b/test/ngRoute/routeParamsSpec.js index e3357fee8152..6d4f416489f2 100644 --- a/test/ngRoute/routeParamsSpec.js +++ b/test/ngRoute/routeParamsSpec.js @@ -77,5 +77,24 @@ describe('$routeParams', function() { }); }); + it('should correctly extract path params containing hashes and/or question marks', function() { + module(function($routeProvider) { + $routeProvider.when('/foo/:bar', {}); + }); + + inject(function($rootScope, $route, $location, $routeParams) { + $location.path('/foo/bar#baz'); + $rootScope.$digest(); + expect($routeParams).toEqual({bar: 'bar#baz'}); + + $location.path('/foo/bar?baz'); + $rootScope.$digest(); + expect($routeParams).toEqual({bar: 'bar?baz'}); + + $location.path('/foo/bar#baz?qux'); + $rootScope.$digest(); + expect($routeParams).toEqual({bar: 'bar#baz?qux'}); + }); + }); }); From afff8095da6974f950239c894052a2c9b54f89b2 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Thu, 23 Aug 2018 15:23:07 +0300 Subject: [PATCH 2/8] fixup! fix($route): correctly extract path params if path contains question mark or hash --- test/ngRoute/routeParamsSpec.js | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/test/ngRoute/routeParamsSpec.js b/test/ngRoute/routeParamsSpec.js index 6d4f416489f2..88b27dd8409d 100644 --- a/test/ngRoute/routeParamsSpec.js +++ b/test/ngRoute/routeParamsSpec.js @@ -80,20 +80,41 @@ describe('$routeParams', function() { it('should correctly extract path params containing hashes and/or question marks', function() { module(function($routeProvider) { $routeProvider.when('/foo/:bar', {}); + $routeProvider.when('/zoo/:bar/:baz/:qux', {}); }); - inject(function($rootScope, $route, $location, $routeParams) { + inject(function($location, $rootScope, $routeParams) { + $location.path('/foo/bar?baz'); + $rootScope.$digest(); + expect($routeParams).toEqual({bar: 'bar?baz'}); + + $location.path('/foo/bar?baz=val'); + $rootScope.$digest(); + expect($routeParams).toEqual({bar: 'bar?baz=val'}); + $location.path('/foo/bar#baz'); $rootScope.$digest(); expect($routeParams).toEqual({bar: 'bar#baz'}); - $location.path('/foo/bar?baz'); + $location.path('/foo/bar?baz#qux'); $rootScope.$digest(); - expect($routeParams).toEqual({bar: 'bar?baz'}); + expect($routeParams).toEqual({bar: 'bar?baz#qux'}); + + $location.path('/foo/bar?baz=val#qux'); + $rootScope.$digest(); + expect($routeParams).toEqual({bar: 'bar?baz=val#qux'}); $location.path('/foo/bar#baz?qux'); $rootScope.$digest(); expect($routeParams).toEqual({bar: 'bar#baz?qux'}); + + $location.path('/zoo/bar?p1=v1#h1/baz?p2=v2#h2/qux?p3=v3#h3'); + $rootScope.$digest(); + expect($routeParams).toEqual({ + bar: 'bar?p1=v1#h1', + baz: 'baz?p2=v2#h2', + qux: 'qux?p3=v3#h3' + }); }); }); From ff1d24e0fc7746ed8d5b5c62df8f89185fb8dd67 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Tue, 21 Aug 2018 14:57:10 +0300 Subject: [PATCH 3/8] test(ngMocks): use correct method name in `$httpBackend` test --- test/ngMock/angular-mocksSpec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/ngMock/angular-mocksSpec.js b/test/ngMock/angular-mocksSpec.js index 5595e9675d2c..9dd950f51adc 100644 --- a/test/ngMock/angular-mocksSpec.js +++ b/test/ngMock/angular-mocksSpec.js @@ -2251,7 +2251,7 @@ describe('ngMock', function() { } ); they('should ignore query params when matching in ' + routeShortcut + ' $prop method', methods, - function() { + function(method) { angular.forEach([ {route: '/route1/:id', url: '/route1/Alpha', expectedParams: {id: 'Alpha'}}, {route: '/route2/:id', url: '/route2/Bravo/?', expectedParams: {id: 'Bravo'}}, @@ -2268,14 +2268,14 @@ describe('ngMock', function() { ], function(testDataEntry) { callback.calls.reset(); var paramsSpy = jasmine.createSpy('params'); - hb[routeShortcut](this, testDataEntry.route).respond( + hb[routeShortcut](method, testDataEntry.route).respond( function(method, url, data, headers, params) { paramsSpy(params); // status, response, headers, statusText, xhrStatus return [200, 'path', { 'x-header': 'foo' }, 'OK', 'complete']; } ); - hb(this, testDataEntry.url, undefined, callback); + hb(method, testDataEntry.url, undefined, callback); hb.flush(); expect(callback).toHaveBeenCalledOnceWith(200, 'path', 'x-header: foo', 'OK', 'complete'); expect(paramsSpy).toHaveBeenCalledOnceWith(testDataEntry.expectedParams); From 08c095a8e3c94e8316a73bfa55f948b356a4a9e4 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Tue, 21 Aug 2018 14:58:56 +0300 Subject: [PATCH 4/8] refactor(ngRoute): do not unnecessarily return `originalPath` in `routeToRegExp` --- src/ngRoute/route.js | 3 ++- src/routeToRegExp.js | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ngRoute/route.js b/src/ngRoute/route.js index 706600a5bdeb..224455052ebd 100644 --- a/src/ngRoute/route.js +++ b/src/ngRoute/route.js @@ -225,6 +225,7 @@ function $RouteProvider() { } routes[path] = angular.extend( routeCopy, + {originalPath: path}, path && routeToRegExp(path, routeCopy) ); @@ -235,7 +236,7 @@ function $RouteProvider() { : path + '/'; routes[redirectPath] = angular.extend( - {redirectTo: path}, + {originalPath: path, redirectTo: path}, routeToRegExp(redirectPath, routeCopy) ); } diff --git a/src/routeToRegExp.js b/src/routeToRegExp.js index d8edd4706359..b570c6a083a4 100644 --- a/src/routeToRegExp.js +++ b/src/routeToRegExp.js @@ -36,7 +36,6 @@ function routeToRegExp(pathOrUrl, opts) { } return { - originalPath: pathOrUrl, keys: keys, regexp: new RegExp( '^' + pattern + '(?:[?#]|$)', From af758156ad3912d886442b55ab3c2601fb5ec9a5 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Tue, 21 Aug 2018 15:03:16 +0300 Subject: [PATCH 5/8] refactor(ngMocks): clean up `MockHttpExpectation#params()` --- src/ngMock/angular-mocks.js | 41 +++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index e7f78fcccdbc..204503eb1dd0 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -2145,7 +2145,9 @@ function MockHttpExpectation(method, url, data, headers, keys) { }; this.params = function(u) { - return angular.extend(parseQuery(), pathParams()); + var queryStr = u.indexOf('?') === -1 ? '' : u.substring(u.indexOf('?') + 1); + + return angular.extend(parseQuery(queryStr), pathParams()); function pathParams() { var keyObj = {}; @@ -2164,30 +2166,29 @@ function MockHttpExpectation(method, url, data, headers, keys) { return keyObj; } - function parseQuery() { - var obj = {}, key_value, key, - queryStr = u.indexOf('?') > -1 - ? u.substring(u.indexOf('?') + 1) - : ''; - - angular.forEach(queryStr.split('&'), function(keyValue) { - if (keyValue) { - key_value = keyValue.replace(/\+/g,'%20').split('='); - key = tryDecodeURIComponent(key_value[0]); - if (angular.isDefined(key)) { - var val = angular.isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; - if (!hasOwnProperty.call(obj, key)) { - obj[key] = val; - } else if (angular.isArray(obj[key])) { - obj[key].push(val); - } else { - obj[key] = [obj[key],val]; - } + function parseQuery(queryStr) { + var obj = {}, + keyValuePairs = queryStr.split('&'). + filter(angular.identity). // Ignore empty segments. + map(function(keyValue) { return keyValue.replace(/\+/g, '%20').split('='); }); + + angular.forEach(keyValuePairs, function(pair) { + var key = tryDecodeURIComponent(pair[0]); + if (angular.isDefined(key)) { + var val = angular.isDefined(pair[1]) ? tryDecodeURIComponent(pair[1]) : true; + if (!hasOwnProperty.call(obj, key)) { + obj[key] = val; + } else if (angular.isArray(obj[key])) { + obj[key].push(val); + } else { + obj[key] = [obj[key], val]; } } }); + return obj; } + function tryDecodeURIComponent(value) { try { return decodeURIComponent(value); From db8394bc9bc933cb7c34bdc11e89659374b8d2dd Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Tue, 21 Aug 2018 15:08:39 +0300 Subject: [PATCH 6/8] refactor(ngMocks): ignore query/hash when extracting path params for `MockHttpExpectation` --- src/ngMock/angular-mocks.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index 204503eb1dd0..ecdc3a6a9430 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -2092,6 +2092,9 @@ function assertArgDefined(args, index, name) { } } +function stripQueryAndHash(url) { + return url.replace(/[?#].*$/, ''); +} function MockHttpExpectation(method, url, data, headers, keys) { @@ -2146,15 +2149,17 @@ function MockHttpExpectation(method, url, data, headers, keys) { this.params = function(u) { var queryStr = u.indexOf('?') === -1 ? '' : u.substring(u.indexOf('?') + 1); + var strippedUrl = stripQueryAndHash(u); - return angular.extend(parseQuery(queryStr), pathParams()); + return angular.extend(parseQuery(queryStr), pathParams(strippedUrl)); - function pathParams() { + function pathParams(strippedUrl) { var keyObj = {}; if (!url || !angular.isFunction(url.test) || !keys || keys.length === 0) return keyObj; - var m = url.exec(u); + var m = url.exec(strippedUrl); if (!m) return keyObj; + for (var i = 1, len = m.length; i < len; ++i) { var key = keys[i - 1]; var val = m[i]; From eaf7a5db10b9af09c3ca46997073f4b80c099eff Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Sat, 25 Aug 2018 15:25:10 +0300 Subject: [PATCH 7/8] refactor(ngMocks): clean up `MockHttpExpectation` --- src/ngMock/angular-mocks.js | 166 +++++++++++++++++++----------------- 1 file changed, 87 insertions(+), 79 deletions(-) diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index ecdc3a6a9430..0e157bafcd6f 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -2096,112 +2096,120 @@ function stripQueryAndHash(url) { return url.replace(/[?#].*$/, ''); } -function MockHttpExpectation(method, url, data, headers, keys) { +function MockHttpExpectation(expectedMethod, expectedUrl, expectedData, expectedHeaders, + expectedKeys) { - function getUrlParams(u) { - var params = u.slice(u.indexOf('?') + 1).split('&'); - return params.sort(); - } - - function compareUrl(u) { - return (url.slice(0, url.indexOf('?')) === u.slice(0, u.indexOf('?')) && - getUrlParams(url).join() === getUrlParams(u).join()); - } - - this.data = data; - this.headers = headers; + this.data = expectedData; + this.headers = expectedHeaders; - this.match = function(m, u, d, h) { - if (method !== m) return false; - if (!this.matchUrl(u)) return false; - if (angular.isDefined(d) && !this.matchData(d)) return false; - if (angular.isDefined(h) && !this.matchHeaders(h)) return false; + this.match = function(method, url, data, headers) { + if (expectedMethod !== method) return false; + if (!this.matchUrl(url)) return false; + if (angular.isDefined(data) && !this.matchData(data)) return false; + if (angular.isDefined(headers) && !this.matchHeaders(headers)) return false; return true; }; - this.matchUrl = function(u) { - if (!url) return true; - if (angular.isFunction(url.test)) return url.test(u); - if (angular.isFunction(url)) return url(u); - return (url === u || compareUrl(u)); + this.matchUrl = function(url) { + if (!expectedUrl) return true; + if (angular.isFunction(expectedUrl.test)) return expectedUrl.test(url); + if (angular.isFunction(expectedUrl)) return expectedUrl(url); + return (expectedUrl === url || compareUrlWithQuery(url)); }; - this.matchHeaders = function(h) { - if (angular.isUndefined(headers)) return true; - if (angular.isFunction(headers)) return headers(h); - return angular.equals(headers, h); + this.matchHeaders = function(headers) { + if (angular.isUndefined(expectedHeaders)) return true; + if (angular.isFunction(expectedHeaders)) return expectedHeaders(headers); + return angular.equals(expectedHeaders, headers); }; - this.matchData = function(d) { - if (angular.isUndefined(data)) return true; - if (data && angular.isFunction(data.test)) return data.test(d); - if (data && angular.isFunction(data)) return data(d); - if (data && !angular.isString(data)) { - return angular.equals(angular.fromJson(angular.toJson(data)), angular.fromJson(d)); + this.matchData = function(data) { + if (angular.isUndefined(expectedData)) return true; + if (expectedData && angular.isFunction(expectedData.test)) return expectedData.test(data); + if (expectedData && angular.isFunction(expectedData)) return expectedData(data); + if (expectedData && !angular.isString(expectedData)) { + return angular.equals(angular.fromJson(angular.toJson(expectedData)), angular.fromJson(data)); } // eslint-disable-next-line eqeqeq - return data == d; + return expectedData == data; }; this.toString = function() { - return method + ' ' + url; + return expectedMethod + ' ' + expectedUrl; }; - this.params = function(u) { - var queryStr = u.indexOf('?') === -1 ? '' : u.substring(u.indexOf('?') + 1); - var strippedUrl = stripQueryAndHash(u); + this.params = function(url) { + var queryStr = url.indexOf('?') === -1 ? '' : url.substring(url.indexOf('?') + 1); + var strippedUrl = stripQueryAndHash(url); - return angular.extend(parseQuery(queryStr), pathParams(strippedUrl)); + return angular.extend(extractParamsFromQuery(queryStr), extractParamsFromPath(strippedUrl)); + }; - function pathParams(strippedUrl) { - var keyObj = {}; - if (!url || !angular.isFunction(url.test) || !keys || keys.length === 0) return keyObj; + function compareUrlWithQuery(url) { + var urlWithQueryRe = /^([^?]*)\?(.*)$/; - var m = url.exec(strippedUrl); - if (!m) return keyObj; + var expectedMatch = urlWithQueryRe.exec(expectedUrl); + var actualMatch = urlWithQueryRe.exec(url); - for (var i = 1, len = m.length; i < len; ++i) { - var key = keys[i - 1]; - var val = m[i]; - if (key && val) { - keyObj[key.name || key] = val; - } - } + return !!(expectedMatch && actualMatch) && + (expectedMatch[1] === actualMatch[1]) && + (normalizeQuery(expectedMatch[2]) === normalizeQuery(actualMatch[2])); + } - return keyObj; - } + function normalizeQuery(queryStr) { + return queryStr.split('&').sort().join('&'); + } - function parseQuery(queryStr) { - var obj = {}, - keyValuePairs = queryStr.split('&'). - filter(angular.identity). // Ignore empty segments. - map(function(keyValue) { return keyValue.replace(/\+/g, '%20').split('='); }); - - angular.forEach(keyValuePairs, function(pair) { - var key = tryDecodeURIComponent(pair[0]); - if (angular.isDefined(key)) { - var val = angular.isDefined(pair[1]) ? tryDecodeURIComponent(pair[1]) : true; - if (!hasOwnProperty.call(obj, key)) { - obj[key] = val; - } else if (angular.isArray(obj[key])) { - obj[key].push(val); - } else { - obj[key] = [obj[key], val]; - } - } - }); + function extractParamsFromPath(strippedUrl) { + var keyObj = {}; - return obj; + if (!expectedUrl || !angular.isFunction(expectedUrl.test) || + !expectedKeys || !expectedKeys.length) return keyObj; + + var match = expectedUrl.exec(strippedUrl); + if (!match) return keyObj; + + for (var i = 1, len = match.length; i < len; ++i) { + var key = expectedKeys[i - 1]; + var val = match[i]; + if (key && val) { + keyObj[key.name || key] = val; + } } - function tryDecodeURIComponent(value) { - try { - return decodeURIComponent(value); - } catch (e) { - // Ignore any invalid uri component + return keyObj; + } + + function extractParamsFromQuery(queryStr) { + var obj = {}, + keyValuePairs = queryStr.split('&'). + filter(angular.identity). // Ignore empty segments. + map(function(keyValue) { return keyValue.replace(/\+/g, '%20').split('='); }); + + angular.forEach(keyValuePairs, function(pair) { + var key = tryDecodeURIComponent(pair[0]); + if (angular.isDefined(key)) { + var val = angular.isDefined(pair[1]) ? tryDecodeURIComponent(pair[1]) : true; + if (!hasOwnProperty.call(obj, key)) { + obj[key] = val; + } else if (angular.isArray(obj[key])) { + obj[key].push(val); + } else { + obj[key] = [obj[key], val]; + } } + }); + + return obj; + } + + function tryDecodeURIComponent(value) { + try { + return decodeURIComponent(value); + } catch (e) { + // Ignore any invalid uri component } - }; + } } function createMockXhr() { From 9b9e25fc8b40faf75075140439f6a384c88a9dce Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Tue, 21 Aug 2018 15:13:21 +0300 Subject: [PATCH 8/8] refactor(ngMocks): simplify `routeToRegExp` by assuming `path` has query/hash stripped off --- src/ngMock/angular-mocks.js | 14 ++++++++++---- src/routeToRegExp.js | 21 +++++++++++---------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index 0e157bafcd6f..7537dcab463a 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -1771,8 +1771,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * See {@link ngMock.$httpBackend#when `when`} for more info. */ $httpBackend.whenRoute = function(method, url) { - var pathObj = routeToRegExp(url, {caseInsensitiveMatch: true, ignoreTrailingSlashes: true, isUrl: true}); - return $httpBackend.when(method, pathObj.regexp, undefined, undefined, pathObj.keys); + var parsed = parseRouteUrl(url); + return $httpBackend.when(method, parsed.regexp, undefined, undefined, parsed.keys); }; /** @@ -1955,8 +1955,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * See {@link ngMock.$httpBackend#expect `expect`} for more info. */ $httpBackend.expectRoute = function(method, url) { - var pathObj = routeToRegExp(url, {caseInsensitiveMatch: true, ignoreTrailingSlashes: true, isUrl: true}); - return $httpBackend.expect(method, pathObj.regexp, undefined, undefined, pathObj.keys); + var parsed = parseRouteUrl(url); + return $httpBackend.expect(method, parsed.regexp, undefined, undefined, parsed.keys); }; @@ -2084,6 +2084,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { }; }); } + + function parseRouteUrl(url) { + var strippedUrl = stripQueryAndHash(url); + var parseOptions = {caseInsensitiveMatch: true, ignoreTrailingSlashes: true}; + return routeToRegExp(strippedUrl, parseOptions); + } } function assertArgDefined(args, index, name) { diff --git a/src/routeToRegExp.js b/src/routeToRegExp.js index b570c6a083a4..c2dc8d817843 100644 --- a/src/routeToRegExp.js +++ b/src/routeToRegExp.js @@ -3,29 +3,30 @@ /* global routeToRegExp: true */ /** - * @param pathOrUrl {string} path or url - * @param opts {Object} options - * @return {?Object} + * @param {string} path - The path to parse. (It is assumed to have query and hash stripped off.) + * @param {Object} opts - Options. + * @return {Object} - An object containing an array of path parameter names (`keys`) and a regular + * expression (`regexp`) that can be used to identify a matching URL and extract the path + * parameter values. * * @description - * Normalizes the given path, returning a regular expression - * and the original path. + * Parses the given path, extracting path parameter names and a regular expression to match URLs. * - * Inspired by pathRexp in visionmedia/express/lib/utils.js. + * Originally inspired by `pathRexp` in `visionmedia/express/lib/utils.js`. */ -function routeToRegExp(pathOrUrl, opts) { +function routeToRegExp(path, opts) { var keys = []; - var pattern = pathOrUrl + var pattern = path .replace(/([().])/g, '\\$1') .replace(/(\/)?:(\w+)(\*\?|[?*])?/g, function(_, slash, key, option) { var optional = option === '?' || option === '*?'; var star = option === '*' || option === '*?'; - keys.push({ name: key, optional: optional }); + keys.push({name: key, optional: optional}); slash = slash || ''; return ( (optional ? '(?:' + slash : slash + '(?:') + - (opts.isUrl ? (star ? '([^?#]+?)' : '([^/?#]+)') : (star ? '(.+?)' : '([^/]+)')) + + (star ? '(.+?)' : '([^/]+)') + (optional ? '?)?' : ')') ); })