X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/37c295ffa9d5a062fe15d78427e2a7096a416bb3..50ab30fc3a257e46da6b8bdc0dad054f729e16e1:/value.js diff --git a/value.js b/value.js index 6471ea3..1039a3c 100644 --- a/value.js +++ b/value.js @@ -173,6 +173,343 @@ export const POSITIVE_ZERO = 0; /** The undefined primitive. */ export const UNDEFINED = undefined; +/** + * Completes the provided property descriptor by setting missing values + * to their defaults. + * + * ※ This method modifies the provided object and returns undefined. + */ +export const completePropertyDescriptor = (Desc) => { + if (Desc === UNDEFINED) { + throw new TypeError( + "Piscēs: Cannot complete undefined property descriptor.", + ); + } else if (!("get" in Desc || "set" in Desc)) { + // This is a generic or data descriptor. + if (!("value" in Desc)) { + // `value` is not defined on this. + Desc.value = UNDEFINED; + } else { + // `value` is already defined on this. + /* do nothing */ + } + if (!("writable" in Desc)) { + // `writable` is not defined on this. + Desc.writable = false; + } else { + // `writable` is already defined on this. + /* do nothing */ + } + } else { + // This is not a generic or data descriptor. + if (!("get" in Desc)) { + // `get` is not defined on this. + Desc.get = UNDEFINED; + } else { + // `get` is already defined on this. + /* do nothing */ + } + if (!("set" in Desc)) { + // `set` is not defined on this. + Desc.set = UNDEFINED; + } else { + // `set` is already defined on this. + /* do nothing */ + } + } + if (!("enumerable" in Desc)) { + // `enumerable` is not defined on this. + Desc.enumerable = false; + } else { + // `enumerable` is already defined on this. + /* do nothing */ + } + if (!("configurable" in Desc)) { + // `configurable` is not defined on this. + Desc.configurable = false; + } else { + // `configurable` is already defined on this. + /* do nothing */ + } +}; + +/** Gets whether the provided value is an accessor descrtiptor. */ +export const isAccessorDescriptor = (Desc) => + Desc !== UNDEFINED && ("get" in Desc || "set" in Desc); + +/** Gets whether the provided value is a data descrtiptor. */ +export const isDataDescriptor = (Desc) => + Desc !== UNDEFINED && ("value" in Desc || "writable" in Desc); + +/** + * Gets whether the provided value is a fully‐populated property + * descriptor. + */ +export const isFullyPopulatedDescriptor = (Desc) => + Desc !== UNDEFINED && + ("value" in Desc && "writable" in Desc || + "get" in Desc && "set" in Desc) && + "enumerable" in Desc && "configurable" in Desc; + +/** + * Gets whether the provided value is a generic (not accessor or data) + * descrtiptor. + */ +export const isGenericDescriptor = (Desc) => + Desc !== UNDEFINED && + !("get" in Desc || "set" in Desc || "value" in Desc || + "writable" in Desc); + +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. + */ + toPropertyDescriptor, +} = (() => { + const { + assign: setPropertyValues, + create: objectCreate, + defineProperty: defineOwnProperty, + } = Object; + const { + apply: call, + defineProperty: reflectDefineProperty, + setPrototypeOf: reflectSetPrototypeOf, + } = Reflect; + 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 = Object.freeze( + Object.assign( + Object.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 = setPropertyValues(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, [$]), + toPropertyDescriptor: (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 = objectCreate(null); + if ("enumerable" in Obj) { + // An enumerable property is specified. + defineOwnProperty(desc, "enumerable", { + configurable: true, + enumerable: true, + value: !!Obj.enumerable, + writable: true, + }); + } else { + // An enumerable property is not specified. + /* do nothing */ + } + if ("configurable" in Obj) { + // A configurable property is specified. + defineOwnProperty(desc, "configurable", { + configurable: true, + enumerable: true, + value: !!Obj.configurable, + writable: true, + }); + } else { + // A configurable property is not specified. + /* do nothing */ + } + if ("value" in Obj) { + // A value property is specified. + defineOwnProperty(desc, "value", { + configurable: true, + enumerable: true, + value: Obj.value, + writable: true, + }); + } else { + // A value property is not specified. + /* do nothing */ + } + if ("writable" in Obj) { + // A writable property is specified. + defineOwnProperty(desc, "writable", { + configurable: true, + enumerable: true, + value: !!Obj.writable, + writable: true, + }); + } 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. + defineOwnProperty(desc, "get", { + configurable: true, + enumerable: true, + value: getter, + writable: true, + }); + } + } 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. + defineOwnProperty(desc, "set", { + configurable: true, + enumerable: true, + value: setter, + writable: true, + }); + } + } 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; + } + } + }, + }; +})(); + export const { /** * Returns the primitive value of the provided object per its