X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/f6b84c13c5c539dbac4f4b9b530eeec95d683475..4e463d42f4a4f1cf4b4ed3b7cdb79ca7264e66e2:/object.js?ds=inline diff --git a/object.js b/object.js index aa94bc5..3221e1d 100644 --- a/object.js +++ b/object.js @@ -10,15 +10,15 @@ 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}`; -};