1 // ♓🌟 Piscēs ∷ object.js
2 // ====================================================================
4 // Copyright © 2022 Lady [@ Lady’s Computer].
6 // This Source Code Form is subject to the terms of the Mozilla Public
7 // License, v. 2.0. If a copy of the MPL was not distributed with this
8 // file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
10 import { bind
, call
} from "./function.js";
11 import { toPrimitive
, type
} from "./value.js";
14 * A property descriptor object.
16 * Actually constructing a property descriptor object using this class
17 * is only necessary if you need strict guarantees about the types of
18 * its properties; the resulting object is proxied to ensure the types
19 * match what one would expect from composing FromPropertyDescriptor
20 * and ToPropertyDescriptor in the Ecmascript specification.
22 * Otherwise, the instance properties and methods are generic.
24 export const { PropertyDescriptor
} = (() => {
25 class PropertyDescriptor
extends null {
27 * Constructs a new property descriptor object from the provided
30 * The resulting object is proxied to enforce types (for example,
31 * its `enumerable` property, if defined, will always be a
34 //deno-lint-ignore constructor-super
36 if (type(O
) !== "object") {
37 // The provided value is not an object.
39 "Piscēs: Cannot convert primitive to property descriptor.",
42 // The provided value is an object.
43 const desc
= objectCreate(propertyDescriptorPrototype
);
44 if ("enumerable" in O
) {
45 // An enumerable property is specified.
46 desc
.enumerable
= !!O
.enumerable
;
48 // An enumerable property is not specified.
51 if ("configurable" in O
) {
52 // A configurable property is specified.
53 desc
.configurable
= !!O
.configurable
;
55 // A configurable property is not specified.
59 // A value property is specified.
62 // A value property is not specified.
65 if ("writable" in O
) {
66 // A writable property is specified.
67 desc
.writable
= !!O
.writable
;
69 // A writable property is not specified.
73 // A get property is specified.
75 if (getter
!== undefined && typeof getter
!== "function") {
76 // The getter is not callable.
77 throw new TypeError("Piscēs: Getters must be callable.");
79 // The getter is callable.
83 // A get property is not specified.
87 // A set property is specified.
89 if (setter
!== undefined && typeof setter
!== "function") {
90 // The setter is not callable.
91 throw new TypeError("Piscēs: Setters must be callable.");
93 // The setter is callable.
97 // A set property is not specified.
101 ("get" in desc
|| "set" in desc
) &&
102 ("value" in desc
|| "writable" in desc
)
104 // Both accessor and data attributes have been defined.
106 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
109 // The property descriptor is valid.
110 return new Proxy(desc
, propertyDescriptorProxyHandler
);
116 * Completes this property descriptor by setting missing values to
119 * This method modifies this object and returns undefined.
122 if (this !== undefined && !("get" in this || "set" in this)) {
123 // This is a generic or data descriptor.
124 if (!("value" in this)) {
125 // `value` is not defined on this.
126 this.value
= undefined;
128 // `value` is already defined on this.
131 if (!("writable" in this)) {
132 // `writable` is not defined on this.
133 this.writable
= false;
135 // `writable` is already defined on this.
139 // This is not a generic or data descriptor.
140 if (!("get" in this)) {
141 // `get` is not defined on this.
142 this.get = undefined;
144 // `get` is already defined on this.
147 if (!("set" in this)) {
148 // `set` is not defined on this.
149 this.set = undefined;
151 // `set` is already defined on this.
155 if (!("enumerable" in this)) {
156 // `enumerable` is not defined on this.
157 this.enumerable
= false;
159 // `enumerable` is already defined on this.
162 if (!("configurable" in this)) {
163 // `configurable` is not defined on this.
164 this.configurable
= false;
166 // `configurable` is already defined on this.
171 /** Gets whether this is an accessor descrtiptor. */
172 get isAccessorDescriptor() {
173 return this !== undefined && ("get" in this || "set" in this);
176 /** Gets whether this is a data descrtiptor. */
177 get isDataDescriptor() {
178 return this !== undefined &&
179 ("value" in this || "writable" in this);
182 /** Gets whether this is a fully‐populated property descriptor. */
183 get isFullyPopulated() {
184 return this !== undefined &&
185 ("value" in this && "writable" in this ||
186 "get" in this && "set" in this) &&
187 "enumerable" in this && "configurable" in this;
191 * Gets whether this is a generic (not accessor or data)
194 get isGenericDescriptor() {
195 return this !== undefined &&
196 !("get" in this || "set" in this || "value" in this ||
201 const coercePropertyDescriptorValue
= (P
, V
) => {
210 if (V
!== undefined && typeof V
!== "function") {
212 "Piscēs: Getters must be callable.",
218 if (V
!== undefined && typeof V
!== "function") {
220 "Piscēs: Setters must be callable.",
231 prototype: propertyDescriptorPrototype
,
232 } = PropertyDescriptor
;
234 const propertyDescriptorProxyHandler
= Object
.assign(
237 defineProperty(O
, P
, Desc
) {
239 P
=== "configurable" || P
=== "enumerable" ||
240 P
=== "writable" || P
=== "value" ||
241 P
=== "get" || P
=== "set"
243 // P is a property descriptor attribute.
244 const desc
= new PropertyDescriptor(Desc
);
245 if ("get" in desc
|| "set" in desc
) {
246 // Desc is an accessor property descriptor.
248 "Piscēs: Property descriptor attributes must be data properties.",
250 } else if ("value" in desc
|| !(P
in O
)) {
251 // Desc has a value or P does not already exist on O.
252 desc
.value
= coercePropertyDescriptorValue(P
, desc
.value
);
254 // Desc is not an accessor property descriptor and has no
258 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
259 "get" in O
|| "set" in O
;
260 const isDataDescriptor
= "value" === P
|| "writable" === P
||
261 "value" in O
|| "writable" in O
;
262 if (isAccessorDescriptor
&& isDataDescriptor
) {
263 // Both accessor and data attributes will be present on O
266 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
269 // P can be safely defined on O.
270 return defineOwnProperty(O
, P
, desc
);
273 // P is not a property descriptor attribute.
274 return defineOwnProperty(O
, P
, Desc
);
277 set(O
, P
, V
, Receiver
) {
279 P
=== "configurable" || P
=== "enumerable" ||
280 P
=== "writable" || P
=== "value" ||
281 P
=== "get" || P
=== "set"
283 // P is a property descriptor attribute.
284 const newValue
= coercePropertyDescriptorValue(P
, V
);
285 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
286 "get" in O
|| "set" in O
;
287 const isDataDescriptor
= "value" === P
|| "writable" === P
||
288 "value" in O
|| "writable" in O
;
289 if (isAccessorDescriptor
&& isDataDescriptor
) {
290 // Both accessor and data attributes will be present on O
293 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
296 // P can be safely defined on O.
298 // ☡ Receiver will be the *proxied* object, so passing it
299 // through to setPropertyValue here would produce an
302 // ☡ This has implications on objects with a proxied
303 // PropertyDescriptor in their prototype.
304 return setPropertyValue(O
, P
, newValue
, O
);
307 return setPropertyValue(O
, P
, V
, Receiver
);
310 setPrototypeOf(O
, V
) {
311 if (V
!== propertyDescriptorPrototype
) {
312 // V is not the property descriptor prototype.
315 // V is the property descriptor prototype.
316 return setPrototype(O
, V
);
322 return { PropertyDescriptor
};
327 * Defines own properties on the provided object using the
328 * descriptors on the enumerable own properties of the provided
329 * additional objects.
331 * ※ This differs from Object.defineProperties in that it can take
332 * multiple source objects.
336 const { defineProperties
} = Object
;
337 const { forEach
: arrayForEach
} = Array
.prototype;
339 defineOwnProperties
: (O
, ...sources
) => {
343 [(source
) => defineProperties(O
, source
)],
352 * Defines an own property on the provided object on the provided
353 * property key using the provided property descriptor.
355 * ※ This is an alias for Object.defineProperty.
357 defineProperty
: defineOwnProperty
,
360 * Marks the provided object as non·extensible and marks all its
361 * properties as nonconfigurable and (if data properties)
362 * nonwritable, and returns the object.
364 * ※ This is an alias for Object.freeze.
369 * Returns the property descriptor for the own property with the
370 * provided property key on the provided object, or null if none
373 * ※ This is an alias for Object.getOwnPropertyDescriptor.
375 getOwnPropertyDescriptor
,
378 * Returns the property descriptors for the own properties on the
381 * ※ This is an alias for Object.getOwnPropertyDescriptors.
383 getOwnPropertyDescriptors
,
386 * Returns an array of string‐valued own property keys on the
389 * ☡ This includes both enumerable and non·enumerable properties.
391 * ※ This is an alias for Object.getOwnPropertyNames.
393 getOwnPropertyNames
: getOwnPropertyStrings
,
396 * Returns an array of symbol‐valued own property keys on the
399 * ☡ This includes both enumerable and non·enumerable properties.
401 * ※ This is an alias for Object.getOwnPropertySymbols.
403 getOwnPropertySymbols
,
406 * Returns the prototype of the provided object.
408 * ※ This is an alias for Object.getPrototypeOf.
410 getPrototypeOf
: getPrototype
,
413 * Returns whether the provided object has an own property with the
414 * provided property key.
416 * ※ This is an alias for Object.hasOwn.
418 hasOwn
: hasOwnProperty
,
421 * Returns whether the provided object is extensible.
423 * ※ This is an alias for Object.isExtensible.
428 * Returns whether the provided object is frozen.
430 * ※ This is an alias for Object.isFrozen.
435 * Returns whether the provided object is sealed.
437 * ※ This is an alias for Object.isSealed.
442 * Returns an array of key~value pairs for the enumerable,
443 * string‐valued property keys on the provided object.
445 * ※ This is an alias for Object.entries.
447 entries
: namedEntries
,
450 * Returns an array of the enumerable, string‐valued property keys on
451 * the provided object.
453 * ※ This is an alias for Object.keys.
458 * Returns an array of property values for the enumerable,
459 * string‐valued property keys on the provided object.
461 * ※ This is an alias for Object.values.
466 * Returns a new object with the provided prototype and property
469 * ※ This is an alias for Object.create.
471 create
: objectCreate
,
474 * Returns a new object with the provided property keys and values.
476 * ※ This is an alias for Object.fromEntries.
478 fromEntries
: objectFromEntries
,
481 * Marks the provided object as non·extensible, and returns the
484 * ※ This is an alias for Object.preventExtensions.
489 * Marks the provided object as non·extensible and marks all its
490 * properties as nonconfigurable, and returns the object.
492 * ※ This is an alias for Object.seal.
497 * Sets the values of the enumerable own properties of the provided
498 * additional objects on the provided object.
500 * ※ This is an alias for Object.assign.
502 assign
: setPropertyValues
,
505 * Sets the prototype of the provided object to the provided value
506 * and returns the object.
508 * ※ This is an alias for Object.setPrototypeOf.
510 setPrototypeOf
: setPrototype
,
515 * Removes the provided property key from the provided object and
516 * returns the object.
518 * ☡ This function differs from Reflect.deleteProperty and the
519 * `delete` operator in that it throws if the deletion is
525 * Sets the provided property key to the provided value on the
526 * provided object and returns the object.
528 * ※ This function differs from Reflect.set in that it throws if the
529 * setting is unsuccessful.
533 const { deleteProperty
, set } = Reflect
;
536 deleteOwnProperty
: (O
, P
) => {
537 if (!deleteProperty(O
, P
)) {
539 `Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`,
545 setPropertyValue
: (O
, P
, V
, Receiver
= O
) => {
546 if (!set(O
, P
, V
, Receiver
)) {
548 `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`,
559 * Returns a new frozen shallow copy of the enumerable own properties
560 * of the provided object, according to the following rules :—
562 * - For data properties, create a nonconfigurable, nonwritable
563 * property with the same value.
565 * - For accessor properties, create a nonconfigurable accessor
566 * property with the same getter *and* setter.
568 * The prototype for the resulting object will be taken from the
569 * `prototype` property of the provided constructor, or the
570 * `prototype` of the `constructor` of the provided object if the
571 * provided constructor is undefined. If the used constructor has a
572 * nonnullish `Symbol.species`, that will be used instead. If the
573 * used constructor or species is nullish or does not have a
574 * `prototype` property, the prototype is set to null.
579 iterator
: iteratorSymbol
,
580 species
: speciesSymbol
,
583 next
: generatorIteratorNext
,
584 } = getPrototype(function* () {}.prototype);
585 const propertyDescriptorEntryIterablePrototype
= {
588 next
: bind(generatorIteratorNext
, this.generator(), []),
593 frozenCopy
: (O
, constructor = O
?.constructor) => {
595 // O is null or undefined.
597 "Piscēs: Cannot copy properties of null or undefined.",
600 // O is not null or undefined.
602 // (If not provided, the constructor will be the value of
603 // getting the `constructor` property of O.)
604 const species
= constructor?.[speciesSymbol
] ?? constructor;
605 return preventExtensions(
607 species
== null || !("prototype" in species
)
612 propertyDescriptorEntryIterablePrototype
,
615 value
: function* () {
616 const ownPropertyKeys
= getOwnPropertyKeys(O
);
619 i
< ownPropertyKeys
.length
;
622 const P
= ownPropertyKeys
[i
];
623 const Desc
= getOwnPropertyDescriptor(O
, P
);
624 if (Desc
.enumerable
) {
625 // P is an enumerable property.
628 "get" in Desc
|| "set" in Desc
643 // P is not an enumerable property.
661 * Returns an array of property keys on the provided object.
663 * ※ This is an alias for Reflect.ownKeys.
665 ownKeys
: getOwnPropertyKeys
,
668 * Returns the value of the provided property key on the provided
671 * ※ This is an alias for Reflect.get.
673 get: getPropertyValue
,
676 * Returns whether the provided property key exists on the provided
679 * ※ This is an alias for Reflect.has.
681 * ※ This includes properties present on the prototype chain.
687 * Returns the provided value converted to an object.
689 * Null and undefined are converted to a new, empty object. Other
690 * primitives are wrapped. Existing objects are returned with no
693 * ※ This is effectively a nonconstructible version of the Object
696 export const { toObject
} = (() => {
697 const makeObject
= Object
;
698 return { toObject
: ($) => makeObject($) };
702 * Returns the property key (symbol or string) corresponding to the
705 export const toPropertyKey
= ($) => {
706 const key
= toPrimitive($, "string");
707 return typeof key
== "symbol" ? key
: `${key}`;