]> Lady’s Gitweb - Pisces/commitdiff
Refactor object.js not to depend on function.js
authorLady <redacted>
Fri, 24 Nov 2023 23:18:42 +0000 (18:18 -0500)
committerLady <redacted>
Fri, 24 Nov 2023 23:34:40 +0000 (18:34 -0500)
This unfortunately requires a literal re·implementation of
`Object.assign`, but it’s otherwise worth it.

This commit also adds `defineOwnDataProperty` and
`defineOwnNonenumerableDataProperty` for convenience.

object.js
object.test.js

index 9132e4b5d2cf2eb44677151adb828cb789ee44d1..aa94bc571c469f3125b4d701dc07bacd6220ccdb 100644 (file)
--- a/object.js
+++ b/object.js
@@ -7,10 +7,9 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
 
-import { bind, call, createArrowFunction } from "./function.js";
 import {
   IS_CONCAT_SPREADABLE,
-  ITERATOR,
+  isAccessorDescriptor,
   SPECIES,
   toFunctionName,
   toLength,
@@ -20,6 +19,40 @@ import {
   UNDEFINED,
 } from "./value.js";
 
+const { isArray } = Array;
+const object = Object;
+const {
+  assign,
+  create,
+  defineProperty,
+  defineProperties,
+  entries,
+  freeze: objectFreeze,
+  fromEntries,
+  getOwnPropertyDescriptor: objectGetOwnPropertyDescriptor,
+  getOwnPropertyNames,
+  getOwnPropertySymbols: objectGetOwnPropertySymbols,
+  getPrototypeOf,
+  hasOwn,
+  isExtensible,
+  isFrozen,
+  isSealed,
+  keys,
+  preventExtensions: objectPreventExtensions,
+  seal: objectSeal,
+  setPrototypeOf,
+  values,
+} = Object;
+const {
+  apply: call,
+  deleteProperty,
+  get,
+  getOwnPropertyDescriptor: reflectGetOwnPropertyDescriptor,
+  has,
+  ownKeys,
+  set,
+} = Reflect;
+
 /**
  * An object whose properties are lazy‐loaded from the methods on the
  * own properties of the provided object.
@@ -56,380 +89,146 @@ export class LazyLoader extends null {
     } else {
       // The provided value is an object; process it and build the
       // result.
-      const result = objectCreate(getPrototype(loadMethods));
-      const methodKeys = getOwnPropertyKeys(loadMethods);
+      const result = create(getPrototypeOf(loadMethods));
+      const methodKeys = ownKeys(loadMethods);
       for (let index = 0; index < methodKeys.length; ++index) {
         // Iterate over the property keys of the provided object and
         // define getters and setters appropriately on the result.
         const methodKey = methodKeys[index];
         const { configurable, enumerable, writable } =
           getOwnPropertyDescriptor(loadMethods, methodKey);
-        defineOwnProperty(result, methodKey, {
-          configurable: true,
-          enumerable,
-          get: defineOwnProperty(
-            () => {
-              const value = call(loadMethods[methodKey], result, []);
-              defineOwnProperty(result, methodKey, {
-                configurable,
-                enumerable,
-                value,
-                writable,
-              });
-              return value;
-            },
-            "name",
-            { value: toFunctionName(methodKey, "get") },
-          ),
-          set: writable
-            ? defineOwnProperty(
-              ($) =>
-                defineOwnProperty(result, methodKey, {
-                  configurable,
-                  enumerable,
-                  value: $,
-                  writable,
-                }),
+        defineProperty(
+          result,
+          methodKey,
+          assign(create(null), {
+            configurable: true,
+            enumerable,
+            get: defineProperty(
+              () => {
+                const value = call(loadMethods[methodKey], result, []);
+                defineProperty(
+                  result,
+                  methodKey,
+                  assign(create(null), {
+                    configurable,
+                    enumerable,
+                    value,
+                    writable,
+                  }),
+                );
+                return value;
+              },
               "name",
-              { value: toFunctionName(methodKey, "set") },
-            )
-            : void {},
-        });
+              assign(create(null), {
+                value: toFunctionName(methodKey, "get"),
+              }),
+            ),
+            set: writable
+              ? defineProperty(
+                ($) =>
+                  defineProperty(
+                    result,
+                    methodKey,
+                    assign(create(null), {
+                      configurable,
+                      enumerable,
+                      value: $,
+                      writable,
+                    }),
+                  ),
+                "name",
+                assign(create(null), {
+                  value: toFunctionName(methodKey, "set"),
+                }),
+              )
+              : UNDEFINED,
+          }),
+        );
       }
       return result;
     }
   }
 }
 
+/**
+ * Defines an own enumerable data property on the provided object with
+ * the provided property key and value.
+ */
+export const defineOwnDataProperty = (O, P, V) =>
+  defineProperty(
+    O,
+    P,
+    assign(create(null), {
+      configurable: true,
+      enumerable: true,
+      value: V,
+      writable: true,
+    }),
+  );
+
+/**
+ * Defines an own nonenumerable data property on the provided object
+ * with the provided property key and value.
+ */
+export const defineOwnNonenumerableDataProperty = (O, P, V) =>
+  defineProperty(
+    O,
+    P,
+    assign(create(null), {
+      configurable: true,
+      enumerable: false,
+      value: V,
+      writable: true,
+    }),
+  );
+
 /**
  * Defines an own property on the provided object on the provided
  * property key using the provided property descriptor.
  *
  * ※ This is effectively an alias for `Object.defineProperty`.
  */
-export const defineOwnProperty = createArrowFunction(
-  Object.defineProperty,
-  { name: "defineOwnProperty" },
-);
-
-export const {
-  /**
-   * Defines own properties on the provided object using the
-   * descriptors on the enumerable own properties of the provided
-   * additional objects.
-   *
-   * ※ This differs from `Object.defineProperties` in that it can take
-   * multiple source objects.
-   */
-  defineOwnProperties,
-
-  /**
-   * Returns a new frozen shallow copy of the enumerable own properties
-   * of the provided object, according to the following rules :—
-   *
-   * - For data properties, create a nonconfigurable, nonwritable
-   *   property with the same value.
-   *
-   * - For accessor properties, create a nonconfigurable accessor
-   *   property with the same getter *and* setter.
-   *
-   * The prototype for the resulting object will be taken from the
-   * `.prototype` property of the provided constructor, or the
-   * `.prototype` of the `.constructor` of the provided object if the
-   * provided constructor is undefined. If the used constructor has a
-   * nonnullish `.[Symbol.species]`, that will be used instead. If the
-   * used constructor or species is nullish or does not have a
-   * `.prototype` property, the prototype is set to null.
-   *
-   * ※ The prototype of the provided object itself is ignored.
-   */
-  frozenCopy,
-
-  /**
-   * Returns the property descriptor record for the own property with
-   * the provided property key on the provided object, or null if none
-   * exists.
-   *
-   * ※ This is effectively an alias for
-   * `Object.getOwnPropertyDescriptor`, but the return value is a
-   * proxied object with null prototype.
-   */
-  getOwnPropertyDescriptor,
-
-  /**
-   * Returns the property descriptors for the own properties on the
-   * provided object.
-   *
-   * ※ This is effectively an alias for
-   * `Object.getOwnPropertyDescriptors`, but the values on the
-   * resulting object are proxied objects with null prototypes.
-   */
-  getOwnPropertyDescriptors,
-
-  /**
-   * Returns whether the provided object is frozen.
-   *
-   * ※ This function returns false for nonobjects.
-   *
-   * ※ This is effectively an alias for `!Object.isFrozen`.
-   */
-  isUnfrozenObject,
-
-  /**
-   * Returns whether the provided object is sealed.
-   *
-   * ※ This function returns false for nonobjects.
-   *
-   * ※ This is effectively an alias for `!Object.isSealed`.
-   */
-  isUnsealedObject,
-
-  /**
-   * Sets the prototype of the provided object to the provided value
-   * and returns the object.
-   *
-   * ※ This is effectively an alias for `Object.setPrototypeOf`.
-   */
-  setPrototype,
+export const defineOwnProperty = (O, P, Desc) =>
+  defineProperty(O, P, Desc);
 
-  /**
-   * Returns the provided value converted to an object.
-   *
-   * Existing objects are returned with no modification.
-   *
-   * ☡ This function throws if its argument is null or undefined.
-   */
-  toObject,
-} = (() => {
-  const createObject = Object;
-  const {
-    create,
-    defineProperties,
-    getOwnPropertyDescriptor: objectGetOwnPropertyDescriptor,
-    getPrototypeOf,
-    isFrozen,
-    isSealed,
-    setPrototypeOf,
-  } = Object;
-  const {
-    next: generatorIteratorNext,
-  } = getPrototypeOf(function* () {}.prototype);
-  const propertyDescriptorEntryIterablePrototype = {
-    [ITERATOR]() {
-      return {
-        next: bind(generatorIteratorNext, this.generator(), []),
-      };
-    },
-  };
-  const propertyDescriptorEntryIterable = ($) =>
-    create(propertyDescriptorEntryIterablePrototype, {
-      generator: { value: $ },
-    });
-
-  return {
-    defineOwnProperties: (O, ...sources) => {
-      const { length } = sources;
-      for (let index = 0; index < length; ++index) {
-        defineProperties(O, sources[index]);
-      }
-      return O;
-    },
-    frozenCopy: (O, constructor = O?.constructor) => {
-      if (O == null) {
-        // O is null or undefined.
-        throw new TypeError(
-          "Piscēs: Cannot copy properties of null or undefined.",
-        );
-      } else {
-        // O is not null or undefined.
-        //
-        // (If not provided, the constructor will be the value of
-        // getting the `.constructor` property of O.)
-        const species = constructor?.[SPECIES] ?? constructor;
-        return preventExtensions(
-          objectCreate(
-            species == null || !("prototype" in species)
-              ? null
-              : species.prototype,
-            objectFromEntries(
-              propertyDescriptorEntryIterable(function* () {
-                const ownPropertyKeys = getOwnPropertyKeys(O);
-                for (
-                  let i = 0;
-                  i < ownPropertyKeys.length;
-                  ++i
-                ) {
-                  const P = ownPropertyKeys[i];
-                  const Desc = getOwnPropertyDescriptor(O, P);
-                  if (Desc.enumerable) {
-                    // P is an enumerable property.
-                    yield [
-                      P,
-                      "get" in Desc || "set" in Desc
-                        ? {
-                          configurable: false,
-                          enumerable: true,
-                          get: Desc.get,
-                          set: Desc.set,
-                        }
-                        : {
-                          configurable: false,
-                          enumerable: true,
-                          value: Desc.value,
-                          writable: false,
-                        },
-                    ];
-                  } else {
-                    // P is not an enumerable property.
-                    /* do nothing */
-                  }
-                }
-              }),
-            ),
-          ),
-        );
-      }
-    },
-    getOwnPropertyDescriptor: (O, P) => {
-      const desc = objectGetOwnPropertyDescriptor(O, P);
-      return desc === UNDEFINED
-        ? UNDEFINED
-        : toPropertyDescriptor(desc);
-    },
-    getOwnPropertyDescriptors: (O) => {
-      const obj = toObject(O);
-      const ownKeys = getOwnPropertyKeys(obj);
-      const descriptors = {};
-      for (let k = 0; k < ownKeys.length; ++k) {
-        const key = ownKeys[k];
-        defineOwnProperty(descriptors, key, {
-          configurable: true,
-          enumerable: true,
-          value: getOwnPropertyDescriptor(O, key),
-          writable: true,
-        });
-      }
-      return descriptors;
-    },
-    isUnfrozenObject: (O) => !isFrozen(O),
-    isUnsealedObject: (O) => !isSealed(O),
-    setPrototype: (O, proto) => {
-      const obj = toObject(O);
-      if (O === obj) {
-        // The provided value is an object; set its prototype normally.
-        return setPrototypeOf(O, proto);
-      } else {
-        // The provided value is not an object; attempt to set the
-        // prototype on a coerced version with extensions prevented,
-        // then return the provided value.
-        //
-        // This will throw if the given prototype does not match the
-        // existing one on the coerced object.
-        setPrototypeOf(preventExtensions(obj), proto);
-        return O;
-      }
-    },
-    toObject: ($) => {
-      if ($ == null) {
-        // The provided value is nullish; this is an error.
-        throw new TypeError(
-          `Piscēs: Cannot convert ${$} into an object.`,
-        );
-      } else {
-        // The provided value is not nullish; coerce it to an object.
-        return createObject($);
-      }
-    },
-  };
-})();
-
-export const {
-  /**
-   * Removes the provided property key from the provided object and
-   * returns the object.
-   *
-   * ※ This function differs from `Reflect.deleteProperty` and the
-   * `delete` operator in that it throws if the deletion is
-   * unsuccessful.
-   *
-   * ☡ This function throws if the first argument is not an object.
-   */
-  deleteOwnProperty,
-
-  /**
-   * Returns an array of property keys on the provided object.
-   *
-   * ※ This is effectively an alias for `Reflect.ownKeys`, except that
-   * it does not require that the argument be an object.
-   */
-  getOwnPropertyKeys,
-
-  /**
-   * Returns the value of the provided property key on the provided
-   * object.
-   *
-   * ※ This is effectively an alias for `Reflect.get`, except that it
-   * does not require that the argument be an object.
-   */
-  getPropertyValue,
-
-  /**
-   * Returns whether the provided property key exists on the provided
-   * object.
-   *
-   * ※ This is effectively an alias for `Reflect.has`, except that it
-   * does not require that the argument be an object.
-   *
-   * ※ This includes properties present on the prototype chain.
-   */
-  hasProperty,
+/**
+ * Defines own properties on the provided object using the descriptors
+ * on the enumerable own properties of the provided additional objects.
+ *
+ * ※ This differs from `Object.defineProperties` in that it can take
+ * multiple source objects.
+ */
+export const defineOwnProperties = (O, ...sources) => {
+  const { length } = sources;
+  for (let k = 0; k < length; ++k) {
+    defineProperties(O, sources[k]);
+  }
+  return O;
+};
 
-  /**
-   * Sets the provided property key to the provided value on the
-   * provided object and returns the object.
-   *
-   * ※ This function differs from `Reflect.set` in that it throws if
-   * the setting is unsuccessful.
-   *
-   * ☡ This function throws if the first argument is not an object.
-   */
-  setPropertyValue,
-} = (() => {
-  const { deleteProperty, get, has, ownKeys, set } = Reflect;
-
-  return {
-    deleteOwnProperty: (O, P) => {
-      if (type(O) !== "object") {
-        throw new TypeError(
-          `Piscēs: Tried to set property but provided value was not an object: ${V}`,
-        );
-      } else if (!deleteProperty(O, P)) {
-        throw new TypeError(
-          `Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`,
-        );
-      } else {
-        return O;
-      }
-    },
-    getOwnPropertyKeys: (O) => ownKeys(toObject(O)),
-    getPropertyValue: (O, P, Receiver = O) =>
-      get(toObject(O), P, Receiver),
-    hasProperty: (O, P) => has(toObject(O), P),
-    setPropertyValue: (O, P, V, Receiver = O) => {
-      if (type(O) !== "object") {
-        throw new TypeError(
-          `Piscēs: Tried to set property but provided value was not an object: ${V}`,
-        );
-      } else if (!set(O, P, V, Receiver)) {
-        throw new TypeError(
-          `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`,
-        );
-      } else {
-        return O;
-      }
-    },
-  };
-})();
+/**
+ * Removes the provided property key from the provided object and
+ * returns the object.
+ *
+ * ※ This function differs from `Reflect.deleteProperty` and the
+ * `delete` operator in that it throws if the deletion is
+ * unsuccessful.
+ *
+ * ☡ This function throws if the first argument is not an object.
+ */
+export const deleteOwnProperty = (O, P) => {
+  if (type(O) !== "object") {
+    throw new TypeError(
+      `Piscēs: Tried to set property but provided value was not an object: ${V}`,
+    );
+  } else if (!deleteProperty(O, P)) {
+    throw new TypeError(
+      `Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`,
+    );
+  } else {
+    return O;
+  }
+};
 
 /**
  * Marks the provided object as non·extensible and marks all its
@@ -438,7 +237,79 @@ export const {
  *
  * ※ This is effectively an alias for `Object.freeze`.
  */
-export const freeze = createArrowFunction(Object.freeze);
+export const freeze = (O) => objectFreeze(O);
+
+/**
+ * Returns a new frozen shallow copy of the enumerable own properties
+ * of the provided object, according to the following rules :—
+ *
+ * - For data properties, create a nonconfigurable, nonwritable
+ *   property with the same value.
+ *
+ * - For accessor properties, create a nonconfigurable accessor
+ *   property with the same getter *and* setter.
+ *
+ * The prototype for the resulting object will be taken from the
+ * `.prototype` property of the provided constructor, or the
+ * `.prototype` of the `.constructor` of the provided object if the
+ * provided constructor is undefined. If the used constructor has a
+ * nonnullish `.[Symbol.species]`, that will be used instead. If the
+ * used constructor or species is nullish or does not have a
+ * `.prototype` property, the prototype is set to null.
+ *
+ * ※ The prototype of the provided object itself is ignored.
+ */
+export const frozenCopy = (O, constructor = O?.constructor) => {
+  if (O == null) {
+    // O is null or undefined.
+    throw new TypeError(
+      "Piscēs: Cannot copy properties of null or undefined.",
+    );
+  } else {
+    // O is not null or undefined.
+    //
+    // (If not provided, the constructor will be the value of getting
+    // the `.constructor` property of O.)
+    const species = constructor?.[SPECIES] ?? constructor;
+    const copy = create(
+      species == null || !("prototype" in species)
+        ? null
+        : species.prototype,
+    );
+    const keys = ownKeys(O);
+    for (let k = 0; k < keys.length; ++k) {
+      const P = keys[k];
+      const Desc = getOwnPropertyDescriptor(O, P);
+      if (Desc.enumerable) {
+        // P is an enumerable property.
+        defineProperty(
+          copy,
+          P,
+          assign(
+            create(null),
+            isAccessorDescriptor(Desc)
+              ? {
+                configurable: false,
+                enumerable: true,
+                get: Desc.get,
+                set: Desc.set,
+              }
+              : {
+                configurable: false,
+                enumerable: true,
+                value: Desc.value,
+                writable: false,
+              },
+          ),
+        );
+      } else {
+        // P is not an enumerable property.
+        /* do nothing */
+      }
+    }
+    return objectPreventExtensions(copy);
+  }
+};
 
 /**
  * Returns the function on the provided value at the provided property
@@ -448,7 +319,7 @@ export const freeze = createArrowFunction(Object.freeze);
  * associated value which is callable.
  */
 export const getMethod = (V, P) => {
-  const func = getPropertyValue(V, P);
+  const func = V[P];
   if (func == null) {
     return undefined;
   } else if (typeof func !== "function") {
@@ -458,51 +329,107 @@ export const getMethod = (V, P) => {
   }
 };
 
+/**
+ * Returns the property descriptor record for the own property with the
+ * provided property key on the provided value, or null if none exists.
+ *
+ * ※ This is effectively an alias for
+ * `Object.getOwnPropertyDescriptor`, but the return value is a proxied
+ * object with null prototype.
+ */
+export const getOwnPropertyDescriptor = (O, P) => {
+  const desc = objectGetOwnPropertyDescriptor(O, P);
+  return desc === UNDEFINED ? UNDEFINED : toPropertyDescriptor(desc);
+};
+
+/**
+ * Returns the property descriptors for the own properties on the
+ * provided value.
+ *
+ * ※ This is effectively an alias for
+ * `Object.getOwnPropertyDescriptors`, but the values on the resulting
+ * object are proxied objects with null prototypes.
+ */
+export const getOwnPropertyDescriptors = (O) => {
+  const obj = toObject(O);
+  const keys = ownKeys(obj);
+  const descriptors = {};
+  for (let k = 0; k < keys.length; ++k) {
+    const key = keys[k];
+    defineOwnDataProperty(
+      descriptors,
+      key,
+      getOwnPropertyDescriptor(O, key),
+    );
+  }
+  return descriptors;
+};
+
+/**
+ * Returns an array of property keys on the provided value.
+ *
+ * ※ This is effectively an alias for `Reflect.ownKeys`, except that
+ * it does not require that the argument be an object.
+ */
+export const getOwnPropertyKeys = (O) => ownKeys(toObject(O));
+
 /**
  * Returns an array of string‐valued own property keys on the
- * provided object.
+ * provided value.
  *
  * ☡ This includes both enumerable and non·enumerable properties.
  *
  * ※ This is effectively an alias for `Object.getOwnPropertyNames`.
  */
-export const getOwnPropertyStrings = createArrowFunction(
-  Object.getOwnPropertyNames,
-  { name: "getOwnPropertyStrings" },
-);
+export const getOwnPropertyStrings = (O) => getOwnPropertyNames(O);
 
 /**
  * Returns an array of symbol‐valued own property keys on the
- * provided object.
+ * provided value.
  *
  * ☡ This includes both enumerable and non·enumerable properties.
  *
  * ※ This is effectively an alias for
  * `Object.getOwnPropertySymbols`.
  */
-export const getOwnPropertySymbols = createArrowFunction(
-  Object.getOwnPropertySymbols,
-);
+export const getOwnPropertySymbols = (O) =>
+  objectGetOwnPropertySymbols(O);
+
+/**
+ * Returns the value of the provided property key on the provided
+ * value.
+ *
+ * ※ This is effectively an alias for `Reflect.get`, except that it
+ * does not require that the argument be an object.
+ */
+export const getPropertyValue = (O, P, Receiver = O) =>
+  get(toObject(O), P, Receiver);
 
 /**
- * Returns the prototype of the provided object.
+ * Returns the prototype of the provided value.
  *
  * ※ This is effectively an alias for `Object.getPrototypeOf`.
  */
-export const getPrototype = createArrowFunction(
-  Object.getPrototypeOf,
-  { name: "getPrototype" },
-);
+export const getPrototype = (O) => getPrototypeOf(O);
 
 /**
- * Returns whether the provided object has an own property with the
+ * Returns whether the provided value has an own property with the
  * provided property key.
  *
  * ※ This is effectively an alias for `Object.hasOwn`.
  */
-export const hasOwnProperty = createArrowFunction(Object.hasOwn, {
-  name: "hasOwnProperty",
-});
+export const hasOwnProperty = (O, P) => hasOwn(O, P);
+
+/**
+ * Returns whether the provided property key exists on the provided
+ * value.
+ *
+ * ※ This is effectively an alias for `Reflect.has`, except that it
+ * does not require that the argument be an object.
+ *
+ * ※ This includes properties present on the prototype chain.
+ */
+export const hasProperty = (O, P) => has(toObject(O), P);
 
 /** Returns whether the provided value is an arraylike object. */
 export const isArraylikeObject = ($) => {
@@ -518,43 +445,50 @@ export const isArraylikeObject = ($) => {
   }
 };
 
-export const {
-  /**
-   * Returns whether the provided value is spreadable during array
-   * concatenation.
-   *
-   * This is also used to determine which things should be treated as
-   * collections.
-   */
-  isConcatSpreadableObject,
-} = (() => {
-  const { isArray } = Array;
-
-  return {
-    isConcatSpreadableObject: ($) => {
-      if (type($) !== "object") {
-        // The provided value is not an object.
-        return false;
-      } else {
-        // The provided value is an object.
-        const spreadable = $[IS_CONCAT_SPREADABLE];
-        return spreadable !== undefined ? !!spreadable : isArray($);
-      }
-    },
-  };
-})();
+/**
+ * Returns whether the provided value is spreadable during array
+ * concatenation.
+ *
+ * This is also used to determine which things should be treated as
+ * collections.
+ */
+export const isConcatSpreadableObject = ($) => {
+  if (type($) !== "object") {
+    // The provided value is not an object.
+    return false;
+  } else {
+    // The provided value is an object.
+    const spreadable = $[IS_CONCAT_SPREADABLE];
+    return spreadable !== UNDEFINED ? !!spreadable : isArray($);
+  }
+};
 
 /**
- * Returns whether the provided object is extensible.
+ * Returns whether the provided value is an extensible object.
  *
  * ※ This function returns false for nonobjects.
  *
  * ※ This is effectively an alias for `Object.isExtensible`.
  */
-export const isExtensibleObject = createArrowFunction(
-  Object.isExtensible,
-  { name: "isExtensibleObject" },
-);
+export const isExtensibleObject = (O) => isExtensible(O);
+
+/**
+ * Returns whether the provided value is an unfrozen object.
+ *
+ * ※ This function returns false for nonobjects.
+ *
+ * ※ This is effectively an alias for `!Object.isFrozen`.
+ */
+export const isUnfrozenObject = (O) => !isFrozen(O);
+
+/**
+ * Returns whether the provided value is an unsealed object.
+ *
+ * ※ This function returns false for nonobjects.
+ *
+ * ※ This is effectively an alias for `!Object.isSealed`.
+ */
+export const isUnsealedObject = (O) => !isSealed(O);
 
 /**
  * Returns the length of the provided arraylike value.
@@ -568,33 +502,27 @@ export const lengthOfArraylike = ({ length }) => toLength(length);
 
 /**
  * Returns an array of key~value pairs for the enumerable,
- * string‐valued property keys on the provided object.
+ * string‐valued property keys on the provided value.
  *
  * ※ This is effectively an alias for `Object.entries`.
  */
-export const namedEntries = createArrowFunction(Object.entries, {
-  name: "namedEntries",
-});
+export const namedEntries = (O) => entries(O);
 
 /**
  * Returns an array of the enumerable, string‐valued property keys on
- * the provided object.
+ * the provided value.
  *
  * ※ This is effectively an alias for `Object.keys`.
  */
-export const namedKeys = createArrowFunction(Object.keys, {
-  name: "namedKeys",
-});
+export const namedKeys = (O) => keys(O);
 
 /**
  * Returns an array of property values for the enumerable,
- * string‐valued property keys on the provided object.
+ * string‐valued property keys on the provided value.
  *
  * ※ This is effectively an alias for `Object.values`.
  */
-export const namedValues = createArrowFunction(Object.values, {
-  name: "namedValues",
-});
+export const namedValues = (O) => values(O);
 
 /**
  * Returns a new object with the provided prototype and property
@@ -602,19 +530,15 @@ export const namedValues = createArrowFunction(Object.values, {
  *
  * ※ This is effectively an alias for `Object.create`.
  */
-export const objectCreate = createArrowFunction(Object.create, {
-  name: "objectCreate",
-});
+export const objectCreate = (O, Properties) => create(O, Properties);
 
 /**
- * Returns a new object with the provided property keys and values.
+ * Returns a new object with property keys and values from the provided
+ * iterable value.
  *
  * ※ This is effectively an alias for `Object.fromEntries`.
  */
-export const objectFromEntries = createArrowFunction(
-  Object.fromEntries,
-  { name: "objectFromEntries" },
-);
+export const objectFromEntries = (iterable) => fromEntries(iterable);
 
 /**
  * Marks the provided object as non·extensible, and returns the
@@ -622,9 +546,7 @@ export const objectFromEntries = createArrowFunction(
  *
  * ※ This is effectively an alias for `Object.preventExtensions`.
  */
-export const preventExtensions = createArrowFunction(
-  Object.preventExtensions,
-);
+export const preventExtensions = (O) => objectPreventExtensions(O);
 
 /**
  * Marks the provided object as non·extensible and marks all its
@@ -632,17 +554,113 @@ export const preventExtensions = createArrowFunction(
  *
  * ※ This is effectively an alias for `Object.seal`.
  */
-export const seal = createArrowFunction(Object.seal);
+export const seal = (O) => objectSeal(O);
+
+/**
+ * Sets the provided property key to the provided value on the provided
+ * object and returns the object.
+ *
+ * ※ This function differs from `Reflect.set` in that it throws if the
+ * setting is unsuccessful.
+ *
+ * ☡ This function throws if the first argument is not an object.
+ */
+export const setPropertyValue = (O, P, V, Receiver = O) => {
+  if (type(O) !== "object") {
+    throw new TypeError(
+      `Piscēs: Tried to set property but provided value was not an object: ${V}`,
+    );
+  } else if (!set(O, P, V, Receiver)) {
+    throw new TypeError(
+      `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`,
+    );
+  } else {
+    return O;
+  }
+};
 
 /**
  * Sets the values of the enumerable own properties of the provided
- * additional objects on the provided object.
+ * additional objects on the provided values.
  *
  * ※ This is effectively an alias for `Object.assign`.
  */
-export const setPropertyValues = createArrowFunction(Object.assign, {
-  name: "setPropertyValues",
-});
+export const setPropertyValues = (target, source, ...sources) => {
+  const to = toObject(target);
+  for (let i = -1; i < sources.length; ++i) {
+    // Iterate over each source and set the appropriate property
+    // values.
+    const nextSource = i === -1 ? source : sources[i];
+    if (nextSource != null) {
+      // The current source is not nullish; handle its own properties.
+      const from = toObject(nextSource);
+      const keys = ownKeys(from);
+      for (let k = 0; k < keys.length; ++k) {
+        // Iterate over each key in the current source and set it in
+        // the target object if it is enumerable.
+        const nextKey = keys[k];
+        const desc = reflectGetOwnPropertyDescriptor(from, nextKey);
+        if (desc !== UNDEFINED && desc.enumerable) {
+          // The current key is present and enumerable; set it to its
+          // corresponding value.
+          const propValue = from[nextKey];
+          to[nextKey] = propValue;
+        } else {
+          // The current key is not present or not enumerable.
+          /* do nothing */
+        }
+      }
+    } else {
+      // The current source is nullish.
+      /* do nothing */
+    }
+  }
+  return to;
+};
+
+/**
+ * Sets the prototype of the provided object to the provided value and
+ * returns the object.
+ *
+ * ※ This is effectively an alias for `Object.setPrototypeOf`, but it
+ * won’t throw when setting the prototype of a primitive to its current
+ * value.
+ */
+export const setPrototype = (O, proto) => {
+  const obj = toObject(O);
+  if (O === obj) {
+    // The provided value is an object; set its prototype normally.
+    return setPrototypeOf(O, proto);
+  } else {
+    // The provided value is not an object; attempt to set the
+    // prototype on a coerced version with extensions prevented, then
+    // return the provided value.
+    //
+    // This will throw if the given prototype does not match the
+    // existing one on the coerced object.
+    setPrototypeOf(objectPreventExtensions(obj), proto);
+    return O;
+  }
+};
+
+/**
+ * Returns the provided value converted to an object.
+ *
+ * Existing objects are returned with no modification.
+ *
+ * ☡ This function throws if its argument is null or undefined.
+ */
+export const toObject = ($) => {
+  if ($ == null) {
+    // The provided value is nullish; this is an error.
+    throw new TypeError(
+      `Piscēs: Cannot convert ${$} into an object.`,
+    );
+  } else {
+    // The provided value is not nullish; coerce it to an object.
+    return object($);
+  }
+};
 
 /**
  * Returns the property key (symbol or string) corresponding to the
index 38d333f75d273c61abbfc0e51a7fdb6b620ce560..3a0e1a73363a94d855ede03c3ed99ba7e67265f0 100644 (file)
@@ -19,6 +19,8 @@ import {
   spy,
 } from "./dev-deps.js";
 import {
+  defineOwnDataProperty,
+  defineOwnNonenumerableDataProperty,
   defineOwnProperties,
   defineOwnProperty,
   deleteOwnProperty,
@@ -309,11 +311,113 @@ describe("LazyLoader", () => {
   });
 });
 
+describe("defineOwnDataProperty", () => {
+  it("[[Call]] defines the property", () => {
+    const obj = {};
+    defineOwnDataProperty(obj, "etaoin", "success");
+    assert(Object.hasOwn(obj, "etaoin"));
+    assertStrictEquals(obj.etaoin, "success");
+  });
+
+  it("[[Call]] defines a configurable, enumerable, writable property", () => {
+    const obj = {};
+    defineOwnDataProperty(obj, "etaoin", "success");
+    assertEquals(
+      Object.getOwnPropertyDescriptor(obj, "etaoin"),
+      {
+        configurable: true,
+        enumerable: true,
+        value: "success",
+        writable: true,
+      },
+    );
+  });
+
+  it("[[Call]] returns the provided object", () => {
+    const obj = {};
+    assertStrictEquals(
+      defineOwnDataProperty(obj, "etaoin", null),
+      obj,
+    );
+  });
+
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new defineOwnDataProperty(obj, "etaoin", null));
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(defineOwnDataProperty.length, 3);
+    });
+  });
+
+  describe(".name", () => {
+    it("[[Get]] returns the correct name", () => {
+      assertStrictEquals(
+        defineOwnDataProperty.name,
+        "defineOwnDataProperty",
+      );
+    });
+  });
+});
+
+describe("defineOwnNonenumerableDataProperty", () => {
+  it("[[Call]] defines the property", () => {
+    const obj = {};
+    defineOwnNonenumerableDataProperty(obj, "etaoin", "success");
+    assert(Object.hasOwn(obj, "etaoin"));
+    assertStrictEquals(obj.etaoin, "success");
+  });
+
+  it("[[Call]] defines a configurable, non·enumerable, writable property", () => {
+    const obj = {};
+    defineOwnNonenumerableDataProperty(obj, "etaoin", "success");
+    assertEquals(
+      Object.getOwnPropertyDescriptor(obj, "etaoin"),
+      {
+        configurable: true,
+        enumerable: false,
+        value: "success",
+        writable: true,
+      },
+    );
+  });
+
+  it("[[Call]] returns the provided object", () => {
+    const obj = {};
+    assertStrictEquals(
+      defineOwnNonenumerableDataProperty(obj, "etaoin", null),
+      obj,
+    );
+  });
+
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() =>
+      new defineOwnNonenumerableDataProperty(obj, "etaoin", null)
+    );
+  });
+
+  describe(".length", () => {
+    it("[[Get]] returns the correct length", () => {
+      assertStrictEquals(defineOwnNonenumerableDataProperty.length, 3);
+    });
+  });
+
+  describe(".name", () => {
+    it("[[Get]] returns the correct name", () => {
+      assertStrictEquals(
+        defineOwnNonenumerableDataProperty.name,
+        "defineOwnNonenumerableDataProperty",
+      );
+    });
+  });
+});
+
 describe("defineOwnProperty", () => {
   it("[[Call]] defines the property", () => {
     const obj = {};
     defineOwnProperty(obj, "etaoin", {});
-    assert("etaoin" in obj);
+    assert(Object.hasOwn(obj, "etaoin"));
   });
 
   it("[[Call]] returns the provided object", () => {
@@ -525,17 +629,45 @@ describe("frozenCopy", () => {
     );
   });
 
+  it("[[Call]] preserves data properties", () => {
+    const properties = {
+      implied: {
+        configurable: false,
+        enumerable: true,
+      },
+      writable: {
+        configurable: false,
+        enumerable: true,
+        value: "etaoin",
+        writable: true,
+      },
+      nonwritable: {
+        configurable: false,
+        enumerable: true,
+        value: "shrdlu",
+        writable: false,
+      },
+    };
+    assertEquals(
+      Object.getOwnPropertyDescriptors(
+        frozenCopy(Object.create(null, properties)),
+      ),
+      {
+        implied: {
+          ...properties.implied,
+          value: undefined,
+          writable: false,
+        },
+        writable: { ...properties.writable, writable: false },
+        nonwritable: properties.nonwritable,
+      },
+    );
+  });
+
   it("[[Call]] does not copy properties on the prototype", () => {
     assert(
       !("failure" in
-        frozenCopy(Object.create({ failure: undefined }), {
-          data: {
-            configurable: true,
-            value: undefined,
-            writable: true,
-          },
-          accessor: { configurable: true, get: undefined },
-        })),
+        frozenCopy(Object.create({ failure: undefined }))),
     );
   });
 
This page took 0.048357 seconds and 4 git commands to generate.