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/>.
11 * A property descriptor object.
13 * Actually constructing a property descriptor object using this class
14 * is only necessary if you need strict guarantees about the types of
15 * its properties; the resulting object is proxied to ensure the types
16 * match what one would expect from composing FromPropertyDescriptor
17 * and ToPropertyDescriptor in the Ecmascript specification.
19 * Otherwise, the instance properties and methods are generic.
21 export const PropertyDescriptor
= (() => {
22 class PropertyDescriptor
extends null {
24 * Constructs a new property descriptor object from the provided
27 * The resulting object is proxied to enforce types (for example,
28 * its `enumerable` property, if defined, will always be a
31 //deno-lint-ignore constructor-super
34 // The provided value is not an object.
36 "Piscēs: Cannot convert primitive to property descriptor.",
39 // The provided value is an object.
40 const desc
= Object
.create(propertyDescriptorPrototype
);
41 if ("enumerable" in Obj
) {
42 // An enumerable property is specified.
43 desc
.enumerable
= !!Obj
.enumerable
;
45 // An enumerable property is not specified.
48 if ("configurable" in Obj
) {
49 // A configurable property is specified.
50 desc
.configurable
= !!Obj
.configurable
;
52 // A configurable property is not specified.
56 // A value property is specified.
57 desc
.value
= Obj
.value
;
59 // A value property is not specified.
62 if ("writable" in Obj
) {
63 // A writable property is specified.
64 desc
.writable
= !!Obj
.writable
;
66 // A writable property is not specified.
70 // A get property is specified.
71 const getter
= Obj
.get;
72 if (typeof getter
!= "function") {
73 // The getter is not callable.
74 throw new TypeError("Piscēs: Getters must be callable.");
76 // The getter is callable.
80 // A get property is not specified.
84 // A set property is specified.
85 const setter
= Obj
.set;
86 if (typeof setter
!= "function") {
87 // The setter is not callable.
88 throw new TypeError("Piscēs: Setters must be callable.");
90 // The setter is callable.
94 // A set property is not specified.
98 ("get" in desc
|| "set" in desc
) &&
99 ("value" in desc
|| "writable" in desc
)
101 // Both accessor and data attributes have been defined.
103 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
106 // The property descriptor is valid.
107 return new Proxy(desc
, propertyDescriptorProxyHandler
);
113 * Completes this property descriptor by setting missing values to
116 * This method modifies this object and returns undefined.
119 if (this !== undefined && !("get" in this || "set" in this)) {
120 // This is a generic or data descriptor.
121 if (!("value" in this)) {
122 // `value` is not defined on this.
123 this.value
= undefined;
125 // `value` is already defined on this.
128 if (!("writable" in this)) {
129 // `writable` is not defined on this.
130 this.writable
= false;
132 // `writable` is already defined on this.
136 // This is not a generic or data descriptor.
137 if (!("get" in this)) {
138 // `get` is not defined on this.
139 this.get = undefined;
141 // `get` is already defined on this.
144 if (!("set" in this)) {
145 // `set` is not defined on this.
146 this.set = undefined;
148 // `set` is already defined on this.
152 if (!("enumerable" in this)) {
153 // `enumerable` is not defined on this.
154 this.enumerable
= false;
156 // `enumerable` is already defined on this.
159 if (!("configurable" in this)) {
160 // `configurable` is not defined on this.
161 this.configurable
= false;
163 // `configurable` is already defined on this.
168 /** Returns whether this is an accessor descrtiptor. */
169 get isAccessorDescriptor() {
170 return this !== undefined && ("get" in this || "set" in this);
173 /** Returns whether this is a data descrtiptor. */
174 get isDataDescriptor() {
175 return this !== undefined &&
176 ("value" in this || "writable" in this);
180 * Returns whether this is a fully‐populated property descriptor.
182 get isFullyPopulated() {
183 return this !== undefined &&
184 ("value" in this && "writable" in this ||
185 "get" in this && "set" in this) &&
186 "enumerable" in this && "configurable" in this;
190 * Returns whether this is a generic (not accessor or data)
193 get isGenericDescriptor() {
194 return this !== undefined &&
195 !("get" in this || "set" in this || "value" in this ||
200 const coercePropretyDescriptorValue
= (P
, V
) => {
209 if (typeof V
!= "function") {
211 "Piscēs: Getters must be callable.",
217 if (typeof V
!= "function") {
219 "Piscēs: Setters must be callable.",
229 const propertyDescriptorPrototype
= PropertyDescriptor
.prototype;
231 const propertyDescriptorProxyHandler
= Object
.assign(
234 defineProperty(O
, P
, Desc
) {
236 P
=== "configurable" || P
=== "enumerable" ||
237 P
=== "writable" || P
=== "value" ||
238 P
=== "get" || P
=== "set"
240 // P is a property descriptor attribute.
241 const desc
= new PropertyDescriptor(Desc
);
242 if ("get" in desc
|| "set" in desc
) {
243 // Desc is an accessor property descriptor.
245 "Piscēs: Property descriptor attributes must be data properties.",
247 } else if ("value" in desc
) {
249 desc
.value
= coercePropretyDescriptorValue(P
, desc
.value
);
251 // Desc is not an accessor property descriptor and has no
255 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
256 "get" in O
|| "set" in O
;
257 const isDataDescriptor
= "value" === P
|| "writable" === P
||
258 "value" in O
|| "writable" in O
;
259 if (isAccessorDescriptor
&& isDataDescriptor
) {
260 // Both accessor and data attributes will be present on O
263 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
266 // P can be safely defined on O.
267 return Reflect
.defineProperty(O
, P
, desc
);
270 // P is not a property descriptor attribute.
271 return Reflect
.defineProperty(O
, P
, Desc
);
274 set(O
, P
, V
, Receiver
) {
275 const newValue
= coercePropertyDescriptorValue(P
, V
);
276 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
277 "get" in O
|| "set" in O
;
278 const isDataDescriptor
= "value" === P
|| "writable" === P
||
279 "value" in O
|| "writable" in O
;
280 if (isAccessorDescriptor
&& isDataDescriptor
) {
281 // Both accessor and data attributes will be present on O
284 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
287 // P can be safely defined on O.
288 return Reflect
.set(O
, prop
, newValue
, Receiver
);
291 setPrototypeOf(O
, V
) {
292 if (V
!== propertyDescriptorPrototype
) {
293 // V is not the property descriptor prototype.
296 // V is the property descriptor prototype.
297 return Reflect
.setPrototypeOf(O
, V
);
303 return PropertyDescriptor
;
307 * Returns a new frozen shallow copy of the enumerable own properties
308 * of the provided object, according to the following rules :—
310 * - For data properties, create a nonconfigurable, nonwritable
311 * property with the same value.
313 * - For accessor properties, create a nonconfigurable accessor
314 * property with the same getter *and* setter.
316 * The prototype for the resulting object will be taken from the
317 * `prototype` property of the provided constructor, or the `prototype`
318 * of the `constructor` of the provided object if the provided
319 * constructor is undefined. If the used constructor has a nonnullish
320 * `Symbol.species`, that will be used instead.
322 export const frozenCopy
= (O
, constructor = O
?.constructor) => {
324 // O is null or undefined.
326 "Piscēs: Cannot copy properties of null or undefined.",
329 // O is not null or undefined.
331 // (If not provided, the constructor will be the value of getting
332 // the `constructor` property of O.)
333 const species
= constructor?.[Symbol
.species
] ?? constructor;
334 return Object
.preventExtensions(
336 species
== null || !("prototype" in species
)
341 for (const P
of Reflect
.ownKeys(O
)) {
342 const Desc
= Object
.getOwnPropertyDescriptor(O
, P
);
343 if (Desc
.enumerable
) {
344 // P is an enumerable property.
347 "get" in Desc
|| "set" in Desc
362 // P is not an enumerable property.
373 /** Returns whether the provided value is a constructor. */
374 export const isConstructor
= ($) => {
376 // The provided value is not an object.
379 // The provided value is an object.
385 ); // will throw if $ is not a constructor
393 /** Returns whether the provided value is an object. */
394 export const isObject
= ($) => {
396 (typeof $ == "function" || typeof $ == "object");
400 * Returns whether the provided object inherits from the prototype of
401 * the provided function.
403 export const ordinaryHasInstance
= Function
.prototype.call
.bind(
404 Function
.prototype[Symbol
.hasInstance
],
408 * Returns the primitive value of the provided object per its
409 * `toString` and `valueOf` methods.
411 * If the provided hint is "string", then `toString` takes precedence;
412 * otherwise, `valueOf` does.
414 * Throws an error if both of these methods are not callable or do not
415 * return a primitive.
417 export const ordinaryToPrimitive
= (O
, hint
) => {
419 const name
of hint
== "string"
420 ? ["toString", "valueOf"]
421 : ["valueOf", "toString"]
423 const method
= O
[name
];
424 if (typeof method
== "function") {
425 // Method is callable.
426 const result
= method
.call(O
);
427 if (!isObject(result
)) {
428 // Method returns a primitive.
431 // Method returns an object.
435 // Method is not callable.
439 throw new TypeError("Piscēs: Unable to convert object to primitive");
443 * Returns the provided value converted to a primitive, or throws if
444 * no such conversion is possible.
446 * The provided preferred type, if specified, should be "string",
447 * "number", or "default". If the provided input has a
448 * `Symbol.toPrimitive` method, this function will throw rather than
449 * calling that method with a preferred type other than one of the
452 export const toPrimitive
= ($, preferredType
) => {
454 // The provided value is an object.
455 const exoticToPrim
= $[Symbol
.toPrimitive
] ?? undefined;
456 if (exoticToPrim
!== undefined) {
457 // The provided value has an exotic primitive conversion method.
458 if (typeof exoticToPrim
!= "function") {
459 // The method is not callable.
461 "Piscēs: Symbol.toPrimitive was neither nullish nor callable.",
464 // The method is callable.
465 const hint
= `${preferredType ?? "default"}`;
466 if (!["default", "string", "number"].includes(hint
)) {
467 // An invalid preferred type was specified.
469 `Piscēs: Invalid preferred type: ${preferredType}.`,
472 // The resulting hint is either default, string, or number.
473 return exoticToPrim
.call($, hint
);
477 // Use the ordinary primitive conversion function.
478 ordinaryToPrimitive($, hint
);
481 // The provided value is already a primitive.
487 * Returns the property key (symbol or string) corresponding to the
490 export const toPropertyKey
= ($) => {
491 const key
= toPrimitive($, "string");
492 return typeof key
== "symbol" ? key
: `${key}`;