From a6a0e29e78d26ea381425632e2791d3322bd347e 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 | 8 ++++ .../src/modules-by-versions.mjs | 2 + .../web.url-search-params.constructor.js | 30 +++++++++----- .../modules/web.url-search-params.delete.js | 41 +++++++++++++++++++ .../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, 169 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 0b0cbdec3f5d..99f4308f9a0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - The methods renamed to end with `Symbol`, [May 2023 TC39 meeting](https://github.com/tc39/notes/blob/main/meetings/2023-05/may-15.md#symbol-predicates): - `Symbol.isRegistered` -> `Symbol.isRegisteredSymbol` method - `Symbol.isWellKnown` -> `Symbol.isWellKnownSymbol` method +- Added `value` argument of `URLSearchParams.prototype.{ has, delete }`, [url/735](https://github.com/whatwg/url/pull/735) - Fixed some cases of increasing buffer size in `ArrayBuffer.prototype.{ transfer, transferToFixedLength }` polyfills - Fixed awaiting async `AsyncDisposableStack.prototype.adopt` callback, [#1258](https://github.com/zloirock/core-js/issues/1258) - Added TypeScript definitions to `core-js-compat`, [#1235](https://github.com/zloirock/core-js/issues/1235), thanks [@susnux](https://github.com/susnux) @@ -33,6 +34,7 @@ - Methods from the [change `Array` by copy proposal](https://github.com/tc39/proposal-change-array-by-copy) marked as supported from FF115 - [`Array.fromAsync`](https://github.com/tc39/proposal-array-from-async) marked as supported from FF115 - [`URL.canParse`](https://url.spec.whatwg.org/#dom-url-canparse) marked as supported from FF115 + - `value` argument of `URLSearchParams.prototype.{ has, delete }` marked as supported from [NodeJS 20.2.0](https://github.com/nodejs/node/pull/47885) and FF115 - Added Deno 1.34 compat data mapping - Added Electron 26 compat data mapping - Added Samsung Internet 22 compat data mapping diff --git a/README.md b/README.md index c17f8e9d8734..65e616d210f3 100644 --- a/README.md +++ b/README.md @@ -3239,7 +3239,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); @@ -3263,10 +3263,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; @@ -3286,7 +3286,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 @@ -3322,16 +3322,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 d123b1f7ea07..80fe3c008394 100644 --- a/packages/core-js-compat/src/data.mjs +++ b/packages/core-js-compat/src/data.mjs @@ -2509,6 +2509,14 @@ export const data = { node: '10.0', safari: '14.0', }, + 'web.url-search-params.delete': { + firefox: '115', + node: '20.2.0', + }, + 'web.url-search-params.has': { + firefox: '115', + 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 c3b7b76b082d..ba76ac48d54d 100644 --- a/packages/core-js-compat/src/modules-by-versions.mjs +++ b/packages/core-js-compat/src/modules-by-versions.mjs @@ -210,5 +210,7 @@ export default { 'esnext.promise.with-resolvers', 'esnext.symbol.is-registered-symbol', 'esnext.symbol.is-well-known-symbol', + '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..505e4737974e --- /dev/null +++ b/packages/core-js/modules/web.url-search-params.delete.js @@ -0,0 +1,41 @@ +'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 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 */) { + var length = arguments.length; + var $value = length < 2 ? undefined : arguments[1]; + if (length && $value === undefined) return $delete(this, name); + var entries = []; + forEach(this, function (v, k) { // also validates `this` + push(entries, { key: k, value: v }); + }); + validateArgumentsLength(length, 1); + var key = toString(name); + var value = toString($value); + var index = 0; + var entriesLength = entries.length; + while (index < entriesLength) { + $delete(this, entries[index++].key); + } + index = 0; + while (index < entriesLength) { + 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..050ed18a5017 --- /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 */) { + var length = arguments.length; + var $value = length < 2 ? undefined : arguments[1]; + if (length && $value === undefined) return $has(this, name); + var values = getAll(this, name); // also validates `this` + validateArgumentsLength(length, 1); + 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 894431759065..e095c6a9423d 100644 --- a/tests/compat/tests.js +++ b/tests/compat/tests.js @@ -1956,6 +1956,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');