Skip to content

Commit b4052e6

Browse files
TimothyGuevanlucas
authored andcommitted
url: extend URLSearchParams constructor
PR-URL: #12507 Fixes: #10635 Reviewed-By: James M Snell <[email protected]>
1 parent 5ccafa2 commit b4052e6

File tree

2 files changed

+96
-41
lines changed

2 files changed

+96
-41
lines changed

lib/internal/url.js

+46-4
Original file line numberDiff line numberDiff line change
@@ -614,11 +614,53 @@ function defineIDLClass(proto, classStr, obj) {
614614
}
615615

616616
class URLSearchParams {
617-
constructor(init = '') {
618-
if (init instanceof URLSearchParams) {
619-
const childParams = init[searchParams];
620-
this[searchParams] = childParams.slice();
617+
// URL Standard says the default value is '', but as undefined and '' have
618+
// the same result, undefined is used to prevent unnecessary parsing.
619+
// Default parameter is necessary to keep URLSearchParams.length === 0 in
620+
// accordance with Web IDL spec.
621+
constructor(init = undefined) {
622+
if (init === null || init === undefined) {
623+
this[searchParams] = [];
624+
} else if (typeof init === 'object') {
625+
const method = init[Symbol.iterator];
626+
if (method === this[Symbol.iterator]) {
627+
// While the spec does not have this branch, we can use it as a
628+
// shortcut to avoid having to go through the costly generic iterator.
629+
const childParams = init[searchParams];
630+
this[searchParams] = childParams.slice();
631+
} else if (method !== null && method !== undefined) {
632+
if (typeof method !== 'function') {
633+
throw new TypeError('Query pairs must be iterable');
634+
}
635+
636+
// sequence<sequence<USVString>>
637+
// Note: per spec we have to first exhaust the lists then process them
638+
const pairs = [];
639+
for (const pair of init) {
640+
if (typeof pair !== 'object' ||
641+
typeof pair[Symbol.iterator] !== 'function') {
642+
throw new TypeError('Each query pair must be iterable');
643+
}
644+
pairs.push(Array.from(pair));
645+
}
646+
647+
this[searchParams] = [];
648+
for (const pair of pairs) {
649+
if (pair.length !== 2) {
650+
throw new TypeError('Each query pair must be a name/value tuple');
651+
}
652+
this[searchParams].push(String(pair[0]), String(pair[1]));
653+
}
654+
} else {
655+
// record<USVString, USVString>
656+
this[searchParams] = [];
657+
for (const key of Object.keys(init)) {
658+
const value = String(init[key]);
659+
this[searchParams].push(key, value);
660+
}
661+
}
621662
} else {
663+
// USVString
622664
init = String(init);
623665
if (init[0] === '?') init = init.slice(1);
624666
initSearchParams(this, init);

test/parallel/test-whatwg-url-searchparams-constructor.js

+50-37
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ const common = require('../common');
44
const assert = require('assert');
55
const URLSearchParams = require('url').URLSearchParams;
66
const {
7-
test, assert_equals, assert_true, assert_false
7+
test, assert_equals, assert_true,
8+
assert_false, assert_throws, assert_array_equals
89
} = common.WPT;
910

1011
/* eslint-disable */
@@ -40,10 +41,10 @@ test(() => {
4041
assert_equals(params.__proto__, URLSearchParams.prototype, 'expected URLSearchParams.prototype as prototype.');
4142
}, "URLSearchParams constructor, empty string as argument")
4243

43-
// test(() => {
44-
// params = new URLSearchParams({});
45-
// assert_equals(params + '', "");
46-
// }, 'URLSearchParams constructor, {} as argument');
44+
test(() => {
45+
params = new URLSearchParams({});
46+
assert_equals(params + '', "");
47+
}, 'URLSearchParams constructor, {} as argument');
4748

4849
test(function() {
4950
var params = new URLSearchParams('a=b');
@@ -142,39 +143,39 @@ test(function() {
142143
assert_equals(params.get('a\uD83D\uDCA9b'), 'c');
143144
}, 'Parse %f0%9f%92%a9'); // Unicode Character 'PILE OF POO' (U+1F4A9)
144145

145-
// test(function() {
146-
// var params = new URLSearchParams([]);
147-
// assert_true(params != null, 'constructor returned non-null value.');
148-
// params = new URLSearchParams([['a', 'b'], ['c', 'd']]);
149-
// assert_equals(params.get("a"), "b");
150-
// assert_equals(params.get("c"), "d");
151-
// assert_throws(new TypeError(), function() { new URLSearchParams([[1]]); });
152-
// assert_throws(new TypeError(), function() { new URLSearchParams([[1,2,3]]); });
153-
// }, "Constructor with sequence of sequences of strings");
154-
155-
// [
146+
test(function() {
147+
var params = new URLSearchParams([]);
148+
assert_true(params != null, 'constructor returned non-null value.');
149+
params = new URLSearchParams([['a', 'b'], ['c', 'd']]);
150+
assert_equals(params.get("a"), "b");
151+
assert_equals(params.get("c"), "d");
152+
assert_throws(new TypeError(), function() { new URLSearchParams([[1]]); });
153+
assert_throws(new TypeError(), function() { new URLSearchParams([[1,2,3]]); });
154+
}, "Constructor with sequence of sequences of strings");
155+
156+
[
156157
// { "input": {"+": "%C2"}, "output": [[" ", "\uFFFD"]], "name": "object with +" },
157-
// { "input": {c: "x", a: "?"}, "output": [["c", "x"], ["a", "?"]], "name": "object with two keys" },
158-
// { "input": [["c", "x"], ["a", "?"]], "output": [["c", "x"], ["a", "?"]], "name": "array with two keys" }
159-
// ].forEach((val) => {
160-
// test(() => {
161-
// let params = new URLSearchParams(val.input),
162-
// i = 0
163-
// for (let param of params) {
164-
// assert_array_equals(param, val.output[i])
165-
// i++
166-
// }
167-
// }, "Construct with " + val.name)
168-
// })
158+
{ "input": {c: "x", a: "?"}, "output": [["c", "x"], ["a", "?"]], "name": "object with two keys" },
159+
{ "input": [["c", "x"], ["a", "?"]], "output": [["c", "x"], ["a", "?"]], "name": "array with two keys" }
160+
].forEach((val) => {
161+
test(() => {
162+
let params = new URLSearchParams(val.input),
163+
i = 0
164+
for (let param of params) {
165+
assert_array_equals(param, val.output[i])
166+
i++
167+
}
168+
}, "Construct with " + val.name)
169+
})
169170

170-
// test(() => {
171-
// params = new URLSearchParams()
172-
// params[Symbol.iterator] = function *() {
173-
// yield ["a", "b"]
174-
// }
175-
// let params2 = new URLSearchParams(params)
176-
// assert_equals(params2.get("a"), "b")
177-
// }, "Custom [Symbol.iterator]")
171+
test(() => {
172+
params = new URLSearchParams()
173+
params[Symbol.iterator] = function *() {
174+
yield ["a", "b"]
175+
}
176+
let params2 = new URLSearchParams(params)
177+
assert_equals(params2.get("a"), "b")
178+
}, "Custom [Symbol.iterator]")
178179
/* eslint-enable */
179180

180181
// Tests below are not from WPT.
@@ -192,5 +193,17 @@ test(function() {
192193
params = new URLSearchParams(undefined);
193194
assert.strictEqual(params.toString(), '');
194195
params = new URLSearchParams(null);
195-
assert.strictEqual(params.toString(), 'null=');
196+
assert.strictEqual(params.toString(), '');
197+
assert.throws(() => new URLSearchParams([[1]]),
198+
/^TypeError: Each query pair must be a name\/value tuple$/);
199+
assert.throws(() => new URLSearchParams([[1, 2, 3]]),
200+
/^TypeError: Each query pair must be a name\/value tuple$/);
201+
assert.throws(() => new URLSearchParams({ [Symbol.iterator]: 42 }),
202+
/^TypeError: Query pairs must be iterable$/);
203+
assert.throws(() => new URLSearchParams([{}]),
204+
/^TypeError: Each query pair must be iterable$/);
205+
assert.throws(() => new URLSearchParams(['a']),
206+
/^TypeError: Each query pair must be iterable$/);
207+
assert.throws(() => new URLSearchParams([{ [Symbol.iterator]: 42 }]),
208+
/^TypeError: Each query pair must be iterable$/);
196209
}

0 commit comments

Comments
 (0)