Browse Source

url: add urlSearchParams.sort()

PR-URL: https://github.com/nodejs/node/pull/11098
Fixes: https://github.com/nodejs/node/issues/10760
Ref: https://github.com/whatwg/url/issues/26
Ref: https://github.com/whatwg/url/pull/199
Ref: https://github.com/w3c/web-platform-tests/pull/4531
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
v6
Timothy Gu 8 years ago
parent
commit
02d1e32fe3
  1. 16
      doc/api/url.md
  2. 74
      lib/internal/url.js
  3. 84
      test/parallel/test-whatwg-url-searchparams-sort.js

16
doc/api/url.md

@ -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}
@ -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
lib/internal/url.js

@ -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)) {
@ -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`.

84
test/parallel/test-whatwg-url-searchparams-sort.js

@ -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);
});
Loading…
Cancel
Save