Skip to content

Commit

Permalink
url: add urlSearchParams.sort()
Browse files Browse the repository at this point in the history
  • Loading branch information
TimothyGu committed Feb 11, 2017
1 parent 81ef56b commit 798b358
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 0 deletions.
16 changes: 16 additions & 0 deletions doc/api/url.md
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,21 @@ Returns an ES6 Iterator over the names of each name-value pair.
Remove any existing name-value pairs whose name is `name` and append a new
name-value pair.

#### urlSearchParams.sort()

Sort all existing name-value pairs in-place by their names. Sorting is done
with a [stable sorting algorithm][], so relative order between name-value pairs
with the same name is preserved.

This method can be used, in particular, to increase cache hits.

```js
const params = new URLSearchParams('query[]=abc&type=search&query[]=123');
params.sort();
console.log(params.toString());
// Prints query%5B%5D=abc&query%5B%5D=123&type=search
```

#### urlSearchParams.toString()

* Returns: {String}
Expand Down Expand Up @@ -872,3 +887,4 @@ console.log(myURL.origin);
[`Map`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
[`array.toString()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString
[WHATWG URL]: #url_the_whatwg_url_api
[stable sorting algorithm]: https://en.wikipedia.org/wiki/Sorting_algorithm#Stability
74 changes: 74 additions & 0 deletions lib/internal/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,35 @@ class URLSearchParams {
}
}

// for merge sort
function merge(out, start, mid, end, lBuffer, rBuffer) {
const sizeLeft = mid - start;
const sizeRight = end - mid;
var l, r, o;

for (l = 0; l < sizeLeft; l++)
lBuffer[l] = out[start + l];
for (r = 0; r < sizeRight; r++)
rBuffer[r] = out[mid + r];

l = 0;
r = 0;
o = start;
while (l < sizeLeft && r < sizeRight) {
if (lBuffer[l] <= rBuffer[r]) {
out[o++] = lBuffer[l++];
out[o++] = lBuffer[l++];
} else {
out[o++] = rBuffer[r++];
out[o++] = rBuffer[r++];
}
}
while (l < sizeLeft)
out[o++] = lBuffer[l++];
while (r < sizeRight)
out[o++] = rBuffer[r++];
}

defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', {
append(name, value) {
if (!this || !(this instanceof URLSearchParams)) {
Expand Down Expand Up @@ -897,6 +926,51 @@ defineIDLClass(URLSearchParams.prototype, 'URLSearchParams', {
update(this[context], this);
},

sort() {
const a = this[searchParams];
const len = a.length;
if (len <= 2) {
return;
}

// arbitrary number found through testing
if (len < 100) {
// Simple stable in-place insertion sort
// Derived from v8/src/js/array.js
for (var i = 2; i < len; i += 2) {
var curKey = a[i];
var curVal = a[i + 1];
var j;
for (j = i - 2; j >= 0; j -= 2) {
if (a[j] > curKey) {
a[j + 2] = a[j];
a[j + 3] = a[j + 1];
} else {
break;
}
}
a[j + 2] = curKey;
a[j + 3] = curVal;
}
} else {
// Bottom-up iterative stable merge sort
const lBuffer = new Array(len);
const rBuffer = new Array(len);
for (var step = 2; step < len; step *= 2) {
for (var start = 0; start < len - 2; start += 2 * step) {
var mid = start + step;
var end = mid + step;
end = end < len ? end : len;
if (mid > end)
continue;
merge(a, start, mid, end, lBuffer, rBuffer);
}
}
}

update(this[context], this);
},

// https://heycam.github.io/webidl/#es-iterators
// Define entries here rather than [Symbol.iterator] as the function name
// must be set to `entries`.
Expand Down
84 changes: 84 additions & 0 deletions test/parallel/test-whatwg-url-searchparams-sort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
'use strict';

const common = require('../common');
const {URL, URLSearchParams} = require('url');
const { test, assert_array_equals } = common.WPT;

/* eslint-disable */
/* WPT Refs:
https://github.com/w3c/web-platform-tests/blob/5903e00e77e85f8bcb21c73d1d7819fcd04763bd/url/urlsearchparams-sort.html
License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html
*/
[
{
"input": "z=b&a=b&z=a&a=a",
"output": [["a", "b"], ["a", "a"], ["z", "b"], ["z", "a"]]
},
{
"input": "\uFFFD=x&\uFFFC&\uFFFD=a",
"output": [["\uFFFC", ""], ["\uFFFD", "x"], ["\uFFFD", "a"]]
},
{
"input": "ffi&🌈", // 🌈 > code point, but < code unit because two code units
"output": [["🌈", ""], ["ffi", ""]]
},
{
"input": "é&e\uFFFD&e\u0301",
"output": [["e\u0301", ""], ["e\uFFFD", ""], ["é", ""]]
},
{
"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",
"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"]]
}
].forEach((val) => {
test(() => {
let params = new URLSearchParams(val.input),
i = 0
params.sort()
for(let param of params) {
assert_array_equals(param, val.output[i])
i++
}
}, "Parse and sort: " + val.input)

test(() => {
let url = new URL("?" + val.input, "https://example/")
url.searchParams.sort()
let params = new URLSearchParams(url.search),
i = 0
for(let param of params) {
assert_array_equals(param, val.output[i])
i++
}
}, "URL parse and sort: " + val.input)
})
/* eslint-enable */

// Tests below are not from WPT.
;[
{
'input': 'z=a&=b&c=d',
'output': [['', 'b'], ['c', 'd'], ['z', 'a']]
}
].forEach((val) => {
test(() => {
const params = new URLSearchParams(val.input);
let i = 0;
params.sort();
for (const param of params) {
assert_array_equals(param, val.output[i]);
i++;
}
}, 'Parse and sort: ' + val.input);

test(() => {
const url = new URL(`?${val.input}`, 'https://example/');
url.searchParams.sort();
const params = new URLSearchParams(url.search);
let i = 0;
for (const param of params) {
assert_array_equals(param, val.output[i]);
i++;
}
}, 'URL parse and sort: ' + val.input);
});

0 comments on commit 798b358

Please sign in to comment.