Skip to content

Commit c25c16c

Browse files
TimothyGuitaloacasas
authored andcommitted
url: add urlSearchParams.sort()
Backport-of: #11098 Fixes: #10760 Ref: whatwg/url#26 Ref: whatwg/url#199 Ref: web-platform-tests/wpt#4531
1 parent 5a2db15 commit c25c16c

File tree

3 files changed

+174
-0
lines changed

3 files changed

+174
-0
lines changed

doc/api/url.md

+16
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,21 @@ Returns an ES6 Iterator over the names of each name-value pair.
661661
Remove any existing name-value pairs whose name is `name` and append a new
662662
name-value pair.
663663

664+
#### urlSearchParams.sort()
665+
666+
Sort all existing name-value pairs in-place by their names. Sorting is done
667+
with a [stable sorting algorithm][], so relative order between name-value pairs
668+
with the same name is preserved.
669+
670+
This method can be used, in particular, to increase cache hits.
671+
672+
```js
673+
const params = new URLSearchParams('query[]=abc&type=search&query[]=123');
674+
params.sort();
675+
console.log(params.toString());
676+
// Prints query%5B%5D=abc&query%5B%5D=123&type=search
677+
```
678+
664679
#### urlSearchParams.toString()
665680

666681
* Returns: {String}
@@ -754,3 +769,4 @@ console.log(myURL.origin);
754769
[`url.format()`]: #url_url_format_urlobject
755770
[Punycode]: https://tools.ietf.org/html/rfc5891#section-4.4
756771
[WHATWG URL]: #url_the_whatwg_url_api
772+
[stable sorting algorithm]: https://en.wikipedia.org/wiki/Sorting_algorithm#Stability

lib/internal/url.js

+74
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,35 @@ class URLSearchParams {
724724
}
725725
}
726726

