Skip to content

Commit

Permalink
url: add urlSearchParams.sort()
Browse files Browse the repository at this point in the history
  • Loading branch information
TimothyGu authored and italoacasas committed Feb 22, 2017
1 parent 5a2db15 commit c25c16c
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 @@ -661,6 +661,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 @@ -754,3 +769,4 @@ console.log(myURL.origin);
[`url.format()`]: #url_url_format_urlobject
[Punycode]: https://tools.ietf.org/html/rfc5891#section-4.4
[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 @@ -724,6 +724,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 @@ -855,6 +884,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);
});

1 comment on commit c25c16c

@MylesBorins
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This did not land with proper meta data and is breaking tooling

Please sign in to comment.