From 889b2ab992be410d9d655f912c0b658782e0f630 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Tue, 30 May 2023 02:24:52 +0700 Subject: [PATCH] add `value` argument of `URLSearchParams.prototype.{ has, delete }` https://github.com/whatwg/url/pull/735 --- CHANGELOG.md | 2 + README.md | 21 ++++++---- packages/core-js-compat/src/data.mjs | 6 +++ .../src/modules-by-versions.mjs | 4 ++ .../web.url-search-params.constructor.js | 30 ++++++++----- .../modules/web.url-search-params.delete.js | 42 +++++++++++++++++++ .../modules/web.url-search-params.has.js | 27 ++++++++++++ packages/core-js/proposals/url.js | 6 +-- packages/core-js/web/index.js | 2 + packages/core-js/web/url-search-params.js | 2 + packages/core-js/web/url.js | 3 +- tests/compat/tests.js | 9 ++++ tests/unit-global/web.url-search-params.js | 21 ++++++++++ tests/unit-pure/web.url-search-params.js | 21 ++++++++++ 14 files changed, 170 insertions(+), 26 deletions(-) create mode 100644 packages/core-js/modules/web.url-search-params.delete.js create mode 100644 packages/core-js/modules/web.url-search-params.has.js diff --git a/CHANGELOG.md b/CHANGELOG.md index fbf7a9ba400f..f704022dfd51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,11 @@ - Changed `Symbol.iterator` fallback from callable check to `undefined` / `null` check, May 2023 TC39 meeting, [proposal-iterator-helpers/272](https://github.com/tc39/proposal-iterator-helpers/pull/272) - Removed `IsCallable` check on `NextMethod`, deferring errors to `Call` site, May 2023 TC39 meeting, [proposal-iterator-helpers/274](https://github.com/tc39/proposal-iterator-helpers/pull/274) - Fixed some cases of increasing buffer size in `ArrayBuffer.prototype.{ transfer, transferToFixedLength }` polyfills +- Added `value` argument of `URLSearchParams.prototype.{ has, delete }`, [url/735](https://github.com/whatwg/url/pull/735) - Compat data improvements: - `Set.prototype.difference` that was missed in Bun because of [a bug](https://github.com/oven-sh/bun/issues/2309) added in 0.6.0 - `Array.prototype.{ group, groupToMap }` are disabled from Bun 0.6.2 because of [web compat issues](https://github.com/tc39/proposal-array-grouping/issues/44) + - `value` argument of `URLSearchParams.prototype.{ has, delete }` marked as [supported](https://github.com/nodejs/node/pull/47885) from NodeJS 20.2.0 - Added Deno 1.34 compat data mapping - Added Electron 26 compat data mapping - Added Quest Browser 27 compat data mapping diff --git a/README.md b/README.md index e441b21211c6..bc5e8f6cc31e 100644 --- a/README.md +++ b/README.md @@ -3213,7 +3213,7 @@ queueMicrotask(() => console.log('called as microtask')); ``` #### `URL` and `URLSearchParams`[⬆](#index) -[`URL` standard](https://url.spec.whatwg.org/) implementation. Modules [`web.url`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.url.js), [`web.url.can-parse`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.url.can-parse.js), [`web.url.to-json`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.url.to-json.js), [`web.url-search-params`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.url-search-params.js), [`web.url-search-params.size`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.url-search-params.size.js). +[`URL` standard](https://url.spec.whatwg.org/) implementation. Modules [`web.url`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.url.js), [`web.url.can-parse`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.url.can-parse.js), [`web.url.to-json`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.url.to-json.js), [`web.url-search-params`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.url-search-params.js), [`web.url-search-params.delete`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.url-search-params.delete.js), [`web.url-search-params.has`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.url-search-params.has.js), [`web.url-search-params.size`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.url-search-params.size.js). ```js class URL { constructor(url: string, base?: string); @@ -3237,10 +3237,10 @@ class URL { class URLSearchParams { constructor(params?: string | Iterable<[key, value]> | Object); append(name: string, value: string): void; - delete(name: string): void; + delete(name: string, value?: string): void; get(name: string): string | void; getAll(name: string): Array; - has(name: string): boolean; + has(name: string, value?: string): boolean; set(name: string, value: string): void; sort(): void; toString(): string; @@ -3260,7 +3260,7 @@ core-js(-pure)/stable|actual|full/url/can-parse core-js/stable|actual|full/url/to-json core-js(-pure)/stable|actual|full/url-search-params ``` -[*Examples*](https://tinyurl.com/2ovt23zn): +[*Examples*](https://tinyurl.com/2j35uor6): ```js URL.canParse('https://login:password@example.com:8080/?a=1&b=2&a=3&c=4#fragment'); // => true URL.canParse('https'); // => false @@ -3296,16 +3296,21 @@ const params = new URLSearchParams('?a=1&b=2&a=3'); params.append('c', 4); params.append('a', 2); +params.delete('a', 1); params.sort(); -console.log(params.size); // => 5 +console.log(params.size); // => 4 for (let [key, value] of params) { - console.log(key); // => 'a', 'a', 'a', 'b', 'c' - console.log(value); // => '1', '3', '2', '2', '4' + console.log(key); // => a', 'a', 'b', 'c' + console.log(value); // => '3', '2', '2', '4' } -console.log(params.toString()); // => 'a=1&a=3&a=2&b=2&c=4' +console.log(params.has('a')); // => true +console.log(params.has('a', 3)); // => true +console.log(params.has('a', 4)); // => false + +console.log(params.toString()); // => 'a=3&a=2&b=2&c=4' ``` ##### Caveats when using `URL` and `URLSearchParams`:[⬆](#index) diff --git a/packages/core-js-compat/src/data.mjs b/packages/core-js-compat/src/data.mjs index 0009f32b953b..8dd43a633d04 100644 --- a/packages/core-js-compat/src/data.mjs +++ b/packages/core-js-compat/src/data.mjs @@ -2487,6 +2487,12 @@ export const data = { node: '10.0', safari: '14.0', }, + 'web.url-search-params.delete': { + node: '20.2.0', + }, + 'web.url-search-params.has': { + node: '20.2.0', + }, 'web.url-search-params.size': { chrome: '113', deno: '1.32', diff --git a/packages/core-js-compat/src/modules-by-versions.mjs b/packages/core-js-compat/src/modules-by-versions.mjs index 1672e28c9382..f88b215833f4 100644 --- a/packages/core-js-compat/src/modules-by-versions.mjs +++ b/packages/core-js-compat/src/modules-by-versions.mjs @@ -202,4 +202,8 @@ export default { '3.30': [ 'web.url.can-parse', ], + 3.31: [ + 'web.url-search-params.delete', + 'web.url-search-params.has', + ], }; diff --git a/packages/core-js/modules/web.url-search-params.constructor.js b/packages/core-js/modules/web.url-search-params.constructor.js index fe05a05b71d4..9f96717a68a4 100644 --- a/packages/core-js/modules/web.url-search-params.constructor.js +++ b/packages/core-js/modules/web.url-search-params.constructor.js @@ -214,23 +214,28 @@ defineBuiltIns(URLSearchParamsPrototype, { // `URLSearchParams.prototype.append` method // https://url.spec.whatwg.org/#dom-urlsearchparams-append append: function append(name, value) { - validateArgumentsLength(arguments.length, 2); var state = getInternalParamsState(this); + validateArgumentsLength(arguments.length, 2); push(state.entries, { key: $toString(name), value: $toString(value) }); if (!DESCRIPTORS) this.length++; state.updateURL(); }, // `URLSearchParams.prototype.delete` method // https://url.spec.whatwg.org/#dom-urlsearchparams-delete - 'delete': function (name) { - validateArgumentsLength(arguments.length, 1); + 'delete': function (name /* , value */) { var state = getInternalParamsState(this); + var length = validateArgumentsLength(arguments.length, 1); var entries = state.entries; var key = $toString(name); + var $value = length < 2 ? undefined : arguments[1]; + var value = $value === undefined ? $value : $toString($value); var index = 0; while (index < entries.length) { - if (entries[index].key === key) splice(entries, index, 1); - else index++; + var entry = entries[index]; + if (entry.key === key && (value === undefined || entry.value === value)) { + splice(entries, index, 1); + if (value !== undefined) break; + } else index++; } if (!DESCRIPTORS) this.length = entries.length; state.updateURL(); @@ -238,8 +243,8 @@ defineBuiltIns(URLSearchParamsPrototype, { // `URLSearchParams.prototype.get` method // https://url.spec.whatwg.org/#dom-urlsearchparams-get get: function get(name) { - validateArgumentsLength(arguments.length, 1); var entries = getInternalParamsState(this).entries; + validateArgumentsLength(arguments.length, 1); var key = $toString(name); var index = 0; for (; index < entries.length; index++) { @@ -250,8 +255,8 @@ defineBuiltIns(URLSearchParamsPrototype, { // `URLSearchParams.prototype.getAll` method // https://url.spec.whatwg.org/#dom-urlsearchparams-getall getAll: function getAll(name) { - validateArgumentsLength(arguments.length, 1); var entries = getInternalParamsState(this).entries; + validateArgumentsLength(arguments.length, 1); var key = $toString(name); var result = []; var index = 0; @@ -262,21 +267,24 @@ defineBuiltIns(URLSearchParamsPrototype, { }, // `URLSearchParams.prototype.has` method // https://url.spec.whatwg.org/#dom-urlsearchparams-has - has: function has(name) { - validateArgumentsLength(arguments.length, 1); + has: function has(name /* , value */) { var entries = getInternalParamsState(this).entries; + var length = validateArgumentsLength(arguments.length, 1); var key = $toString(name); + var $value = length < 2 ? undefined : arguments[1]; + var value = $value === undefined ? $value : $toString($value); var index = 0; while (index < entries.length) { - if (entries[index++].key === key) return true; + var entry = entries[index++]; + if (entry.key === key && (value === undefined || entry.value === value)) return true; } return false; }, // `URLSearchParams.prototype.set` method // https://url.spec.whatwg.org/#dom-urlsearchparams-set set: function set(name, value) { - validateArgumentsLength(arguments.length, 1); var state = getInternalParamsState(this); + validateArgumentsLength(arguments.length, 1); var entries = state.entries; var found = false; var key = $toString(name); diff --git a/packages/core-js/modules/web.url-search-params.delete.js b/packages/core-js/modules/web.url-search-params.delete.js new file mode 100644 index 000000000000..ede79f9d7620 --- /dev/null +++ b/packages/core-js/modules/web.url-search-params.delete.js @@ -0,0 +1,42 @@ +'use strict'; +var getBuiltIn = require('../internals/get-built-in'); +var defineBuiltIn = require('../internals/define-built-in'); +var uncurryThis = require('../internals/function-uncurry-this'); +var toString = require('../internals/to-string'); +var validateArgumentsLength = require('../internals/validate-arguments-length'); + +var URLSearchParams = getBuiltIn('URLSearchParams'); +var URLSearchParamsPrototype = URLSearchParams.prototype; +var append = uncurryThis(URLSearchParamsPrototype.append); +var $delete = uncurryThis(URLSearchParamsPrototype['delete']); +var forEach = uncurryThis(URLSearchParamsPrototype.forEach); +var has = uncurryThis(URLSearchParamsPrototype.has); +var push = uncurryThis([].push); +var params = new URLSearchParams('a=1&a=2'); + +params['delete']('a', 1); + +if (params + '' !== 'a=2') { + defineBuiltIn(URLSearchParamsPrototype, 'delete', function (name /* , value */) { + has(this, ''); // validate `this` + if (validateArgumentsLength(arguments.length, 1) < 2) return $delete(this, name); + var key = toString(name); + var $value = arguments[1]; + if ($value === undefined) return $delete(this, key); + var value = toString($value); + var entries = []; + forEach(this, function (v, k) { + push(entries, { key: k, value: v }); + }); + var index = 0; + var length = entries.length; + while (index < length) { + $delete(this, entries[index++].key); + } + index = 0; + while (index < length) { + var entry = entries[index++]; + if (!(entry.key === key && entry.value === value)) append(this, entry.key, entry.value); + } + }, { enumerable: true, unsafe: true }); +} diff --git a/packages/core-js/modules/web.url-search-params.has.js b/packages/core-js/modules/web.url-search-params.has.js new file mode 100644 index 000000000000..8d02e7b613e0 --- /dev/null +++ b/packages/core-js/modules/web.url-search-params.has.js @@ -0,0 +1,27 @@ +'use strict'; +var getBuiltIn = require('../internals/get-built-in'); +var defineBuiltIn = require('../internals/define-built-in'); +var uncurryThis = require('../internals/function-uncurry-this'); +var toString = require('../internals/to-string'); +var validateArgumentsLength = require('../internals/validate-arguments-length'); + +var URLSearchParams = getBuiltIn('URLSearchParams'); +var URLSearchParamsPrototype = URLSearchParams.prototype; +var getAll = uncurryThis(URLSearchParamsPrototype.getAll); +var $has = uncurryThis(URLSearchParamsPrototype.has); +var params = new URLSearchParams('a=1'); + +if (params.has('a', 2)) { + defineBuiltIn(URLSearchParamsPrototype, 'has', function has(name /* , value */) { + $has(this, ''); // validate `this` + if (validateArgumentsLength(arguments.length, 1) < 2) return $has(this, name); + var values = getAll(this, name); + var $value = arguments[1]; + if ($value === undefined) return values.length !== 0; + var value = toString($value); + var index = 0; + while (index < values.length) { + if (values[index++] === value) return true; + } return false; + }, { enumerable: true, unsafe: true }); +} diff --git a/packages/core-js/proposals/url.js b/packages/core-js/proposals/url.js index 9ec967cd119f..d379445d3f49 100644 --- a/packages/core-js/proposals/url.js +++ b/packages/core-js/proposals/url.js @@ -1,6 +1,2 @@ // https://github.com/jasnell/proposal-url -require('../modules/web.url'); -require('../modules/web.url.can-parse'); -require('../modules/web.url.to-json'); -require('../modules/web.url-search-params'); -require('../modules/web.url-search-params.size'); +require('../web/url'); diff --git a/packages/core-js/web/index.js b/packages/core-js/web/index.js index 7c20cb74ce77..86487714fbe4 100644 --- a/packages/core-js/web/index.js +++ b/packages/core-js/web/index.js @@ -14,6 +14,8 @@ require('../modules/web.url'); require('../modules/web.url.can-parse'); require('../modules/web.url.to-json'); require('../modules/web.url-search-params'); +require('../modules/web.url-search-params.delete'); +require('../modules/web.url-search-params.has'); require('../modules/web.url-search-params.size'); var path = require('../internals/path'); diff --git a/packages/core-js/web/url-search-params.js b/packages/core-js/web/url-search-params.js index 427ccc4a6aa4..601b08df8265 100644 --- a/packages/core-js/web/url-search-params.js +++ b/packages/core-js/web/url-search-params.js @@ -1,4 +1,6 @@ require('../modules/web.url-search-params'); +require('../modules/web.url-search-params.delete'); +require('../modules/web.url-search-params.has'); require('../modules/web.url-search-params.size'); var path = require('../internals/path'); diff --git a/packages/core-js/web/url.js b/packages/core-js/web/url.js index ea29f180b9b9..bba7ff224991 100644 --- a/packages/core-js/web/url.js +++ b/packages/core-js/web/url.js @@ -1,8 +1,7 @@ +require('./url-search-params'); require('../modules/web.url'); require('../modules/web.url.can-parse'); require('../modules/web.url.to-json'); -require('../modules/web.url-search-params'); -require('../modules/web.url-search-params.size'); var path = require('../internals/path'); module.exports = path.URL; diff --git a/tests/compat/tests.js b/tests/compat/tests.js index c224c6827f0f..8ce8f9e511d4 100644 --- a/tests/compat/tests.js +++ b/tests/compat/tests.js @@ -1947,6 +1947,15 @@ GLOBAL.tests = { return URL.prototype.toJSON; }], 'web.url-search-params.constructor': URL_AND_URL_SEARCH_PARAMS_SUPPORT, + 'web.url-search-params.delete': [URL_AND_URL_SEARCH_PARAMS_SUPPORT, function () { + var params = new URLSearchParams('a=1&a=2'); + params['delete']('a', 1); + return params + '' === 'a=2'; + }], + 'web.url-search-params.has': [URL_AND_URL_SEARCH_PARAMS_SUPPORT, function () { + var params = new URLSearchParams('a=1'); + return params.has('a', 1) && !params.has('a', 2); + }], 'web.url-search-params.size': [URL_AND_URL_SEARCH_PARAMS_SUPPORT, function () { return 'size' in URLSearchParams.prototype; }] diff --git a/tests/unit-global/web.url-search-params.js b/tests/unit-global/web.url-search-params.js index f300f6b4f767..00fd9ddd0b61 100644 --- a/tests/unit-global/web.url-search-params.js +++ b/tests/unit-global/web.url-search-params.js @@ -231,6 +231,18 @@ QUnit.test('URLSearchParams#delete', assert => { params.delete('first'); assert.false(params.has('first'), 'search params object has no "first" name'); + params = new URLSearchParams('a=1&a=2&a=null&a=3&b=4'); + params.delete('a', 2); + assert.same(String(params), 'a=1&a=null&a=3&b=4'); + + params = new URLSearchParams('a=1&a=2&a=null&a=3&b=4'); + params.delete('a', null); + assert.same(String(params), 'a=1&a=2&a=3&b=4'); + + params = new URLSearchParams('a=1&a=2&a=null&a=3&b=4'); + params.delete('a', undefined); + assert.same(String(params), 'b=4'); + if (DESCRIPTORS) { let url = new URL('http://example.com/?param1¶m2'); url.searchParams.delete('param1'); @@ -380,6 +392,15 @@ QUnit.test('URLSearchParams#has', assert => { params.delete('first'); assert.false(params.has('first'), 'search params object has no name "first"'); + params = new URLSearchParams('a=1&a=2&a=null&a=3&b=4'); + assert.true(params.has('a', 2)); + assert.true(params.has('a', null)); + assert.false(params.has('a', 4)); + assert.true(params.has('b', 4)); + assert.false(params.has('b', null)); + assert.true(params.has('b', undefined)); + assert.false(params.has('c', undefined)); + assert.throws(() => { return new URLSearchParams('').has(); }, 'throws w/o arguments'); diff --git a/tests/unit-pure/web.url-search-params.js b/tests/unit-pure/web.url-search-params.js index e6ee20b474a2..638667cae4a8 100644 --- a/tests/unit-pure/web.url-search-params.js +++ b/tests/unit-pure/web.url-search-params.js @@ -230,6 +230,18 @@ QUnit.test('URLSearchParams#delete', assert => { params.delete('first'); assert.false(params.has('first'), 'search params object has no "first" name'); + params = new URLSearchParams('a=1&a=2&a=null&a=3&b=4'); + params.delete('a', 2); + assert.same(String(params), 'a=1&a=null&a=3&b=4'); + + params = new URLSearchParams('a=1&a=2&a=null&a=3&b=4'); + params.delete('a', null); + assert.same(String(params), 'a=1&a=2&a=3&b=4'); + + params = new URLSearchParams('a=1&a=2&a=null&a=3&b=4'); + params.delete('a', undefined); + assert.same(String(params), 'b=4'); + if (DESCRIPTORS) { let url = new URL('http://example.com/?param1¶m2'); url.searchParams.delete('param1'); @@ -373,6 +385,15 @@ QUnit.test('URLSearchParams#has', assert => { params.delete('first'); assert.false(params.has('first'), 'search params object has no name "first"'); + params = new URLSearchParams('a=1&a=2&a=null&a=3&b=4'); + assert.true(params.has('a', 2)); + assert.true(params.has('a', null)); + assert.false(params.has('a', 4)); + assert.true(params.has('b', 4)); + assert.false(params.has('b', null)); + assert.true(params.has('b', undefined)); + assert.false(params.has('c', undefined)); + assert.throws(() => { return new URLSearchParams('').has(); }, 'throws w/o arguments');