From e3d9840b4e345c7ffba481812914272ac9fe69eb Mon Sep 17 00:00:00 2001 From: Jeremy Roman Date: Thu, 2 Nov 2023 22:42:26 -0400 Subject: [PATCH 1/2] Apply syntax changes proposed in whatwg/urlpattern#179. --- src/url-pattern-parser.ts | 50 +++++++++++++++++++++++++-------------- src/url-pattern.ts | 46 +++++++++++++++++++++++++---------- 2 files changed, 66 insertions(+), 30 deletions(-) diff --git a/src/url-pattern-parser.ts b/src/url-pattern-parser.ts index d500e0a..651428d 100644 --- a/src/url-pattern-parser.ts +++ b/src/url-pattern-parser.ts @@ -106,11 +106,8 @@ export class Parser { this.#changeState(State.HASH, /*skip=*/1); } else if (this.#isSearchPrefix()) { this.#changeState(State.SEARCH, /*skip=*/1); - this.#internalResult.hash = ''; } else { this.#changeState(State.PATHNAME, /*skip=*/0); - this.#internalResult.search = ''; - this.#internalResult.hash = ''; } continue; } @@ -147,17 +144,6 @@ export class Parser { switch (this.#state) { case State.INIT: if (this.#isProtocolSuffix()) { - // We are in absolute mode and we know values will not be inherited - // from a base URL. Therefore initialize the rest of the components - // to the empty string. - this.#internalResult.username = ''; - this.#internalResult.password = ''; - this.#internalResult.hostname = ''; - this.#internalResult.port = ''; - this.#internalResult.pathname = ''; - this.#internalResult.search = ''; - this.#internalResult.hash = ''; - // Update the state to expect the start of an absolute URL. this.#rewindAndSetState(State.PROTOCOL); } @@ -179,10 +165,6 @@ export class Parser { let nextState: State = State.PATHNAME; let skip: number = 1; - if (this.#shouldTreatAsStandardURL) { - this.#internalResult.pathname = '/'; - } - // If there are authority slashes, like `https://`, then // we must transition to the authority section of the URLPattern. if (this.#nextIsAuthoritySlashes()) { @@ -319,6 +301,13 @@ export class Parser { break; } } + + if (this.#internalResult.hostname !== undefined && + this.#internalResult.port === undefined) { + // If the hostname is specified in a constructor string but the port is + // not, the default port is assumed to be meant. + this.#internalResult.port = ''; + } } #changeState(newState: State, skip: number): void { @@ -358,6 +347,31 @@ export class Parser { break; } + if (this.#state !== State.INIT && newState !== State.DONE) { + // If hostname, pathname or search is skipped but something appears after + // it, then it takes its default value (usually the empty string). + if ([State.PROTOCOL, State.AUTHORITY, State.USERNAME, State.PASSWORD] + .includes(this.#state) && + [State.PORT, State.PATHNAME, State.SEARCH, State.HASH] + .includes(newState)) { + this.#internalResult.hostname ??= ''; + } + if ([State.PROTOCOL, State.AUTHORITY, State.USERNAME, State.PASSWORD, + State.HOSTNAME, State.PORT] + .includes(this.#state) && + [State.SEARCH, State.HASH] + .includes(newState)) { + this.#internalResult.pathname ??= + (this.#shouldTreatAsStandardURL ? '/' : ''); + } + if ([State.PROTOCOL, State.AUTHORITY, State.USERNAME, State.PASSWORD, + State.HOSTNAME, State.PORT, State.PATHNAME] + .includes(this.#state) && + newState === State.HASH) { + this.#internalResult.search ??= ''; + } + } + this.#changeStateWithoutSettingComponent(newState, skip); } diff --git a/src/url-pattern.ts b/src/url-pattern.ts index 68d1e9f..66867a8 100644 --- a/src/url-pattern.ts +++ b/src/url-pattern.ts @@ -77,22 +77,44 @@ function processBaseURLString(input: string, isPattern: boolean) { function applyInit(o: URLPatternInit, init: URLPatternInit, isPattern: boolean): URLPatternInit { // If there is a baseURL we need to apply its component values first. The // rest of the URLPatternInit structure will then later override these - // values. Note, the baseURL will always set either an empty string or - // longer value for each considered component. We do not allow null strings - // to persist for these components past this phase since they should no - // longer be treated as wildcards. + // values. let baseURL; if (typeof init.baseURL === 'string') { try { baseURL = new URL(init.baseURL); - o.protocol = processBaseURLString(baseURL.protocol.substring(0, baseURL.protocol.length - 1), isPattern); - o.username = processBaseURLString(baseURL.username, isPattern); - o.password = processBaseURLString(baseURL.password, isPattern); - o.hostname = processBaseURLString(baseURL.hostname, isPattern); - o.port = processBaseURLString(baseURL.port, isPattern); - o.pathname = processBaseURLString(baseURL.pathname, isPattern); - o.search = processBaseURLString(baseURL.search.substring(1, baseURL.search.length), isPattern); - o.hash = processBaseURLString(baseURL.hash.substring(1, baseURL.hash.length), isPattern); + if (init.protocol === undefined) { + o.protocol = processBaseURLString(baseURL.protocol.substring(0, baseURL.protocol.length - 1), isPattern); + } + if (!isPattern && init.protocol === undefined && init.hostname === undefined && + init.port === undefined && init.username === undefined) { + o.username = processBaseURLString(baseURL.username, isPattern); + } + if (!isPattern && init.protocol === undefined && init.hostname === undefined && + init.port === undefined && init.username === undefined && + init.password === undefined) { + o.password = processBaseURLString(baseURL.password, isPattern); + } + if (init.protocol === undefined && init.hostname === undefined) { + o.hostname = processBaseURLString(baseURL.hostname, isPattern); + } + if (init.protocol === undefined && init.hostname === undefined && + init.port === undefined) { + o.port = processBaseURLString(baseURL.port, isPattern); + } + if (init.protocol === undefined && init.hostname === undefined && + init.port === undefined && init.pathname === undefined) { + o.pathname = processBaseURLString(baseURL.pathname, isPattern); + } + if (init.protocol === undefined && init.hostname === undefined && + init.port === undefined && init.pathname === undefined && + init.search === undefined) { + o.search = processBaseURLString(baseURL.search.substring(1, baseURL.search.length), isPattern); + } + if (init.protocol === undefined && init.hostname === undefined && + init.port === undefined && init.pathname === undefined && + init.search === undefined && init.hash === undefined) { + o.hash = processBaseURLString(baseURL.hash.substring(1, baseURL.hash.length), isPattern); + } } catch { throw new TypeError(`invalid baseURL '${init.baseURL}'.`); } From 6e82268651892e3d6a21c20064047e91ed02dcf9 Mon Sep 17 00:00:00 2001 From: Jeremy Roman Date: Thu, 2 Nov 2023 23:02:01 -0400 Subject: [PATCH 2/2] port tests from WPT --- test/urlpatterntestdata.json | 254 +++++++++++++++++------------------ test/urlpatterntests.js | 25 +++- 2 files changed, 144 insertions(+), 135 deletions(-) diff --git a/test/urlpatterntestdata.json b/test/urlpatterntestdata.json index 56b3a0c..4a05e4c 100644 --- a/test/urlpatterntestdata.json +++ b/test/urlpatterntestdata.json @@ -80,16 +80,19 @@ "baseURL": "https://example.com?query#hash" }], "inputs": [{ "protocol": "https", "hostname": "example.com", "pathname": "/foo/bar" }], - "exactly_empty_components": [ "username", "password", "port" ], - "expected_match": null + "exactly_empty_components": [ "port" ], + "expected_match": { + "hostname": { "input": "example.com", "groups": {} }, + "pathname": { "input": "/foo/bar", "groups": {} }, + "protocol": { "input": "https", "groups": {} } + } }, { "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com" }], "inputs": [{ "protocol": "https", "hostname": "example.com", "pathname": "/foo/bar" }], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_match": { "hostname": { "input": "example.com", "groups": {} }, "pathname": { "input": "/foo/bar", "groups": {} }, @@ -109,8 +112,14 @@ "inputs": [{ "protocol": "https", "hostname": "example.com", "pathname": "/foo/bar", "search": "otherquery", "hash": "otherhash" }], - "exactly_empty_components": [ "username", "password", "port" ], - "expected_match": null + "exactly_empty_components": [ "port" ], + "expected_match": { + "hash": { "input": "otherhash", "groups": { "0": "otherhash" } }, + "hostname": { "input": "example.com", "groups": {} }, + "pathname": { "input": "/foo/bar", "groups": {} }, + "protocol": { "input": "https", "groups": {} }, + "search": { "input": "otherquery", "groups": { "0": "otherquery" } } + } }, { "pattern": [{ "pathname": "/foo/bar", @@ -118,9 +127,14 @@ "inputs": [{ "protocol": "https", "hostname": "example.com", "pathname": "/foo/bar", "search": "otherquery", "hash": "otherhash" }], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], - "expected_match": null + "exactly_empty_components": [ "port" ], + "expected_match": { + "hash": { "input": "otherhash", "groups": { "0": "otherhash" } }, + "hostname": { "input": "example.com", "groups": {} }, + "pathname": { "input": "/foo/bar", "groups": {} }, + "protocol": { "input": "https", "groups": {} }, + "search": { "input": "otherquery", "groups": { "0": "otherquery" } } + } }, { "pattern": [{ "pathname": "/foo/bar", @@ -128,40 +142,50 @@ "inputs": [{ "protocol": "https", "hostname": "example.com", "pathname": "/foo/bar", "search": "otherquery", "hash": "otherhash" }], - "exactly_empty_components": [ "username", "password", "port" ], + "exactly_empty_components": [ "port" ], "expected_match": { - "hash": { "input": "otherhash", "groups": {} }, + "hash": { "input": "otherhash", "groups": { "0": "otherhash" } }, "hostname": { "input": "example.com", "groups": {} }, "pathname": { "input": "/foo/bar", "groups": {} }, "protocol": { "input": "https", "groups": {} }, - "search": { "input": "otherquery", "groups": {} } + "search": { "input": "otherquery", "groups": { "0": "otherquery" } } } }, { "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], "inputs": [ "https://example.com/foo/bar" ], - "exactly_empty_components": [ "username", "password", "port" ], - "expected_match": null + "exactly_empty_components": [ "port" ], + "expected_match": { + "hostname": { "input": "example.com", "groups": {} }, + "pathname": { "input": "/foo/bar", "groups": {} }, + "protocol": { "input": "https", "groups": {} } + } }, { "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], "inputs": [ "https://example.com/foo/bar?otherquery#otherhash" ], - "exactly_empty_components": [ "username", "password", "port" ], - "expected_match": null + "exactly_empty_components": [ "port" ], + "expected_match": { + "hash": { "input": "otherhash", "groups": { "0": "otherhash" } }, + "hostname": { "input": "example.com", "groups": {} }, + "pathname": { "input": "/foo/bar", "groups": {} }, + "protocol": { "input": "https", "groups": {} }, + "search": { "input": "otherquery", "groups": { "0": "otherquery" } } + } }, { "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], "inputs": [ "https://example.com/foo/bar?query#hash" ], - "exactly_empty_components": [ "username", "password", "port" ], + "exactly_empty_components": [ "port" ], "expected_match": { - "hash": { "input": "hash", "groups": {} }, + "hash": { "input": "hash", "groups": { "0": "hash" } }, "hostname": { "input": "example.com", "groups": {} }, "pathname": { "input": "/foo/bar", "groups": {} }, "protocol": { "input": "https", "groups": {} }, - "search": { "input": "query", "groups": {} } + "search": { "input": "query", "groups": { "0": "query" } } } }, { @@ -186,21 +210,23 @@ "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], "inputs": [{ "pathname": "/foo/bar", "baseURL": "https://example.com" }], - "exactly_empty_components": [ "username", "password", "port" ], - "expected_match": null + "exactly_empty_components": [ "port" ], + "expected_match": { + "hostname": { "input": "example.com", "groups": {} }, + "pathname": { "input": "/foo/bar", "groups": {} }, + "protocol": { "input": "https", "groups": {} } + } }, { "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], "inputs": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], - "exactly_empty_components": [ "username", "password", "port" ], + "exactly_empty_components": [ "port" ], "expected_match": { - "hash": { "input": "hash", "groups": {} }, "hostname": { "input": "example.com", "groups": {} }, "pathname": { "input": "/foo/bar", "groups": {} }, - "protocol": { "input": "https", "groups": {} }, - "search": { "input": "query", "groups": {} } + "protocol": { "input": "https", "groups": {} } } }, { @@ -1209,8 +1235,7 @@ { "pattern": [{ "pathname": "./foo/bar", "baseURL": "https://example.com" }], "inputs": [{ "pathname": "foo/bar", "baseURL": "https://example.com" }], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "pathname": "/foo/bar" }, @@ -1223,8 +1248,7 @@ { "pattern": [{ "pathname": "", "baseURL": "https://example.com" }], "inputs": [{ "pathname": "/", "baseURL": "https://example.com" }], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "pathname": "/" }, @@ -1237,8 +1261,7 @@ { "pattern": [{ "pathname": "{/bar}", "baseURL": "https://example.com/foo/" }], "inputs": [{ "pathname": "./bar", "baseURL": "https://example.com/foo/" }], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "pathname": "/bar" }, @@ -1247,8 +1270,7 @@ { "pattern": [{ "pathname": "\\/bar", "baseURL": "https://example.com/foo/" }], "inputs": [{ "pathname": "./bar", "baseURL": "https://example.com/foo/" }], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "pathname": "/bar" }, @@ -1257,8 +1279,7 @@ { "pattern": [{ "pathname": "b", "baseURL": "https://example.com/foo/" }], "inputs": [{ "pathname": "./b", "baseURL": "https://example.com/foo/" }], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "pathname": "/foo/b" }, @@ -1276,8 +1297,7 @@ { "pattern": [{ "pathname": "foo/bar", "baseURL": "https://example.com" }], "inputs": [ "https://example.com/foo/bar" ], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "pathname": "/foo/bar" }, @@ -1290,8 +1310,7 @@ { "pattern": [{ "pathname": ":name.html", "baseURL": "https://example.com" }], "inputs": [ "https://example.com/foo.html"] , - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "pathname": "/:name.html" }, @@ -1464,9 +1483,10 @@ "pattern": [ "https://example.com:8080/foo?bar#baz" ], "inputs": [{ "pathname": "/foo", "search": "bar", "hash": "baz", "baseURL": "https://example.com:8080" }], - "exactly_empty_components": [ "username", "password" ], "expected_obj": { "protocol": "https", + "username": "*", + "password": "*", "hostname": "example.com", "port": "8080", "pathname": "/foo", @@ -1486,7 +1506,6 @@ "pattern": [ "/foo?bar#baz", "https://example.com:8080" ], "inputs": [{ "pathname": "/foo", "search": "bar", "hash": "baz", "baseURL": "https://example.com:8080" }], - "exactly_empty_components": [ "username", "password" ], "expected_obj": { "pathname": "/foo", "search": "bar", @@ -1512,8 +1531,7 @@ { "pattern": [ "http{s}?://{*.}?example.com/:product/:endpoint" ], "inputs": [ "https://sub.example.com/foo/bar" ], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "http{s}?", "hostname": "{*.}?example.com", @@ -1529,8 +1547,7 @@ { "pattern": [ "https://example.com?foo" ], "inputs": [ "https://example.com/?foo" ], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1547,8 +1564,7 @@ { "pattern": [ "https://example.com#foo" ], "inputs": [ "https://example.com/#foo" ], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port", "search" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1565,7 +1581,6 @@ { "pattern": [ "https://example.com:8080?foo" ], "inputs": [ "https://example.com:8080/?foo" ], - "exactly_empty_components": [ "username", "password", "hash" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1584,7 +1599,7 @@ { "pattern": [ "https://example.com:8080#foo" ], "inputs": [ "https://example.com:8080/#foo" ], - "exactly_empty_components": [ "username", "password", "search" ], + "exactly_empty_components": [ "search" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1603,7 +1618,7 @@ { "pattern": [ "https://example.com/?foo" ], "inputs": [ "https://example.com/?foo" ], - "exactly_empty_components": [ "username", "password", "port", "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1620,7 +1635,7 @@ { "pattern": [ "https://example.com/#foo" ], "inputs": [ "https://example.com/#foo" ], - "exactly_empty_components": [ "username", "password", "port", "search" ], + "exactly_empty_components": [ "port", "search" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1637,8 +1652,7 @@ { "pattern": [ "https://example.com/*?foo" ], "inputs": [ "https://example.com/?foo" ], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1649,7 +1663,7 @@ { "pattern": [ "https://example.com/*\\?foo" ], "inputs": [ "https://example.com/?foo" ], - "exactly_empty_components": [ "username", "password", "port", "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1666,8 +1680,7 @@ { "pattern": [ "https://example.com/:name?foo" ], "inputs": [ "https://example.com/bar?foo" ], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1678,7 +1691,7 @@ { "pattern": [ "https://example.com/:name\\?foo" ], "inputs": [ "https://example.com/bar?foo" ], - "exactly_empty_components": [ "username", "password", "port", "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1695,7 +1708,7 @@ { "pattern": [ "https://example.com/(bar)?foo" ], "inputs": [ "https://example.com/bar?foo" ], - "exactly_empty_components": [ "username", "password", "port", "search", "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1706,7 +1719,7 @@ { "pattern": [ "https://example.com/(bar)\\?foo" ], "inputs": [ "https://example.com/bar?foo" ], - "exactly_empty_components": [ "username", "password", "port", "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1723,7 +1736,7 @@ { "pattern": [ "https://example.com/{bar}?foo" ], "inputs": [ "https://example.com/bar?foo" ], - "exactly_empty_components": [ "username", "password", "port", "search", "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1734,7 +1747,7 @@ { "pattern": [ "https://example.com/{bar}\\?foo" ], "inputs": [ "https://example.com/bar?foo" ], - "exactly_empty_components": [ "username", "password", "port", "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1751,8 +1764,7 @@ { "pattern": [ "https://example.com/" ], "inputs": [ "https://example.com:8080/" ], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1769,8 +1781,7 @@ { "pattern": [ "data\\:foobar" ], "inputs": [ "data:foobar" ], - "exactly_empty_components": [ "username", "password", "hostname", "port", - "search", "hash" ], + "exactly_empty_components": [ "hostname", "port" ], "expected_obj": { "protocol": "data", "pathname": "foobar" @@ -1783,8 +1794,7 @@ { "pattern": [ "https://{sub.}?example.com/foo" ], "inputs": [ "https://example.com/foo" ], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "{sub.}?example.com", @@ -1809,8 +1819,7 @@ { "pattern": [ "https://(sub.)?example.com/foo" ], "inputs": [ "https://example.com/foo" ], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "(sub.)?example.com", @@ -1826,12 +1835,11 @@ { "pattern": [ "https://(sub.)?example(.com/)foo" ], "inputs": [ "https://example.com/foo" ], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "(sub.)?example(.com/)foo", - "pathname": "/" + "pathname": "*" }, "expected_match": null }, @@ -1848,8 +1856,7 @@ { "pattern": [ "https://(sub(?:.))?example.com/foo" ], "inputs": [ "https://example.com/foo" ], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "(sub(?:.))?example.com", @@ -1865,8 +1872,7 @@ { "pattern": [ "file:///foo/bar" ], "inputs": [ "file:///foo/bar" ], - "exactly_empty_components": [ "username", "password", "hostname", "port", - "search", "hash" ], + "exactly_empty_components": [ "hostname", "port" ], "expected_obj": { "protocol": "file", "pathname": "/foo/bar" @@ -1879,8 +1885,7 @@ { "pattern": [ "data:" ], "inputs": [ "data:" ], - "exactly_empty_components": [ "username", "password", "hostname", "port", - "pathname", "search", "hash" ], + "exactly_empty_components": [ "hostname", "port", "pathname" ], "expected_obj": { "protocol": "data" }, @@ -1891,8 +1896,7 @@ { "pattern": [ "foo://bar" ], "inputs": [ "foo://bad_url_browser_interop" ], - "exactly_empty_components": [ "username", "password", "port", "pathname", - "search", "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "foo", "hostname": "bar" @@ -1909,7 +1913,7 @@ "search": "?bar", "hash": "#baz", "baseURL": "http://example.com/foo" }], - "exactly_empty_components": [ "username", "password", "port" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1917,13 +1921,7 @@ "search": "bar", "hash": "baz" }, - "expected_match": { - "protocol": { "input": "https", "groups": {} }, - "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/foo", "groups": {} }, - "search": { "input": "bar", "groups": {} }, - "hash": { "input": "baz", "groups": {} } - } + "expected_match": null }, { "pattern": [{ "protocol": "http{s}?:", @@ -1946,7 +1944,7 @@ { "pattern": [ "?bar#baz", "https://example.com/foo" ], "inputs": [ "?bar#baz", "https://example.com/foo" ], - "exactly_empty_components": [ "username", "password", "port" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1965,12 +1963,13 @@ { "pattern": [ "?bar", "https://example.com/foo#baz" ], "inputs": [ "?bar", "https://example.com/foo#snafu" ], - "exactly_empty_components": [ "username", "password", "port", "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "example.com", "pathname": "/foo", - "search": "bar" + "search": "bar", + "hash": "*" }, "expected_match": { "protocol": { "input": "https", "groups": {} }, @@ -1982,7 +1981,7 @@ { "pattern": [ "#baz", "https://example.com/foo?bar" ], "inputs": [ "#baz", "https://example.com/foo?bar" ], - "exactly_empty_components": [ "username", "password", "port" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -2001,7 +2000,7 @@ { "pattern": [ "#baz", "https://example.com/foo" ], "inputs": [ "#baz", "https://example.com/foo" ], - "exactly_empty_components": [ "username", "password", "port", "search" ], + "exactly_empty_components": [ "port", "search" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -2028,99 +2027,98 @@ { "pattern": [ "https://foo\\:bar@example.com" ], "inputs": [ "https://foo:bar@example.com" ], - "exactly_empty_components": [ "port", "search", "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "username": "foo", "password": "bar", "hostname": "example.com", - "pathname": "/" + "pathname": "*" }, "expected_match": { "protocol": { "input": "https", "groups": {} }, "username": { "input": "foo", "groups": {} }, "password": { "input": "bar", "groups": {} }, "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": {} } + "pathname": { "input": "/", "groups": { "0": "/" } } } }, { "pattern": [ "https://foo@example.com" ], "inputs": [ "https://foo@example.com" ], - "exactly_empty_components": [ "password", "port", "search", "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "username": "foo", "hostname": "example.com", - "pathname": "/" + "pathname": "*" }, "expected_match": { "protocol": { "input": "https", "groups": {} }, "username": { "input": "foo", "groups": {} }, "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": {} } + "pathname": { "input": "/", "groups": { "0": "/" } } } }, { "pattern": [ "https://\\:bar@example.com" ], "inputs": [ "https://:bar@example.com" ], - "exactly_empty_components": [ "username", "port", "search", "hash" ], + "exactly_empty_components": [ "username", "port" ], "expected_obj": { "protocol": "https", "password": "bar", "hostname": "example.com", - "pathname": "/" + "pathname": "*" }, "expected_match": { "protocol": { "input": "https", "groups": {} }, "password": { "input": "bar", "groups": {} }, "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": {} } + "pathname": { "input": "/", "groups": { "0": "/" } } } }, { "pattern": [ "https://:user::pass@example.com" ], "inputs": [ "https://foo:bar@example.com" ], - "exactly_empty_components": [ "port", "search", "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "username": ":user", "password": ":pass", "hostname": "example.com", - "pathname": "/" + "pathname": "*" }, "expected_match": { "protocol": { "input": "https", "groups": {} }, "username": { "input": "foo", "groups": { "user": "foo" } }, "password": { "input": "bar", "groups": { "pass": "bar" } }, "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": {} } + "pathname": { "input": "/", "groups": { "0": "/" } } } }, { "pattern": [ "https\\:foo\\:bar@example.com" ], "inputs": [ "https:foo:bar@example.com" ], - "exactly_empty_components": [ "port", "search", "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "username": "foo", "password": "bar", "hostname": "example.com", - "pathname": "/" + "pathname": "*" }, "expected_match": { "protocol": { "input": "https", "groups": {} }, "username": { "input": "foo", "groups": {} }, "password": { "input": "bar", "groups": {} }, "hostname": { "input": "example.com", "groups": {} }, - "pathname": { "input": "/", "groups": {} } + "pathname": { "input": "/", "groups": { "0": "/" } } } }, { "pattern": [ "data\\:foo\\:bar@example.com" ], "inputs": [ "data:foo:bar@example.com" ], - "exactly_empty_components": [ "username", "password", "hostname", "port", - "search", "hash" ], + "exactly_empty_components": [ "hostname", "port" ], "expected_obj": { "protocol": "data", "pathname": "foo\\:bar@example.com" @@ -2133,24 +2131,24 @@ { "pattern": [ "https://foo{\\:}bar@example.com" ], "inputs": [ "https://foo:bar@example.com" ], - "exactly_empty_components": [ "password", "port", "search", "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "username": "foo%3Abar", - "hostname": "example.com", - "pathname": "/" + "hostname": "example.com" }, "expected_match": null }, { "pattern": [ "data{\\:}channel.html", "https://example.com" ], "inputs": [ "https://example.com/data:channel.html" ], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "https", "hostname": "example.com", - "pathname": "/data\\:channel.html" + "pathname": "/data\\:channel.html", + "search": "*", + "hash": "*" }, "expected_match": { "protocol": { "input": "https", "groups": {} }, @@ -2161,8 +2159,7 @@ { "pattern": [ "http://[\\:\\:1]/" ], "inputs": [ "http://[::1]/" ], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "http", "hostname": "[\\:\\:1]", @@ -2177,7 +2174,6 @@ { "pattern": [ "http://[\\:\\:1]:8080/" ], "inputs": [ "http://[::1]:8080/" ], - "exactly_empty_components": [ "username", "password", "search", "hash" ], "expected_obj": { "protocol": "http", "hostname": "[\\:\\:1]", @@ -2194,8 +2190,7 @@ { "pattern": [ "http://[\\:\\:a]/" ], "inputs": [ "http://[::a]/" ], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "http", "hostname": "[\\:\\:a]", @@ -2210,8 +2205,7 @@ { "pattern": [ "http://[:address]/" ], "inputs": [ "http://[::1]/" ], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "http", "hostname": "[:address]", @@ -2226,8 +2220,7 @@ { "pattern": [ "http://[\\:\\:AB\\::num]/" ], "inputs": [ "http://[::ab:1]/" ], - "exactly_empty_components": [ "username", "password", "port", "search", - "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "protocol": "http", "hostname": "[\\:\\:ab\\::num]", @@ -2299,8 +2292,7 @@ { "pattern": [ "data\\:text/javascript,let x = 100/:tens?5;" ], "inputs": [ "data:text/javascript,let x = 100/5;" ], - "exactly_empty_components": [ "username", "password", "hostname", "port", - "search", "hash" ], + "exactly_empty_components": [ "hostname", "port" ], "expected_obj": { "protocol": "data", "pathname": "text/javascript,let x = 100/:tens?5;" @@ -2747,7 +2739,6 @@ { "ignoreCase": true }], "inputs": [{ "pathname": "/FOO", "search": "BAR", "hash": "BAZ", "baseURL": "https://example.com:8080" }], - "exactly_empty_components": [ "username", "password" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -2770,7 +2761,6 @@ { "ignoreCase": true }], "inputs": [{ "pathname": "/FOO", "search": "BAR", "hash": "BAZ", "baseURL": "https://example.com:8080" }], - "exactly_empty_components": [ "username", "password" ], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -2798,7 +2788,7 @@ { "pattern": [{ "search": "foo", "baseURL": "https://example.com/a/+/b" }], "inputs": [{ "search": "foo", "baseURL": "https://example.com/a/+/b" }], - "exactly_empty_components": [ "username", "password", "port", "hash" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "pathname": "/a/\\+/b" }, @@ -2812,7 +2802,7 @@ { "pattern": [{ "hash": "foo", "baseURL": "https://example.com/?q=*&v=?&hmm={}&umm=()" }], "inputs": [{ "hash": "foo", "baseURL": "https://example.com/?q=*&v=?&hmm={}&umm=()" }], - "exactly_empty_components": [ "username", "password", "port" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "search": "q=\\*&v=\\?&hmm=\\{\\}&umm=\\(\\)" }, @@ -2827,7 +2817,7 @@ { "pattern": [ "#foo", "https://example.com/?q=*&v=?&hmm={}&umm=()" ], "inputs": [ "https://example.com/?q=*&v=?&hmm={}&umm=()#foo" ], - "exactly_empty_components": [ "username", "password", "port" ], + "exactly_empty_components": [ "port" ], "expected_obj": { "search": "q=\\*&v=\\?&hmm=\\{\\}&umm=\\(\\)", "hash": "foo" diff --git a/test/urlpatterntests.js b/test/urlpatterntests.js index 2fd7376..1e5463e 100644 --- a/test/urlpatterntests.js +++ b/test/urlpatterntests.js @@ -58,22 +58,41 @@ function runTests(data) { baseURL = new URL(entry.pattern[1]); } + const EARLIER_COMPONENTS = { + protocol: [], + hostname: ["protocol"], + port: ["protocol", "hostname"], + username: [], + password: [], + pathname: ["protocol", "hostname", "port"], + search: ["protocol", "hostname", "port", "pathname"], + hash: ["protocol", "hostname", "port", "pathname", "search"], + }; + // We automatically populate the expected pattern string using // the following options in priority order: // // 1. If the original input explicitly provided a pattern, then // echo that back as the expected value. - // 2. If the baseURL exists and provides a component value then + // 2. If an "earlier" component is specified, then a wildcard + // will be used rather than inheriting from the base URL. + // 3. If the baseURL exists and provides a component value then // use that for the expected pattern. - // 3. Otherwise fall back on the default pattern of `*` for an + // 4. Otherwise fall back on the default pattern of `*` for an // empty component pattern. + // + // Note that username and password are never inherited, and will only + // need to match if explicitly specified. if (entry.exactly_empty_components && entry.exactly_empty_components.includes(component)) { expected = ''; } else if (typeof entry.pattern[0] === 'object' && entry.pattern[0][component]) { expected = entry.pattern[0][component]; - } else if (baseURL) { + } else if (typeof entry.pattern[0] === 'object' && + EARLIER_COMPONENTS[component].some(c => c in entry.pattern[0])) { + expected = '*'; + } else if (baseURL && component !== 'username' && component !== 'password') { let base_value = baseURL[component]; // Unfortunately some URL() getters include separator chars; e.g. // the trailing `:` for the protocol. Strip those off if necessary.