]> Lady’s Gitweb - Pisces/blobdiff - object.js
Add methods for own entries and values to object.js
[Pisces] / object.js
index aa94bc571c469f3125b4d701dc07bacd6220ccdb..3221e1da360305d4da5fa5582355cee6cf896e03 100644 (file)
--- a/object.js
+++ b/object.js
 import {
   IS_CONCAT_SPREADABLE,
   isAccessorDescriptor,
+  isDataDescriptor,
   SPECIES,
   toFunctionName,
   toLength,
-  toPrimitive,
-  toPropertyDescriptor,
   type,
   UNDEFINED,
 } from "./value.js";
 
+const createArray = Array;
 const { isArray } = Array;
 const object = Object;
 const {
@@ -46,11 +46,13 @@ const {
 const {
   apply: call,
   deleteProperty,
+  defineProperty: reflectDefineProperty,
   get,
   getOwnPropertyDescriptor: reflectGetOwnPropertyDescriptor,
   has,
   ownKeys,
   set,
+  setPrototypeOf: reflectSetPrototypeOf,
 } = Reflect;
 
 /**
@@ -339,7 +341,9 @@ export const getMethod = (V, P) => {
  */
 export const getOwnPropertyDescriptor = (O, P) => {
   const desc = objectGetOwnPropertyDescriptor(O, P);
-  return desc === UNDEFINED ? UNDEFINED : toPropertyDescriptor(desc);
+  return desc === UNDEFINED
+    ? UNDEFINED
+    : toPropertyDescriptorRecord(desc);
 };
 
 /**
@@ -355,6 +359,8 @@ export const getOwnPropertyDescriptors = (O) => {
   const keys = ownKeys(obj);
   const descriptors = {};
   for (let k = 0; k < keys.length; ++k) {
+    // Iterate over the keys of the provided object and collect its
+    // descriptors.
     const key = keys[k];
     defineOwnDataProperty(
       descriptors,
@@ -366,7 +372,29 @@ export const getOwnPropertyDescriptors = (O) => {
 };
 
 /**
- * Returns an array of property keys on the provided value.
+ * Returns an array of own property entries on the provided value,
+ * using the provided receiver if given.
+ */
+export const getOwnPropertyEntries = (O, Receiver = O) => {
+  const obj = toObject(O);
+  const keys = ownKeys(obj);
+  const target = Receiver === UNDEFINED ? obj : toObject(Receiver);
+  const result = createArray(keys.length);
+  for (let k = 0; k < keys.length; ++k) {
+    // Iterate over each key and add the corresponding entry to the
+    // result.
+    const key = keys[k];
+    defineOwnDataProperty(
+      result,
+      k,
+      [key, getOwnPropertyValue(obj, keys[k], target)],
+    );
+  }
+  return result;
+};
+
+/**
+ * Returns an array of own 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.
@@ -395,6 +423,53 @@ export const getOwnPropertyStrings = (O) => getOwnPropertyNames(O);
 export const getOwnPropertySymbols = (O) =>
   objectGetOwnPropertySymbols(O);
 
+/**
+ * Returns the value of the provided own property on the provided
+ * value using the provided receiver, or undefined if the provided
+ * property is not an own property on the provided value.
+ *
+ * ※ If the receiver is not provided, it defaults to the provided
+ * value.
+ */
+export const getOwnPropertyValue = (O, P, Receiver = UNDEFINED) => {
+  const obj = toObject(O);
+  const desc = getOwnPropertyDescriptor(O, P);
+  if (desc === UNDEFINED) {
+    // The provided property is not an own property on the provided
+    // value; return undefined.
+    return UNDEFINED;
+  }
+  if (isDataDescriptor(desc)) {
+    // The provided property is a data property; return its value.
+    return desc.value;
+  } else {
+    // The provided property is an accessor property; get its value
+    // using the appropriate receiver.
+    const target = Receiver === UNDEFINED ? obj : toObject(Receiver);
+    return call(desc.get, target, []);
+  }
+};
+
+/**
+ * Returns an array of own property values on the provided value, using
+ * the provided receiver if given.
+ */
+export const getOwnPropertyValues = (O, Receiver = O) => {
+  const obj = toObject(O);
+  const keys = ownKeys(obj);
+  const target = Receiver === UNDEFINED ? obj : toObject(Receiver);
+  const result = createArray(keys.length);
+  for (let k = 0; k < keys.length; ++k) {
+    // Iterate over each key and collect the values.
+    defineOwnDataProperty(
+      result,
+      k,
+      getOwnPropertyValue(obj, keys[k], target),
+    );
+  }
+  return result;
+};
+
 /**
  * Returns the value of the provided property key on the provided
  * value.
@@ -472,6 +547,220 @@ export const isConcatSpreadableObject = ($) => {
  */
 export const isExtensibleObject = (O) => isExtensible(O);
 
+export const {
+  /**
+   * Returns whether the provided value is a property descriptor record
+   * as created by `toPropertyDescriptor`.
+   *
+   * ※ This function is provided to enable inspection of whether an
+   * object uses the property descriptor record proxy implementation,
+   * not as a general test of whether an object satisfies the
+   * requirements for property descriptors. In most cases, a more
+   * specific test, like `isAccessorDescriptor`, `isDataDescriptor`, or
+   * `isGenericDescriptor`, is preferrable.
+   */
+  isPropertyDescriptorRecord,
+
+  /**
+   * Converts the provided value to a property descriptor record.
+   *
+   * ※ The prototype of a property descriptor record is always `null`.
+   *
+   * ※ Actually constructing a property descriptor object using this
+   * class is only necessary if you need strict guarantees about the
+   * types of its properties; the resulting object is proxied to ensure
+   * the types match what one would expect from composing
+   * FromPropertyDescriptor and ToPropertyDescriptor in the Ecmascript
+   * specification.
+   */
+  toPropertyDescriptorRecord,
+} = (() => {
+  const proxyConstructor = Proxy;
+  const { add: weakSetAdd, has: weakSetHas } = WeakSet.prototype;
+  const propertyDescriptorRecords = new WeakSet();
+  const coercePropertyDescriptorValue = (P, V) => {
+    switch (P) {
+      case "configurable":
+      case "enumerable":
+      case "writable":
+        return !!V;
+      case "value":
+        return V;
+      case "get":
+        if (V !== undefined && typeof V !== "function") {
+          throw new TypeError(
+            "Piscēs: Getters must be callable.",
+          );
+        } else {
+          return V;
+        }
+      case "set":
+        if (V !== undefined && typeof V !== "function") {
+          throw new TypeError(
+            "Piscēs: Setters must be callable.",
+          );
+        } else {
+          return V;
+        }
+      default:
+        return V;
+    }
+  };
+  const propertyDescriptorProxyHandler = objectFreeze(
+    assign(
+      create(null),
+      {
+        defineProperty(O, P, Desc) {
+          if (
+            P === "configurable" || P === "enumerable" ||
+            P === "writable" || P === "value" ||
+            P === "get" || P === "set"
+          ) {
+            // P is a property descriptor attribute.
+            const desc = assign(objectCreate(null), Desc);
+            if ("get" in desc || "set" in desc) {
+              // Desc is an accessor property descriptor.
+              throw new TypeError(
+                "Piscēs: Property descriptor attributes must be data properties.",
+              );
+            } else if ("value" in desc || !(P in O)) {
+              // Desc has a value or P does not already exist on O.
+              desc.value = coercePropertyDescriptorValue(
+                P,
+                desc.value,
+              );
+            } else {
+              // Desc is not an accessor property descriptor and has no
+              // value, but an existing value is present on O.
+              /* do nothing */
+            }
+            const isAccessorDescriptor = "get" === P || "set" === P ||
+              "get" in O || "set" in O;
+            const isDataDescriptor = "value" === P ||
+              "writable" === P ||
+              "value" in O || "writable" in O;
+            if (isAccessorDescriptor && isDataDescriptor) {
+              // Both accessor and data attributes will be present on O
+              // after defining P.
+              throw new TypeError(
+                "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
+              );
+            } else {
+              // P can be safely defined on O.
+              return reflectDefineProperty(O, P, desc);
+            }
+          } else {
+            // P is not a property descriptor attribute.
+            return reflectDefineProperty(O, P, Desc);
+          }
+        },
+        setPrototypeOf(O, V) {
+          if (V !== null) {
+            // V is not the property descriptor prototype.
+            return false;
+          } else {
+            // V is the property descriptor prototype.
+            return reflectSetPrototypeOf(O, V);
+          }
+        },
+      },
+    ),
+  );
+
+  return {
+    isPropertyDescriptorRecord: ($) =>
+      call(weakSetHas, propertyDescriptorRecords, [$]),
+    toPropertyDescriptorRecord: (Obj) => {
+      if (type(Obj) !== "object") {
+        // The provided value is not an object.
+        throw new TypeError(
+          `Piscēs: Cannot convert primitive to property descriptor: ${O}.`,
+        );
+      } else {
+        // The provided value is an object.
+        const desc = create(null);
+        if ("enumerable" in Obj) {
+          // An enumerable property is specified.
+          defineOwnDataProperty(desc, "enumerable", !!Obj.enumerable);
+        } else {
+          // An enumerable property is not specified.
+          /* do nothing */
+        }
+        if ("configurable" in Obj) {
+          // A configurable property is specified.
+          defineOwnDataProperty(
+            desc,
+            "configurable",
+            !!Obj.configurable,
+          );
+        } else {
+          // A configurable property is not specified.
+          /* do nothing */
+        }
+        if ("value" in Obj) {
+          // A value property is specified.
+          defineOwnDataProperty(desc, "value", Obj.value);
+        } else {
+          // A value property is not specified.
+          /* do nothing */
+        }
+        if ("writable" in Obj) {
+          // A writable property is specified.
+          defineOwnDataProperty(desc, "writable", !!Obj.writable);
+        } else {
+          // A writable property is not specified.
+          /* do nothing */
+        }
+        if ("get" in Obj) {
+          // A get property is specified.
+          const getter = Obj.get;
+          if (getter !== UNDEFINED && typeof getter !== "function") {
+            // The getter is not callable.
+            throw new TypeError("Piscēs: Getters must be callable.");
+          } else {
+            // The getter is callable.
+            defineOwnDataProperty(desc, "get", Obj.get);
+          }
+        } else {
+          // A get property is not specified.
+          /* do nothing */
+        }
+        if ("set" in Obj) {
+          // A set property is specified.
+          const setter = Obj.set;
+          if (setter !== UNDEFINED && typeof setter !== "function") {
+            // The setter is not callable.
+            throw new TypeError("Piscēs: Setters must be callable.");
+          } else {
+            // The setter is callable.
+            defineOwnDataProperty(desc, "set", Obj.set);
+          }
+        } else {
+          // A set property is not specified.
+          /* do nothing */
+        }
+        if (
+          ("get" in desc || "set" in desc) &&
+          ("value" in desc || "writable" in desc)
+        ) {
+          // Both accessor and data attributes have been defined.
+          throw new TypeError(
+            "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
+          );
+        } else {
+          // The property descriptor is valid.
+          const record = new proxyConstructor(
+            desc,
+            propertyDescriptorProxyHandler,
+          );
+          call(weakSetAdd, propertyDescriptorRecords, [record]);
+          return record;
+        }
+      }
+    },
+  };
+})();
+
 /**
  * Returns whether the provided value is an unfrozen object.
  *
@@ -661,12 +950,3 @@ export const toObject = ($) => {
     return object($);
   }
 };
-
-/**
- * Returns the property key (symbol or string) corresponding to the
- * provided value.
- */
-export const toPropertyKey = ($) => {
-  const key = toPrimitive($, "string");
-  return typeof key === "symbol" ? key : `${key}`;
-};
This page took 0.063337 seconds and 4 git commands to generate.