Issue #2990 - implement object.GroupBy

This commit is contained in:
Basilisk-Dev 2026-03-10 10:45:22 -04:00 • committed by OwnedByWuigi
commit 1489a6f8b5
3 changed files with 275 additions and 145 deletions

View file

@ -1247,6 +1247,7 @@ static const JSFunctionSpec object_static_methods[] = {
JS_FN("seal", obj_seal, 1, 0),
JS_FN("isSealed", obj_isSealed, 1, 0),
JS_SELF_HOSTED_FN("fromEntries", "ObjectFromEntries", 1, 0),
JS_SELF_HOSTED_FN("groupBy", "ObjectGroupBy", 2, 0),
JS_SELF_HOSTED_FN("hasOwn", "ObjectHasOwn", 2, 0),
JS_FS_END
};

View file

@ -8,21 +8,22 @@ function ObjectStaticAssign(target, firstSource) {
var to = ToObject(target);
// Step 3.
if (arguments.length < 2)
return to;
if (arguments.length < 2) return to;
// Steps 4-5.
for (var i = 1; i < arguments.length; i++) {
// Step 5.a.
var nextSource = arguments[i];
if (nextSource === null || nextSource === undefined)
continue;
if (nextSource === null || nextSource === undefined) continue;
// Steps 5.b.i-ii.
var from = ToObject(nextSource);
// Steps 5.b.iii-iv.
var keys = OwnPropertyKeys(from, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS);
var keys = OwnPropertyKeys(
from,
JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS,
);
// Step 5.c.
for (var nextIndex = 0, len = keys.length; nextIndex < len; nextIndex++) {
@ -47,7 +48,10 @@ function ObjectGetOwnPropertyDescriptors(O) {
var obj = ToObject(O);
// Step 2.
var keys = OwnPropertyKeys(obj, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS);
var keys = OwnPropertyKeys(
obj,
JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS,
);
// Step 3.
var descriptors = {};
@ -99,15 +103,14 @@ function ObjectDefineSetter(name, setter) {
var object = ToObject(this);
// Step 2.
if (!IsCallable(setter))
ThrowTypeError(JSMSG_BAD_GETTER_OR_SETTER, "setter");
if (!IsCallable(setter)) ThrowTypeError(JSMSG_BAD_GETTER_OR_SETTER, "setter");
// Step 3.
var desc = {
__proto__: null,
enumerable: true,
configurable: true,
set: setter
set: setter,
};
// Step 4.
@ -125,15 +128,14 @@ function ObjectDefineGetter(name, getter) {
var object = ToObject(this);
// Step 2.
if (!IsCallable(getter))
ThrowTypeError(JSMSG_BAD_GETTER_OR_SETTER, "getter");
if (!IsCallable(getter)) ThrowTypeError(JSMSG_BAD_GETTER_OR_SETTER, "getter");
// Step 3.
var desc = {
__proto__: null,
enumerable: true,
configurable: true,
get: getter
get: getter,
};
// Step 4.
@ -151,7 +153,7 @@ function ObjectLookupSetter(name) {
var object = ToObject(this);
// Step 2.
var key = ToPropertyKey(name)
var key = ToPropertyKey(name);
do {
// Step 3.a.
@ -160,8 +162,7 @@ function ObjectLookupSetter(name) {
// Step 3.b.
if (desc) {
// Step.b.i.
if (callFunction(std_Object_hasOwnProperty, desc, "set"))
return desc.set;
if (callFunction(std_Object_hasOwnProperty, desc, "set")) return desc.set;
// Step.b.ii.
return undefined;
@ -180,7 +181,7 @@ function ObjectLookupGetter(name) {
var object = ToObject(this);
// Step 2.
var key = ToPropertyKey(name)
var key = ToPropertyKey(name);
do {
// Step 3.a.
@ -189,8 +190,7 @@ function ObjectLookupGetter(name) {
// Step 3.b.
if (desc) {
// Step.b.i.
if (callFunction(std_Object_hasOwnProperty, desc, "get"))
return desc.get;
if (callFunction(std_Object_hasOwnProperty, desc, "get")) return desc.get;
// Step.b.ii.
return undefined;
@ -221,6 +221,46 @@ function ObjectFromEntries(iter) {
return obj;
}
// ES2024
// Object.groupBy ( items, callbackfn )
function ObjectGroupBy(items, callbackfn) {
// Step 1.
RequireObjectCoercible(items);
// Step 2.
if (!IsCallable(callbackfn))
ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(1, callbackfn));
// Step 3.
var groups = std_Object_create(null);
// Step 4.
var k = 0;
// Steps 5-8.
for (var value of allowContentIter(items)) {
// Step 6.a.
var key = callContentFunction(callbackfn, undefined, value, k);
// Step 6.b.
key = ToPropertyKey(key);
// Steps 6.c-d.
var elements = groups[key];
if (elements === undefined) {
_DefineDataProperty(groups, key, [value]);
} else {
callFunction(std_Array_push, elements, value);
}
// Step 6.e.
k++;
}
// Step 9.
return groups;
}
// Proposal https://github.com/tc39/proposal-accessible-object-hasownproperty
// Object.hasOwn (Object, Property)
function ObjectHasOwn(O, P) {

View file

@ -0,0 +1,89 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
*/
assertEq(typeof Object.groupBy, "function");
assertEq(Object.groupBy.length, 2);
let desc = Object.getOwnPropertyDescriptor(Object, "groupBy");
assertEq(desc.enumerable, false);
assertEq(desc.writable, true);
assertEq(desc.configurable, true);
{
let values = [1, 2, 3, 4, 5];
let calls = [];
let grouped = Object.groupBy(values, function (value, index) {
"use strict";
assertEq(this, undefined);
calls.push([value, index]);
return value % 2 ? "odd" : "even";
});
assertEq(Object.getPrototypeOf(grouped), null);
assertDeepEq(Object.keys(grouped), ["odd", "even"]);
assertDeepEq(grouped.odd, [1, 3, 5]);
assertDeepEq(grouped.even, [2, 4]);
assertDeepEq(calls, [[1, 0], [2, 1], [3, 2], [4, 3], [5, 4]]);
}
{
let sym = Symbol("key");
let grouped = Object.groupBy([10, 11], value => value === 10 ? sym : 0);
assertDeepEq(Object.keys(grouped), ["0"]);
assertDeepEq(grouped[0], [11]);
let symbols = Object.getOwnPropertySymbols(grouped);
assertEq(symbols.length, 1);
assertEq(symbols[0], sym);
assertDeepEq(grouped[sym], [10]);
}
{
let grouped = Object.groupBy([1, 2], () => "__proto__");
assertEq(Object.getPrototypeOf(grouped), null);
assertDeepEq(grouped["__proto__"], [1, 2]);
let protoKeyDesc = Object.getOwnPropertyDescriptor(grouped, "__proto__");
assertEq(protoKeyDesc.enumerable, true);
assertEq(protoKeyDesc.writable, true);
assertEq(protoKeyDesc.configurable, true);
}
{
let closed = false;
let iterable = {
[Symbol.iterator]() {
let i = 0;
return {
next() {
i++;
if (i <= 3)
return { value: i, done: false };
return { done: true };
},
return() {
closed = true;
return { done: true };
}
};
}
};
assertThrowsValue(() => Object.groupBy(iterable, value => {
if (value === 2)
throw 42;
return "x";
}), 42);
assertEq(closed, true);
}
assertThrowsInstanceOf(() => Object.groupBy([], null), TypeError);
assertThrowsInstanceOf(() => Object.groupBy(null, null), TypeError);
assertThrowsInstanceOf(() => Object.groupBy({}, x => x), TypeError);
if (typeof reportCompare === "function")
reportCompare(true, true);