727+
// for merge sort
728+
function merge(out, start, mid, end, lBuffer, rBuffer) {
729+
const sizeLeft = mid - start;
730+
const sizeRight = end - mid;
731+
var l, r, o;
732+
733+
for (l = 0; l < sizeLeft; l++)
734+
lBuffer[l] = out[start + l];
735+
for (r = 0; r < sizeRight; r++)
736+
rBuffer[r] = out[mid + r];
737+
738+
l = 0;
739+
r = 0;
740+
o = start;
741+
while (l < sizeLeft && r < sizeRight) {
742+
if (lBuffer[l] <= rBuffer[r]) {
743+
out[o++] = lBuffer[l++];
744+
out[o++] = lBuffer[l++];
745+
} else {
746+
out[o++] = rBuffer[r++];
747+
out[o++] = rBuffer[r++];
748+
}
749+
}
750+
while (l < sizeLeft)
751+
out[o++] = lBuffer[l++];
752+
while (r < sizeRight)
753+
out[o++] = rBuffer[r++];
754+
}
755+
727756
defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', {
728757
append(name, value) {
729758
if (!this || !(this instanceof URLSearchParams)) {
@@ -855,6 +884,51 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', {
855884
update(this[context], this);
856885
},
857886

887+
sort() {
888+
const a = this[searchParams];
889+
const len = a.length;
890+
if (len <= 2) {
891+
return;
892+
}
893+
894+
// arbitrary number found through testing
895+
if (len < 100) {
896+
// Simple stable in-place insertion sort
897+
// Derived from v8/src/js/array.js
898+
for (var i = 2; i < len; i += 2) {
899+
var curKey = a[i];
900+
var curVal = a[i + 1];
901+
var j;
902+
for (j = i - 2; j >= 0; j -= 2) {
903+
if (a[j] > curKey) {
904+
a[j + 2] = a[j];
905+
a[j + 3] = a[j + 1];
906+
} else {
907+
break;
908+
}
909+
}
910+
a[j + 2] = curKey;
911+
a[j + 3] = curVal;
912+
}
913+
} else {
914+
// Bottom-up iterative stable merge sort
915+
const lBuffer = new Array(len);
916+
const rBuffer = new Array(len);
917+
for (var step = 2; step < len; step *= 2) {
918+
for (var start = 0; start < len - 2; start += 2 * step) {
919+
var mid = start + step;
920+
var end = mid + step;
921+
end = end < len ? end : len;
922+
if (mid > end)
923+
continue;
924+
merge(a, start, mid, end, lBuffer, rBuffer);
925+
}
926+
}
927+
}
928+
929+
update(this[context], this);
930+
},
931+
858932
// https://heycam.github.io/webidl/#es-iterators
859933
// Define entries here rather than [Symbol.iterator] as the function name
860934
// must be set to `entries`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const { URL, URLSearchParams } = require('url');
5+
const { test, assert_array_equals } = common.WPT;
6+
7+
/* eslint-disable */
8+
/* WPT Refs:
9+
https://github.com/w3c/web-platform-tests/blob/5903e00e77e85f8bcb21c73d1d7819fcd04763bd/url/urlsearchparams-sort.html
10+
License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html
11+
*/
12+
[
13+
{
14+
"input": "z=b&a=b&z=a&a=a",
15+
"output": [["a", "b"], ["a", "a"], ["z", "b"], ["z", "a"]]
16+
},
17+
{
18+
"input": "\uFFFD=x&\uFFFC&\uFFFD=a",
19+
"output": [["\uFFFC", ""], ["\uFFFD", "x"], ["\uFFFD", "a"]]
20+
},
21+
{
22+
"input": "ffi&🌈", // 🌈 > code point, but < code unit because two code units
23+
"output": [["🌈", ""], ["ffi", ""]]
24+
},
25+
{
26+
"input": "é&e\uFFFD&e\u0301",
27+
"output": [["e\u0301", ""], ["e\uFFFD", ""], ["é", ""]]
28+
},
29+
{
30+
"input": "z=z&a=a&z=y&a=b&z=x&a=c&z=w&a=d&z=v&a=e&z=u&a=f&z=t&a=g",
31+
"output": [["a", "a"], ["a", "b"], ["a", "c"], ["a", "d"], ["a", "e"], ["a", "f"], ["a", "g"], ["z", "z"], ["z", "y"], ["z", "x"], ["z", "w"], ["z", "v"], ["z", "u"], ["z", "t"]]
32+
}
33+
].forEach((val) => {
34+
test(() => {
35+
let params = new URLSearchParams(val.input),
36+
i = 0
37+
params.sort()
38+
for(let param of params) {
39+
assert_array_equals(param, val.output[i])
40+
i++
41+
}
42+
}, "Parse and sort: " + val.input)
43+
44+
test(() => {
45+
let url = new URL("?" + val.input, "https://example/")
46+
url.searchParams.sort()
47+
let params = new URLSearchParams(url.search),
48+
i = 0
49+
for(let param of params) {
50+
assert_array_equals(param, val.output[i])
51+
i++
52+
}
53+
}, "URL parse and sort: " + val.input)
54+
})
55+
/* eslint-enable */
56+
57+
// Tests below are not from WPT.
58+
;[
59+
{
60+
'input': 'z=a&=b&c=d',
61+
'output': [['', 'b'], ['c', 'd'], ['z', 'a']]
62+
}
63+
].forEach((val) => {
64+
test(() => {
65+
const params = new URLSearchParams(val.input);
66+
let i = 0;
67+
params.sort();
68+
for (const param of params) {
69+
assert_array_equals(param, val.output[i]);
70+
i++;
71+
}
72+
}, 'Parse and sort: ' + val.input);
73+
74+
test(() => {
75+
const url = new URL(`?${val.input}`, 'https://example/');
76+
url.searchParams.sort();
77+
const params = new URLSearchParams(url.search);
78+
let i = 0;
79+
for (const param of params) {
80+
assert_array_equals(param, val.output[i]);
81+
i++;
82+
}
83+
}, 'URL parse and sort: ' + val.input);
84+
});

0 commit comments

Comments
 (0)