Skip to content

Commit

Permalink
Merge pull request #959 from zloirock/array-grouping
Browse files Browse the repository at this point in the history
  • Loading branch information
zloirock committed Jul 29, 2021
2 parents 8b64ba5 + 0784656 commit ac93365
Show file tree
Hide file tree
Showing 21 changed files with 246 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,9 @@
- [`Array` filtering stage 1 proposal](https://github.com/tc39/proposal-array-filtering):
- `Array.prototype.filterReject` replaces `Array.prototype.filterOut`
- `%TypedArray%.prototype.filterReject` replaces `%TypedArray%.prototype.filterOut`
- Added [`Array` grouping stage 1 proposal](https://github.com/tc39/proposal-array-grouping):
- `Array.prototype.groupBy`
- `%TypedArray%.prototype.groupBy`
- Work with symbols made stricter: some missed before cases of methods that should throw an error on symbols now works as they should
- Handling `@@toPrimitive` in some cases of `ToPrimitive` internal logic made stricter
- Fixed work of `Request` with polyfilled `URLSearchParams`, [#965](https://github.com/zloirock/core-js/issues/965)
Expand Down
22 changes: 22 additions & 0 deletions README.md
Expand Up @@ -112,6 +112,7 @@ Promise.resolve(32).then(x => console.log(x)); // => 32
- [`.of` and `.from` methods on collection constructors](#of-and-from-methods-on-collection-constructors)
- [`compositeKey` and `compositeSymbol`](#compositekey-and-compositesymbol)
- [`Array` filtering](#array-filtering)
- [`Array` grouping](#array-grouping)
- [`Array` deduplication](#array-deduplication)
- [Getting last item from `Array`](#getting-last-item-from-array)
- [`Number.range`](#numberrange)
Expand Down Expand Up @@ -2292,6 +2293,27 @@ core-js/features/typed-array/filter-reject
```js
[1, 2, 3, 4, 5].filterReject(it => it % 2); // => [2, 4]
````
##### [`Array` grouping](#https://github.com/tc39/proposal-array-grouping)[⬆](#index)
Modules [`esnext.array.group-by`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.array.group-by.js) and [`esnext.typed-array.group-by`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.typed-array.group-by.js).
```js
class Array {
groupBy(callbackfn: (value: any, index: number, target: any) => key, thisArg?: any): { [key]: Array<mixed> };
}
class %TypedArray% {
groupBy(callbackfn: (value: number, index: number, target: %TypedArray%) => key, thisArg?: any): { [key]: %TypedArray% };
}
```
[*CommonJS entry points:*](#commonjs-api)
```
core-js/proposals/array-grouping
core-js(-pure)/features/array(/virtual)/group-by
core-js/features/typed-array/group-by
```
[*Examples*](http://es6.zloirock.ru/#log(%5B1%2C%202%2C%203%2C%204%2C%205%5D.groupBy(it%20%3D%3E%20it%20%25%202))%3B%20%2F%2F%20%3D%3E%20%7B%201%3A%20%5B1%2C%203%2C%205%5D%2C%200%3A%20%5B2%2C%204%5D%20%7D):
```js
[1, 2, 3, 4, 5].groupBy(it => it % 2); // => { 1: [1, 3, 5], 0: [2, 4] }
````
##### [Array deduplication](https://github.com/tc39/proposal-array-unique)[⬆](#index)
Modules [`esnext.array.unique-by`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.array.unique-by.js) and [`esnext.typed-array.unique-by`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.typed-array.unique-by.js)
```js
Expand Down
4 changes: 4 additions & 0 deletions packages/core-js-compat/src/data.mjs
Expand Up @@ -1425,6 +1425,8 @@ export const data = {
},
'esnext.array.find-last-index': {
},
'esnext.array.group-by': {
},
'esnext.array.is-template-object': {
},
'esnext.array.last-index': {
Expand Down Expand Up @@ -1683,6 +1685,8 @@ export const data = {
},
'esnext.typed-array.find-last-index': {
},
'esnext.typed-array.group-by': {
},
'esnext.typed-array.unique-by': {
},
'esnext.weak-map.delete-all': {
Expand Down
2 changes: 2 additions & 0 deletions packages/core-js-compat/src/modules-by-versions.mjs
Expand Up @@ -97,6 +97,8 @@ export default {
],
3.16: [
'esnext.array.filter-reject',
'esnext.array.group-by',
'esnext.typed-array.filter-reject',
'esnext.typed-array.group-by',
],
};
@@ -0,0 +1 @@
// empty
4 changes: 4 additions & 0 deletions packages/core-js/features/array/group-by.js
@@ -0,0 +1,4 @@
require('../../modules/esnext.array.group-by');
var entryUnbind = require('../../internals/entry-unbind');

module.exports = entryUnbind('Array', 'groupBy');
1 change: 1 addition & 0 deletions packages/core-js/features/array/index.js
Expand Up @@ -6,6 +6,7 @@ require('../../modules/esnext.array.filter-out');
require('../../modules/esnext.array.filter-reject');
require('../../modules/esnext.array.find-last');
require('../../modules/esnext.array.find-last-index');
require('../../modules/esnext.array.group-by');
require('../../modules/esnext.array.is-template-object');
require('../../modules/esnext.array.last-item');
require('../../modules/esnext.array.last-index');
Expand Down
4 changes: 4 additions & 0 deletions packages/core-js/features/array/virtual/group-by.js
@@ -0,0 +1,4 @@
require('../../../modules/esnext.array.group-by');
var entryVirtual = require('../../../internals/entry-virtual');

module.exports = entryVirtual('Array').groupBy;
1 change: 1 addition & 0 deletions packages/core-js/features/array/virtual/index.js
Expand Up @@ -6,6 +6,7 @@ require('../../../modules/esnext.array.filter-out');
require('../../../modules/esnext.array.filter-reject');
require('../../../modules/esnext.array.find-last');
require('../../../modules/esnext.array.find-last-index');
require('../../../modules/esnext.array.group-by');
require('../../../modules/esnext.array.unique-by');

module.exports = parent;
8 changes: 8 additions & 0 deletions packages/core-js/features/instance/group-by.js
@@ -0,0 +1,8 @@
var groupBy = require('../array/virtual/group-by');

var ArrayPrototype = Array.prototype;

module.exports = function (it) {
var own = it.groupBy;
return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.groupBy) ? groupBy : own;
};
1 change: 1 addition & 0 deletions packages/core-js/features/typed-array/group-by.js
@@ -0,0 +1 @@
require('../../modules/esnext.typed-array.group-by');
1 change: 1 addition & 0 deletions packages/core-js/features/typed-array/index.js
Expand Up @@ -6,6 +6,7 @@ require('../../modules/esnext.typed-array.filter-out');
require('../../modules/esnext.typed-array.filter-reject');
require('../../modules/esnext.typed-array.find-last');
require('../../modules/esnext.typed-array.find-last-index');
require('../../modules/esnext.typed-array.group-by');
require('../../modules/esnext.typed-array.unique-by');

module.exports = parent;
33 changes: 33 additions & 0 deletions packages/core-js/internals/array-group-by.js
@@ -0,0 +1,33 @@
var bind = require('../internals/function-bind-context');
var IndexedObject = require('../internals/indexed-object');
var toObject = require('../internals/to-object');
var toLength = require('../internals/to-length');
var toPropertyKey = require('../internals/to-property-key');
var objectCreate = require('../internals/object-create');
var arrayFromConstructorAndList = require('../internals/array-from-constructor-and-list');

var push = [].push;

module.exports = function ($this, callbackfn, that, specificConstructor) {
var O = toObject($this);
var self = IndexedObject(O);
var boundFunction = bind(callbackfn, that, 3);
var target = objectCreate(null);
var length = toLength(self.length);
var index = 0;
var Constructor, key, value;
for (;length > index; index++) {
value = self[index];
key = toPropertyKey(boundFunction(value, index, O));
// in some IE10 builds, `hasOwnProperty` returns incorrect result on integer keys
// but since it's a `null` prototype object, we can safely use `in`
if (key in target) push.call(target[key], value);
else target[key] = [value];
}
if (specificConstructor) {
Constructor = specificConstructor(O);
if (Constructor !== Array) {
for (key in target) target[key] = arrayFromConstructorAndList(Constructor, target[key]);
}
} return target;
};
16 changes: 16 additions & 0 deletions packages/core-js/modules/esnext.array.group-by.js
@@ -0,0 +1,16 @@
'use strict';
var $ = require('../internals/export');
var $groupBy = require('../internals/array-group-by');
var arraySpeciesConstructor = require('../internals/array-species-constructor');
var addToUnscopables = require('../internals/add-to-unscopables');

// `Array.prototype.groupBy` method
// https://github.com/tc39/proposal-array-grouping
$({ target: 'Array', proto: true }, {
groupBy: function groupBy(callbackfn /* , thisArg */) {
var thisArg = arguments.length > 1 ? arguments[1] : undefined;
return $groupBy(this, callbackfn, thisArg, arraySpeciesConstructor);
}
});

addToUnscopables('groupBy');
14 changes: 14 additions & 0 deletions packages/core-js/modules/esnext.typed-array.group-by.js
@@ -0,0 +1,14 @@
'use strict';
var ArrayBufferViewCore = require('../internals/array-buffer-view-core');
var $groupBy = require('../internals/array-group-by');
var typedArraySpeciesConstructor = require('../internals/typed-array-species-constructor');

var aTypedArray = ArrayBufferViewCore.aTypedArray;
var exportTypedArrayMethod = ArrayBufferViewCore.exportTypedArrayMethod;

// `%TypedArray%.prototype.groupBy` method
// https://github.com/tc39/proposal-array-grouping
exportTypedArrayMethod('groupBy', function groupBy(callbackfn /* , thisArg */) {
var thisArg = arguments.length > 1 ? arguments[1] : undefined;
return $groupBy(aTypedArray(this), callbackfn, thisArg, typedArraySpeciesConstructor);
});
3 changes: 3 additions & 0 deletions packages/core-js/proposals/array-grouping.js
@@ -0,0 +1,3 @@
// https://github.com/tc39/proposal-array-grouping
require('../modules/esnext.array.group-by');
require('../modules/esnext.typed-array.group-by');
1 change: 1 addition & 0 deletions packages/core-js/stage/1.js
@@ -1,4 +1,5 @@
require('../proposals/array-filtering');
require('../proposals/array-grouping');
require('../proposals/array-last');
require('../proposals/array-unique');
require('../proposals/collection-methods');
Expand Down
10 changes: 10 additions & 0 deletions tests/commonjs.js
Expand Up @@ -71,6 +71,7 @@ for (PATH of ['core-js-pure', 'core-js']) {
ok(typeof load('features/array/filter-reject') === 'function');
ok(typeof load('features/array/flat') === 'function');
ok(typeof load('features/array/flat-map') === 'function');
ok(typeof load('features/array/group-by') === 'function');
ok(typeof load('features/array/some') === 'function');
ok(typeof load('features/array/every') === 'function');
ok(typeof load('features/array/reduce') === 'function');
Expand Down Expand Up @@ -106,6 +107,7 @@ for (PATH of ['core-js-pure', 'core-js']) {
ok(typeof load('features/array/virtual/filter-reject') === 'function');
ok(typeof load('features/array/virtual/flat') === 'function');
ok(typeof load('features/array/virtual/flat-map') === 'function');
ok(typeof load('features/array/virtual/group-by') === 'function');
ok(typeof load('features/array/virtual/some') === 'function');
ok(typeof load('features/array/virtual/every') === 'function');
ok(typeof load('features/array/virtual/reduce') === 'function');
Expand Down Expand Up @@ -970,6 +972,7 @@ for (PATH of ['core-js-pure', 'core-js']) {
load('proposals/accessible-object-hasownproperty');
load('proposals/array-filtering');
load('proposals/array-find-from-last');
load('proposals/array-grouping');
load('proposals/array-is-template-object');
load('proposals/array-last');
load('proposals/array-unique');
Expand Down Expand Up @@ -1182,6 +1185,12 @@ for (PATH of ['core-js-pure', 'core-js']) {
ok(typeof instanceFilterReject([]) === 'function');
ok(instanceFilterReject([]).call([1, 2, 3], it => it % 2).length === 1);

const instanceGroupBy = load('features/instance/group-by');
ok(typeof instanceGroupBy === 'function');
ok(instanceGroupBy({}) === undefined);
ok(typeof instanceGroupBy([]) === 'function');
ok(instanceGroupBy([]).call([1, 2, 3], it => it % 2)[1].length === 2);

let instanceFindIndex = load('features/instance/find-index');
ok(typeof instanceFindIndex === 'function');
ok(instanceFindIndex({}) === undefined);
Expand Down Expand Up @@ -1710,6 +1719,7 @@ load('features/typed-array/fill');
load('features/typed-array/filter');
load('features/typed-array/filter-out');
load('features/typed-array/filter-reject');
load('features/typed-array/group-by');
load('features/typed-array/find');
load('features/typed-array/find-index');
load('features/typed-array/find-last');
Expand Down
36 changes: 36 additions & 0 deletions tests/pure/esnext.array.group-by.js
@@ -0,0 +1,36 @@
import { STRICT } from '../helpers/constants';

import Symbol from 'core-js-pure/features/symbol';
import groupBy from 'core-js-pure/features/array/group-by';
import getPrototypeOf from 'core-js-pure/features/object/get-prototype-of';

QUnit.test('Array#groupBy', assert => {
assert.isFunction(groupBy);
let array = [1];
const context = {};
groupBy(array, function (value, key, that) {
assert.same(arguments.length, 3, 'correct number of callback arguments');
assert.same(value, 1, 'correct value in callback');
assert.same(key, 0, 'correct index in callback');
assert.same(that, array, 'correct link to array in callback');
assert.same(this, context, 'correct callback context');
}, context);
assert.same(getPrototypeOf(groupBy([], it => it)), null, 'null proto');
assert.deepEqual(groupBy([1, 2, 3], it => it % 2), { 1: [1, 3], 0: [2] }, '#1');
assert.deepEqual(
groupBy([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], it => `i${ it % 5 }`),
{ i1: [1, 6, 11], i2: [2, 7, 12], i3: [3, 8], i4: [4, 9], i0: [5, 10] },
'#2',
);
assert.deepEqual(groupBy(Array(3), it => it), { undefined: [undefined, undefined, undefined] }, '#3');
if (STRICT) {
assert.throws(() => groupBy(null, () => { /* empty */ }), TypeError);
assert.throws(() => groupBy(undefined, () => { /* empty */ }), TypeError);
}
array = [1];
// eslint-disable-next-line object-shorthand -- constructor
array.constructor = { [Symbol.species]: function () {
return { foo: 1 };
} };
assert.same(groupBy(array, Boolean).true.foo, 1, '@@species');
});
39 changes: 39 additions & 0 deletions tests/tests/esnext.array.group-by.js
@@ -0,0 +1,39 @@
import { STRICT } from '../helpers/constants';

const { getPrototypeOf } = Object;

QUnit.test('Array#groupBy', assert => {
const { groupBy } = Array.prototype;
assert.isFunction(groupBy);
assert.arity(groupBy, 1);
assert.name(groupBy, 'groupBy');
assert.looksNative(groupBy);
assert.nonEnumerable(Array.prototype, 'groupBy');
let array = [1];
const context = {};
array.groupBy(function (value, key, that) {
assert.same(arguments.length, 3, 'correct number of callback arguments');
assert.same(value, 1, 'correct value in callback');
assert.same(key, 0, 'correct index in callback');
assert.same(that, array, 'correct link to array in callback');
assert.same(this, context, 'correct callback context');
}, context);
assert.same(getPrototypeOf([].groupBy(it => it)), null, 'null proto');
assert.deepEqual([1, 2, 3].groupBy(it => it % 2), { 1: [1, 3], 0: [2] }, '#1');
assert.deepEqual(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].groupBy(it => `i${ it % 5 }`),
{ i1: [1, 6, 11], i2: [2, 7, 12], i3: [3, 8], i4: [4, 9], i0: [5, 10] },
'#2',
);
assert.deepEqual(Array(3).groupBy(it => it), { undefined: [undefined, undefined, undefined] }, '#3');
if (STRICT) {
assert.throws(() => groupBy.call(null, () => { /* empty */ }), TypeError);
assert.throws(() => groupBy.call(undefined, () => { /* empty */ }), TypeError);
}
array = [1];
// eslint-disable-next-line object-shorthand -- constructor
array.constructor = { [Symbol.species]: function () {
return { foo: 1 };
} };
assert.same(array.groupBy(Boolean).true.foo, 1, '@@species');
});
42 changes: 42 additions & 0 deletions tests/tests/esnext.typed-array.group-by.js
@@ -0,0 +1,42 @@
import { DESCRIPTORS, GLOBAL, TYPED_ARRAYS } from '../helpers/constants';

const { getPrototypeOf } = Object;

if (DESCRIPTORS) QUnit.test('%TypedArrayPrototype%.groupBy', assert => {
// we can't implement %TypedArrayPrototype% in all engines, so run all tests for each typed array constructor
for (const name in TYPED_ARRAYS) {
const TypedArray = GLOBAL[name];
const { groupBy } = TypedArray.prototype;
assert.isFunction(groupBy, `${ name }::groupBy is function`);
assert.arity(groupBy, 1, `${ name }::groupBy arity is 1`);
assert.name(groupBy, 'groupBy', `${ name }::groupBy name is 'groupBy'`);
assert.looksNative(groupBy, `${ name }::groupBy looks native`);
const array = new TypedArray([1]);
const context = {};
array.groupBy(function (value, key, that) {
assert.same(arguments.length, 3, 'correct number of callback arguments');
assert.same(value, 1, 'correct value in callback');
assert.same(key, 0, 'correct index in callback');
assert.same(that, array, 'correct link to array in callback');
assert.same(this, context, 'correct callback context');
}, context);

assert.same(getPrototypeOf(new TypedArray([1]).groupBy(it => it)), null, 'null proto');
assert.ok(new TypedArray([1]).groupBy(it => it)[1] instanceof TypedArray, 'instance');
assert.deepEqual(
new TypedArray([1, 2, 3]).groupBy(it => it % 2),
{ 1: new TypedArray([1, 3]), 0: new TypedArray([2]) },
'#1',
);
assert.deepEqual(new TypedArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]).groupBy(it => `i${ it % 5 }`), {
i1: new TypedArray([1, 6, 11]),
i2: new TypedArray([2, 7, 12]),
i3: new TypedArray([3, 8]),
i4: new TypedArray([4, 9]),
i0: new TypedArray([5, 10]),
}, '#2');

assert.throws(() => groupBy.call([0], () => { /* empty */ }), "isn't generic");
}
});

0 comments on commit ac93365

Please sign in to comment.