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 { call
} from "./function.js";
13 assign
: assignProperties
,
14 defineProperty
: defineOwnProperty
,
15 defineProperties
: defineOwnProperties
,
17 getOwnPropertyDescriptor
,
18 getOwnPropertyDescriptors
,
19 getOwnPropertyNames
: getOwnPropertyStrings
,
20 getOwnPropertySymbols
,
21 getPrototypeOf
: getPrototype
,
22 hasOwn
: hasOwnProperty
,
26 entries
: namedEntries
,
30 fromEntries
: objectFromEntries
,
34 setPrototypeOf
: setPrototype
,
38 delete: deleteOwnProperty
,
39 keys
: getOwnPropertyKeys
,
40 get: getPropertyValue
,
42 set: setPropertyValue
,
46 * A property descriptor object.
48 * Actually constructing a property descriptor object using this class
49 * is only necessary if you need strict guarantees about the types of
50 * its properties; the resulting object is proxied to ensure the types
51 * match what one would expect from composing FromPropertyDescriptor
52 * and ToPropertyDescriptor in the Ecmascript specification.
54 * Otherwise, the instance properties and methods are generic.
56 export const PropertyDescriptor
= (() => {
57 class PropertyDescriptor
extends null {
59 * Constructs a new property descriptor object from the provided
62 * The resulting object is proxied to enforce types (for example,
63 * its `enumerable` property, if defined, will always be a
66 //deno-lint-ignore constructor-super
69 // The provided value is not an object.
71 "Piscēs: Cannot convert primitive to property descriptor.",
74 // The provided value is an object.
75 const desc
= objectCreate(propertyDescriptorPrototype
);
76 if ("enumerable" in Obj
) {
77 // An enumerable property is specified.
78 desc
.enumerable
= !!Obj
.enumerable
;
80 // An enumerable property is not specified.
83 if ("configurable" in Obj
) {
84 // A configurable property is specified.
85 desc
.configurable
= !!Obj
.configurable
;
87 // A configurable property is not specified.
91 // A value property is specified.
92 desc
.value
= Obj
.value
;
94 // A value property is not specified.
97 if ("writable" in Obj
) {
98 // A writable property is specified.
99 desc
.writable
= !!Obj
.writable
;
101 // A writable property is not specified.
105 // A get property is specified.
106 const getter
= Obj
.get;
107 if (typeof getter
!= "function") {
108 // The getter is not callable.
109 throw new TypeError("Piscēs: Getters must be callable.");
111 // The getter is callable.
115 // A get property is not specified.
119 // A set property is specified.
120 const setter
= Obj
.set;
121 if (typeof setter
!= "function") {
122 // The setter is not callable.
123 throw new TypeError("Piscēs: Setters must be callable.");
125 // The setter is callable.
129 // A set property is not specified.
133 ("get" in desc
|| "set" in desc
) &&
134 ("value" in desc
|| "writable" in desc
)
136 // Both accessor and data attributes have been defined.
138 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
141 // The property descriptor is valid.
142 return new Proxy(desc
, propertyDescriptorProxyHandler
);
148 * Completes this property descriptor by setting missing values to
151 * This method modifies this object and returns undefined.
154 if (this !== undefined && !("get" in this || "set" in this)) {
155 // This is a generic or data descriptor.
156 if (!("value" in this)) {
157 // `value` is not defined on this.
158 this.value
= undefined;
160 // `value` is already defined on this.
163 if (!("writable" in this)) {
164 // `writable` is not defined on this.
165 this.writable
= false;
167 // `writable` is already defined on this.
171 // This is not a generic or data descriptor.
172 if (!("get" in this)) {
173 // `get` is not defined on this.
174 this.get = undefined;
176 // `get` is already defined on this.
179 if (!("set" in this)) {
180 // `set` is not defined on this.
181 this.set = undefined;
183 // `set` is already defined on this.
187 if (!("enumerable" in this)) {
188 // `enumerable` is not defined on this.
189 this.enumerable
= false;
191 // `enumerable` is already defined on this.
194 if (!("configurable" in this)) {
195 // `configurable` is not defined on this.
196 this.configurable
= false;
198 // `configurable` is already defined on this.
203 /** Returns whether this is an accessor descrtiptor. */
204 get isAccessorDescriptor() {
205 return this !== undefined && ("get" in this || "set" in this);
208 /** Returns whether this is a data descrtiptor. */
209 get isDataDescriptor() {
210 return this !== undefined &&
211 ("value" in this || "writable" in this);
215 * Returns whether this is a fully‐populated property descriptor.
217 get isFullyPopulated() {
218 return this !== undefined &&
219 ("value" in this && "writable" in this ||
220 "get" in this && "set" in this) &&
221 "enumerable" in this && "configurable" in this;
225 * Returns whether this is a generic (not accessor or data)
228 get isGenericDescriptor() {
229 return this !== undefined &&
230 !("get" in this || "set" in this || "value" in this ||
235 const coercePropretyDescriptorValue
= (P
, V
) => {
244 if (typeof V
!= "function") {
246 "Piscēs: Getters must be callable.",
252 if (typeof V
!= "function") {
254 "Piscēs: Setters must be callable.",
264 const propertyDescriptorPrototype
= PropertyDescriptor
.prototype;
266 const propertyDescriptorProxyHandler
= assignProperties(
269 defineProperty(O
, P
, Desc
) {
271 P
=== "configurable" || P
=== "enumerable" ||
272 P
=== "writable" || P
=== "value" ||
273 P
=== "get" || P
=== "set"
275 // P is a property descriptor attribute.
276 const desc
= new PropertyDescriptor(Desc
);
277 if ("get" in desc
|| "set" in desc
) {
278 // Desc is an accessor property descriptor.
280 "Piscēs: Property descriptor attributes must be data properties.",
282 } else if ("value" in desc
) {
284 desc
.value
= coercePropretyDescriptorValue(P
, desc
.value
);
286 // Desc is not an accessor property descriptor and has no
290 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
291 "get" in O
|| "set" in O
;
292 const isDataDescriptor
= "value" === P
|| "writable" === P
||
293 "value" in O
|| "writable" in O
;
294 if (isAccessorDescriptor
&& isDataDescriptor
) {
295 // Both accessor and data attributes will be present on O
298 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
301 // P can be safely defined on O.
302 return defineOwnProperty(O
, P
, desc
);
305 // P is not a property descriptor attribute.
306 return defineOwnProperty(O
, P
, Desc
);
309 set(O
, P
, V
, Receiver
) {
310 const newValue
= coercePropertyDescriptorValue(P
, V
);
311 const isAccessorDescriptor
= "get" === P
|| "set" === P
||
312 "get" in O
|| "set" in O
;
313 const isDataDescriptor
= "value" === P
|| "writable" === P
||
314 "value" in O
|| "writable" in O
;
315 if (isAccessorDescriptor
&& isDataDescriptor
) {
316 // Both accessor and data attributes will be present on O
319 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
322 // P can be safely defined on O.
323 return setPropertyValue(O
, prop
, newValue
, Receiver
);
326 setPrototypeOf(O
, V
) {
327 if (V
!== propertyDescriptorPrototype
) {
328 // V is not the property descriptor prototype.
331 // V is the property descriptor prototype.
332 return setPrototype(O
, V
);
338 return PropertyDescriptor
;
342 * Returns a new frozen shallow copy of the enumerable own properties
343 * of the provided object, according to the following rules :—
345 * - For data properties, create a nonconfigurable, nonwritable
346 * property with the same value.
348 * - For accessor properties, create a nonconfigurable accessor
349 * property with the same getter *and* setter.
351 * The prototype for the resulting object will be taken from the
352 * `prototype` property of the provided constructor, or the `prototype`
353 * of the `constructor` of the provided object if the provided
354 * constructor is undefined. If the used constructor has a nonnullish
355 * `Symbol.species`, that will be used instead.
357 export const frozenCopy
= (O
, constructor = O
?.constructor) => {
359 // O is null or undefined.
361 "Piscēs: Cannot copy properties of null or undefined.",
364 // O is not null or undefined.
366 // (If not provided, the constructor will be the value of getting
367 // the `constructor` property of O.)
368 const species
= constructor?.[Symbol
.species
] ?? constructor;
369 return preventExtensions(
371 species
== null || !("prototype" in species
)
376 for (const P
of getOwnPropertyKeys(O
)) {
377 const Desc
= getOwnPropertyDescriptor(O
, P
);
378 if (Desc
.enumerable
) {
379 // P is an enumerable property.
382 "get" in Desc
|| "set" in Desc
397 // P is not an enumerable property.
408 /** Returns whether the provided value is an object. */
409 export const isObject
= ($) => {
411 (typeof $ == "function" || typeof $ == "object");
415 * Returns the primitive value of the provided object per its
416 * `toString` and `valueOf` methods.
418 * If the provided hint is "string", then `toString` takes precedence;
419 * otherwise, `valueOf` does.
421 * Throws an error if both of these methods are not callable or do not
422 * return a primitive.
424 export const ordinaryToPrimitive
= (O
, hint
) => {
426 const name
of hint
== "string"
427 ? ["toString", "valueOf"]
428 : ["valueOf", "toString"]
430 const method
= O
[name
];
431 if (typeof method
== "function") {
432 // Method is callable.
433 const result
= call(method
, O
, []);
434 if (!isObject(result
)) {
435 // Method returns a primitive.
438 // Method returns an object.
442 // Method is not callable.
446 throw new TypeError("Piscēs: Unable to convert object to primitive");
450 * Returns the provided value converted to a primitive, or throws if
451 * no such conversion is possible.
453 * The provided preferred type, if specified, should be "string",
454 * "number", or "default". If the provided input has a
455 * `Symbol.toPrimitive` method, this function will throw rather than
456 * calling that method with a preferred type other than one of the
459 export const toPrimitive
= ($, preferredType
) => {
461 // The provided value is an object.
462 const exoticToPrim
= $[Symbol
.toPrimitive
] ?? undefined;
463 if (exoticToPrim
!== undefined) {
464 // The provided value has an exotic primitive conversion method.
465 if (typeof exoticToPrim
!= "function") {
466 // The method is not callable.
468 "Piscēs: Symbol.toPrimitive was neither nullish nor callable.",
471 // The method is callable.
472 const hint
= `${preferredType ?? "default"}`;
473 if (!["default", "string", "number"].includes(hint
)) {
474 // An invalid preferred type was specified.
476 `Piscēs: Invalid preferred type: ${preferredType}.`,
479 // The resulting hint is either default, string, or number.
480 return call(exoticToPrim
, $, [hint
]);
484 // Use the ordinary primitive conversion function.
485 ordinaryToPrimitive($, hint
);
488 // The provided value is already a primitive.
494 * Returns the property key (symbol or string) corresponding to the
497 export const toPropertyKey
= ($) => {
498 const key
= toPrimitive($, "string");
499 return typeof key
== "symbol" ? key
: `${key}`;