diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a750ca2ee74..6e03515a43eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ - `String.prototype.toWellFormed` method - Moved to stable ES, [May 2023 TC39 meeting](https://github.com/tc39/proposal-is-usv-string/pull/31) - Added `es.` namespace modules, `/es/` and `/stable/` namespaces entries +- [`Array` grouping proposal](https://github.com/tc39/proposal-array-grouping): + - Because of the [web compat issue](https://github.com/tc39/proposal-array-grouping/issues/44), [moved from prototype to static methods](https://github.com/tc39/proposal-array-grouping/pull/47), [May 2023 TC39 meeting](https://github.com/babel/proposals/issues/88#issuecomment-1553350818). Added: + - `Object.groupBy` + - `Map.groupBy` + - Demoted to stage 2 - [Decorator Metadata proposal](https://github.com/tc39/proposal-decorator-metadata): - Moved to Stage 3, [May 2023 TC39 meeting](https://github.com/babel/proposals/issues/88#issuecomment-1553366034) - Added `Function.prototype[Symbol.metadata]` (`=== null`), [May 2023 TC39 meeting](https://github.com/babel/proposals/issues/88#issuecomment-1550313363) @@ -23,7 +28,7 @@ - Fixed awaiting async `AsyncDisposableStack.prototype.adopt` callback, [#1258](https://github.com/zloirock/core-js/issues/1258) - 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) + - `Array.prototype.{ group, groupToMap }` marked as no longer supported in WebKit runtimes because of the mentioned above web compat issue. For example, it's disabled from Bun 0.6.2 - 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 f40ad4118489..196b0f60ca2b 100644 --- a/README.md +++ b/README.md @@ -2238,28 +2238,6 @@ core-js(-pure)/full/array/from-async ```js await Array.fromAsync((async function * (){ yield * [1, 2, 3] })(), i => i * i); // => [1, 4, 9] ``` -##### [`Array` grouping](https://github.com/tc39/proposal-array-grouping)[⬆](#index) -Modules [`esnext.array.group`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.array.group.js), [`esnext.array.group-to-map`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.array.group-to-map.js). -```js -class Array { - group(callbackfn: (value: any, index: number, target: any) => key, thisArg?: any): { [key]: Array }; - groupToMap(callbackfn: (value: any, index: number, target: any) => key, thisArg?: any): Map>; -} -``` -[*CommonJS entry points:*](#commonjs-api) -``` -core-js/proposals/array-grouping-stage-3-2 -core-js(-pure)/actual|full/array(/virtual)/group -core-js(-pure)/actual|full/array(/virtual)/group-to-map -``` -[*Examples*](https://is.gd/3a0PbH): -```js -[1, 2, 3, 4, 5].group(it => it % 2); // => { 1: [1, 3, 5], 0: [2, 4] } - -const map = [1, 2, 3, 4, 5].groupToMap(it => it % 2); -map.get(1); // => [1, 3, 5] -map.get(0); // => [2, 4] -```` ##### [New `Set` methods](https://github.com/tc39/proposal-set-methods)[⬆](#index) Modules [`esnext.set.difference.v2`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.set.difference.v2.js), [`esnext.set.intersection.v2`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.set.intersection.v2.js), [`esnext.set.is-disjoint-from.v2`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.set.is-disjoint-from.v2.js), [`esnext.set.is-subset-of.v2`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.set.is-subset-of.v2.js), [`esnext.set.is-superset-of.v2`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.set.is-superset-of.v2.js), [`esnext.set.symmetric-difference.v2`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.set.symmetric-difference.v2.js), [`esnext.set.union.v2`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.set.union.v2.js) ```js @@ -2425,6 +2403,31 @@ core-js(-pure)/actual|full/function/metadata ``` core-js(-pure)/stage/2 ``` +##### [`Array` grouping](https://github.com/tc39/proposal-array-grouping)[⬆](#index) +Modules [`esnext.object.group-by`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.object.group-by.js), [`esnext.map.group-by`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.map.group-by.js). +```js +class Object { + groupBy(items: Iterable, callbackfn: (value: any, index: number) => key): { [key]: Array }; +} + +class Map { + groupBy(items: Iterable, callbackfn: (value: any, index: number) => key): Map>; +} +``` +[*CommonJS entry points:*](#commonjs-api) +``` +core-js/proposals/array-grouping-v2 +core-js(-pure)/full/map/group-by +core-js(-pure)/full/object/group-by +``` +[*Examples*](https://is.gd/3a0PbH): +```js +Object.groupBy([1, 2, 3, 4, 5], it => it % 2); // => { 1: [1, 3, 5], 0: [2, 4] } + +const map = Map.groupBy([1, 2, 3, 4, 5], it => it % 2); +map.get(1); // => [1, 3, 5] +map.get(0); // => [2, 4] +```` ##### [`AsyncIterator` helpers](https://github.com/tc39/proposal-async-iterator-helpers)[⬆](#index) Modules [`esnext.async-iterator.constructor`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.async-iterator.constructor.js), [`esnext.async-iterator.drop`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.async-iterator.drop.js), [`esnext.async-iterator.every`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.async-iterator.every.js), [`esnext.async-iterator.filter`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.async-iterator.filter.js), [`esnext.async-iterator.find`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.async-iterator.find.js), [`esnext.async-iterator.flat-map`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.async-iterator.flat-map.js), [`esnext.async-iterator.for-each`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.async-iterator.for-each.js), [`esnext.async-iterator.from`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.async-iterator.from.js), [`esnext.async-iterator.map`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.async-iterator.map.js), [`esnext.async-iterator.reduce`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.async-iterator.reduce.js), [`esnext.async-iterator.some`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.async-iterator.some.js), [`esnext.async-iterator.take`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.async-iterator.take.js), [`esnext.async-iterator.to-array`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.async-iterator.to-array.js), , [`esnext.iterator.to-async`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.iterator.to-async.js) ```js diff --git a/packages/core-js-compat/src/data.mjs b/packages/core-js-compat/src/data.mjs index f53de9676bfe..e24f9f33a3e2 100644 --- a/packages/core-js-compat/src/data.mjs +++ b/packages/core-js-compat/src/data.mjs @@ -1878,7 +1878,7 @@ export const data = { // bun: '0.1.9', // https://github.com/tc39/proposal-array-grouping/issues/44#issuecomment-1306311107 // chrome: '108', - safari: '16.4', + // safari: '16.4', }, // TODO: Remove from `core-js@4` 'esnext.array.group-by': { @@ -1891,7 +1891,7 @@ export const data = { // bun: '0.1.9', // https://github.com/tc39/proposal-array-grouping/issues/44#issuecomment-1306311107 // chrome: '108', - safari: '16.4', + // safari: '16.4', }, 'esnext.array.is-template-object': { }, @@ -2112,6 +2112,8 @@ export const data = { // TODO: Remove from `core-js@4` 'esnext.object.iterate-values': { }, + 'esnext.object.group-by': { + }, // TODO: Remove this module from `core-js@4` since it's split to modules listed below 'esnext.observable': { }, diff --git a/packages/core-js-compat/src/modules-by-versions.mjs b/packages/core-js-compat/src/modules-by-versions.mjs index f13b1820a067..c3b7b76b082d 100644 --- a/packages/core-js-compat/src/modules-by-versions.mjs +++ b/packages/core-js-compat/src/modules-by-versions.mjs @@ -206,6 +206,7 @@ export default { 'es.string.is-well-formed', 'es.string.to-well-formed', 'esnext.function.metadata', + 'esnext.object.group-by', 'esnext.promise.with-resolvers', 'esnext.symbol.is-registered-symbol', 'esnext.symbol.is-well-known-symbol', diff --git a/packages/core-js/full/object/group-by.js b/packages/core-js/full/object/group-by.js new file mode 100644 index 000000000000..fade84f64662 --- /dev/null +++ b/packages/core-js/full/object/group-by.js @@ -0,0 +1,6 @@ +require('../../modules/es.object.create'); +require('../../modules/esnext.object.group-by'); + +var path = require('../../internals/path'); + +module.exports = path.Object.groupBy; diff --git a/packages/core-js/full/object/index.js b/packages/core-js/full/object/index.js index 576c404e6a70..fb7fbc037115 100644 --- a/packages/core-js/full/object/index.js +++ b/packages/core-js/full/object/index.js @@ -1,4 +1,5 @@ var parent = require('../../actual/object'); +require('../../modules/esnext.object.group-by'); // TODO: Remove from `core-js@4` require('../../modules/esnext.object.has-own'); require('../../modules/esnext.object.iterate-entries'); diff --git a/packages/core-js/modules/esnext.map.group-by.js b/packages/core-js/modules/esnext.map.group-by.js index 0a68f6d6a838..5d7c6b7a899f 100644 --- a/packages/core-js/modules/esnext.map.group-by.js +++ b/packages/core-js/modules/esnext.map.group-by.js @@ -1,29 +1,30 @@ 'use strict'; var $ = require('../internals/export'); -var call = require('../internals/function-call'); var uncurryThis = require('../internals/function-uncurry-this'); -var isCallable = require('../internals/is-callable'); var aCallable = require('../internals/a-callable'); +var requireObjectCoercible = require('../internals/require-object-coercible'); var iterate = require('../internals/iterate'); -var Map = require('../internals/map-helpers').Map; +var MapHelpers = require('../internals/map-helpers'); +var Map = MapHelpers.Map; +var has = MapHelpers.has; +var get = MapHelpers.get; +var set = MapHelpers.set; var push = uncurryThis([].push); // `Map.groupBy` method -// https://github.com/tc39/proposal-collection-methods +// https://github.com/tc39/proposal-array-grouping $({ target: 'Map', stat: true, forced: true }, { - groupBy: function groupBy(iterable, keyDerivative) { - var C = isCallable(this) ? this : Map; - var newMap = new C(); - aCallable(keyDerivative); - var has = aCallable(newMap.has); - var get = aCallable(newMap.get); - var set = aCallable(newMap.set); - iterate(iterable, function (element) { - var derivedKey = keyDerivative(element); - if (!call(has, newMap, derivedKey)) call(set, newMap, derivedKey, [element]); - else push(call(get, newMap, derivedKey), element); + groupBy: function groupBy(items, callbackfn) { + requireObjectCoercible(items); + aCallable(callbackfn); + var map = new Map(); + var k = 0; + iterate(items, function (value) { + var key = callbackfn(value, k++); + if (!has(map, key)) set(map, key, [value]); + else push(get(map, key), value); }); - return newMap; + return map; } }); diff --git a/packages/core-js/modules/esnext.object.group-by.js b/packages/core-js/modules/esnext.object.group-by.js new file mode 100644 index 000000000000..d84a1b529478 --- /dev/null +++ b/packages/core-js/modules/esnext.object.group-by.js @@ -0,0 +1,29 @@ +'use strict'; +var $ = require('../internals/export'); +var getBuiltIn = require('../internals/get-built-in'); +var uncurryThis = require('../internals/function-uncurry-this'); +var aCallable = require('../internals/a-callable'); +var requireObjectCoercible = require('../internals/require-object-coercible'); +var has = require('../internals/has-own-property'); +var toPropertyKey = require('../internals/to-property-key'); +var iterate = require('../internals/iterate'); + +var create = getBuiltIn('Object', 'create'); +var push = uncurryThis([].push); + +// `Object.groupBy` method +// https://github.com/tc39/proposal-array-grouping +$({ target: 'Object', stat: true, forced: true }, { + groupBy: function groupBy(items, callbackfn) { + requireObjectCoercible(items); + aCallable(callbackfn); + var obj = create(null); + var k = 0; + iterate(items, function (value) { + var key = toPropertyKey(callbackfn(value, k++)); + if (!has(obj, key)) obj[key] = [value]; + else push(obj[key], value); + }); + return obj; + } +}); diff --git a/packages/core-js/proposals/array-grouping-v2.js b/packages/core-js/proposals/array-grouping-v2.js new file mode 100644 index 000000000000..5fb0286238b4 --- /dev/null +++ b/packages/core-js/proposals/array-grouping-v2.js @@ -0,0 +1,3 @@ +// https://github.com/tc39/proposal-array-grouping +require('../modules/esnext.map.group-by'); +require('../modules/esnext.object.group-by'); diff --git a/packages/core-js/stage/2.js b/packages/core-js/stage/2.js index 07c6591b2718..a77902975af1 100644 --- a/packages/core-js/stage/2.js +++ b/packages/core-js/stage/2.js @@ -1,5 +1,6 @@ var parent = require('./3'); +require('../proposals/array-grouping-v2'); require('../proposals/array-is-template-object'); require('../proposals/async-explicit-resource-management'); require('../proposals/async-iterator-helpers'); diff --git a/packages/core-js/stage/3.js b/packages/core-js/stage/3.js index e34fe20a18bc..e92ffea63c20 100644 --- a/packages/core-js/stage/3.js +++ b/packages/core-js/stage/3.js @@ -1,7 +1,6 @@ var parent = require('./4'); require('../proposals/array-from-async-stage-2'); -require('../proposals/array-grouping-stage-3-2'); require('../proposals/array-buffer-transfer'); require('../proposals/decorator-metadata-v2'); require('../proposals/explicit-resource-management'); @@ -10,6 +9,7 @@ require('../proposals/json-parse-with-source'); require('../proposals/set-methods-v2'); // TODO: Obsolete versions, remove from `core-js@4` require('../proposals/array-grouping-stage-3'); +require('../proposals/array-grouping-stage-3-2'); require('../proposals/change-array-by-copy'); require('../proposals/iterator-helpers-stage-3'); diff --git a/tests/compat/tests.js b/tests/compat/tests.js index 5a72572ba51d..894431759065 100644 --- a/tests/compat/tests.js +++ b/tests/compat/tests.js @@ -1690,6 +1690,9 @@ GLOBAL.tests = { 'esnext.number.from-string': function () { return Number.fromString; }, + 'esnext.object.group-by': function () { + return Object.groupBy; + }, // TODO: Remove this module from `core-js@4` since it's split to modules listed below 'esnext.observable': function () { return Observable; diff --git a/tests/entries/unit.mjs b/tests/entries/unit.mjs index 9a66513c5977..20c5c403e857 100644 --- a/tests/entries/unit.mjs +++ b/tests/entries/unit.mjs @@ -797,6 +797,7 @@ for (PATH of ['core-js-pure', 'core-js']) { ok(typeof load(NS, 'object/iterate-entries')({}).next == 'function'); ok(typeof load(NS, 'object/iterate-keys')({}).next == 'function'); ok(typeof load(NS, 'object/iterate-values')({}).next == 'function'); + ok(load(NS, 'object/group-by')([1, 2, 3, 4, 5], it => it % 2 === 0 ? 'even' : 'odd').odd.length === 3); ok('from' in load(NS, 'observable')); ok(typeof load(NS, 'reflect/define-metadata') == 'function'); ok(typeof load(NS, 'reflect/delete-metadata') == 'function'); @@ -896,6 +897,7 @@ for (PATH of ['core-js-pure', 'core-js']) { load('proposals/array-grouping'); load('proposals/array-grouping-stage-3'); load('proposals/array-grouping-stage-3-2'); + load('proposals/array-grouping-v2'); load('proposals/array-includes'); load('proposals/array-is-template-object'); load('proposals/array-last'); diff --git a/tests/unit-global/esnext.map.group-by.js b/tests/unit-global/esnext.map.group-by.js index 024d0921e1d0..7453dc0cc322 100644 --- a/tests/unit-global/esnext.map.group-by.js +++ b/tests/unit-global/esnext.map.group-by.js @@ -12,13 +12,15 @@ QUnit.test('Map.groupBy', assert => { assert.true(Map.groupBy([], it => it) instanceof Map); - assert.deepEqual(toArray(Map.groupBy([], it => it)), []); - assert.deepEqual(toArray(Map.groupBy([1, 2], it => it ** 2)), [[1, [1]], [4, [2]]]); - assert.deepEqual(toArray(Map.groupBy([1, 2, 1], it => it ** 2)), [[1, [1, 1]], [4, [2]]]); - assert.deepEqual(toArray(Map.groupBy(createIterable([1, 2]), it => it ** 2)), [[1, [1]], [4, [2]]]); + assert.deepEqual(toArray(groupBy([], it => it)), []); + assert.deepEqual(toArray(groupBy([1, 2], it => it ** 2)), [[1, [1]], [4, [2]]]); + assert.deepEqual(toArray(groupBy([1, 2, 1], it => it ** 2)), [[1, [1, 1]], [4, [2]]]); + assert.deepEqual(toArray(groupBy(createIterable([1, 2]), it => it ** 2)), [[1, [1]], [4, [2]]]); const element = {}; - Map.groupBy([element], it => assert.same(it, element)); - - // assert.throws(() => groupBy([1, 2], it => it)); + groupBy([element], function (it, i) { + assert.same(arguments.length, 2); + assert.same(it, element); + assert.same(i, 0); + }); }); diff --git a/tests/unit-global/esnext.object.group-by.js b/tests/unit-global/esnext.object.group-by.js new file mode 100644 index 000000000000..7959737d0f37 --- /dev/null +++ b/tests/unit-global/esnext.object.group-by.js @@ -0,0 +1,25 @@ +import { createIterable } from '../helpers/helpers'; + +QUnit.test('Object.groupBy', assert => { + const { groupBy, getPrototypeOf, entries } = Object; + + assert.isFunction(groupBy); + assert.arity(groupBy, 2); + assert.name(groupBy, 'groupBy'); + assert.looksNative(groupBy); + assert.nonEnumerable(Object, 'groupBy'); + + assert.same(getPrototypeOf(groupBy([], it => it)), null); + + assert.deepEqual(entries(groupBy([], it => it)), []); + assert.deepEqual(entries(groupBy([1, 2], it => it ** 2)), [['1', [1]], ['4', [2]]]); + assert.deepEqual(entries(groupBy([1, 2, 1], it => it ** 2)), [['1', [1, 1]], ['4', [2]]]); + assert.deepEqual(entries(groupBy(createIterable([1, 2]), it => it ** 2)), [['1', [1]], ['4', [2]]]); + + const element = {}; + groupBy([element], function (it, i) { + assert.same(arguments.length, 2); + assert.same(it, element); + assert.same(i, 0); + }); +}); diff --git a/tests/unit-pure/esnext.map.group-by.js b/tests/unit-pure/esnext.map.group-by.js index 5271ce02872e..4ebd9ee206ec 100644 --- a/tests/unit-pure/esnext.map.group-by.js +++ b/tests/unit-pure/esnext.map.group-by.js @@ -10,15 +10,17 @@ QUnit.test('Map.groupBy', assert => { assert.arity(groupBy, 2); assert.name(groupBy, 'groupBy'); - assert.true(Map.groupBy([], it => it) instanceof Map); + assert.true(groupBy([], it => it) instanceof Map); - assert.deepEqual(from(Map.groupBy([], it => it)), []); - assert.deepEqual(from(Map.groupBy([1, 2], it => it ** 2)), [[1, [1]], [4, [2]]]); - assert.deepEqual(from(Map.groupBy([1, 2, 1], it => it ** 2)), [[1, [1, 1]], [4, [2]]]); - assert.deepEqual(from(Map.groupBy(createIterable([1, 2]), it => it ** 2)), [[1, [1]], [4, [2]]]); + assert.deepEqual(from(groupBy([], it => it)), []); + assert.deepEqual(from(groupBy([1, 2], it => it ** 2)), [[1, [1]], [4, [2]]]); + assert.deepEqual(from(groupBy([1, 2, 1], it => it ** 2)), [[1, [1, 1]], [4, [2]]]); + assert.deepEqual(from(groupBy(createIterable([1, 2]), it => it ** 2)), [[1, [1]], [4, [2]]]); const element = {}; - Map.groupBy([element], it => assert.same(it, element)); - - // assert.throws(() => groupBy([1, 2], it => it)); + groupBy([element], function (it, i) { + assert.same(arguments.length, 2); + assert.same(it, element); + assert.same(i, 0); + }); }); diff --git a/tests/unit-pure/esnext.object.group-by.js b/tests/unit-pure/esnext.object.group-by.js new file mode 100644 index 000000000000..74b5aadbf61f --- /dev/null +++ b/tests/unit-pure/esnext.object.group-by.js @@ -0,0 +1,24 @@ +import { createIterable } from '../helpers/helpers'; +import groupBy from 'core-js-pure/full/object/group-by'; +import getPrototypeOf from 'core-js-pure/es/object/get-prototype-of'; +import entries from 'core-js-pure/es/object/entries'; + +QUnit.test('Object.groupBy', assert => { + assert.isFunction(groupBy); + assert.arity(groupBy, 2); + assert.name(groupBy, 'groupBy'); + + assert.same(getPrototypeOf(groupBy([], it => it)), null); + + assert.deepEqual(entries(groupBy([], it => it)), []); + assert.deepEqual(entries(groupBy([1, 2], it => it ** 2)), [['1', [1]], ['4', [2]]]); + assert.deepEqual(entries(groupBy([1, 2, 1], it => it ** 2)), [['1', [1, 1]], ['4', [2]]]); + assert.deepEqual(entries(groupBy(createIterable([1, 2]), it => it ** 2)), [['1', [1]], ['4', [2]]]); + + const element = {}; + groupBy([element], function (it, i) { + assert.same(arguments.length, 2); + assert.same(it, element); + assert.same(i, 0); + }); +});