]> Lady’s Gitweb - Pisces/blob - object.js
Split some object code into value and unit test
[Pisces] / object.js
1 // ♓🌟 Piscēs ∷ object.js
2 // ====================================================================
3 //
4 // Copyright © 2022 Lady [@ Lady’s Computer].
5 //
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/>.
9
10 import { bind, call } from "./function.js";
11 import { toPrimitive, type } from "./value.js";
12
13 /**
14 * A property descriptor object.
15 *
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.
21 *
22 * Otherwise, the instance properties and methods are generic.
23 */
24 export const { PropertyDescriptor } = (() => {
25 class PropertyDescriptor extends null {
26 /**
27 * Constructs a new property descriptor object from the provided
28 * object.
29 *
30 * The resulting object is proxied to enforce types (for example,
31 * its `enumerable` property, if defined, will always be a
32 * boolean).
33 */
34 //deno-lint-ignore constructor-super
35 constructor(O) {
36 if (type(O) !== "object") {
37 // The provided value is not an object.
38 throw new TypeError(
39 "Piscēs: Cannot convert primitive to property descriptor.",
40 );
41 } else {
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;
47 } else {
48 // An enumerable property is not specified.
49 /* do nothing */
50 }
51 if ("configurable" in O) {
52 // A configurable property is specified.
53 desc.configurable = !!O.configurable;
54 } else {
55 // A configurable property is not specified.
56 /* do nothing */
57 }
58 if ("value" in O) {
59 // A value property is specified.
60 desc.value = O.value;
61 } else {
62 // A value property is not specified.
63 /* do nothing */
64 }
65 if ("writable" in O) {
66 // A writable property is specified.
67 desc.writable = !!O.writable;
68 } else {
69 // A writable property is not specified.
70 /* do nothing */
71 }
72 if ("get" in O) {
73 // A get property is specified.
74 const getter = O.get;
75 if (getter !== undefined && typeof getter !== "function") {
76 // The getter is not callable.
77 throw new TypeError("Piscēs: Getters must be callable.");
78 } else {
79 // The getter is callable.
80 desc.get = getter;
81 }
82 } else {
83 // A get property is not specified.
84 /* do nothing */
85 }
86 if ("set" in O) {
87 // A set property is specified.
88 const setter = O.set;
89 if (setter !== undefined && typeof setter !== "function") {
90 // The setter is not callable.
91 throw new TypeError("Piscēs: Setters must be callable.");
92 } else {
93 // The setter is callable.
94 desc.set = setter;
95 }
96 } else {
97 // A set property is not specified.
98 /* do nothing */
99 }
100 if (
101 ("get" in desc || "set" in desc) &&
102 ("value" in desc || "writable" in desc)
103 ) {
104 // Both accessor and data attributes have been defined.
105 throw new TypeError(
106 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
107 );
108 } else {
109 // The property descriptor is valid.
110 return new Proxy(desc, propertyDescriptorProxyHandler);
111 }
112 }
113 }
114
115 /**
116 * Completes this property descriptor by setting missing values to
117 * their defaults.
118 *
119 * This method modifies this object and returns undefined.
120 */
121 complete() {
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;
127 } else {
128 // `value` is already defined on this.
129 /* do nothing */
130 }
131 if (!("writable" in this)) {
132 // `writable` is not defined on this.
133 this.writable = false;
134 } else {
135 // `writable` is already defined on this.
136 /* do nothing */
137 }
138 } else {
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;
143 } else {
144 // `get` is already defined on this.
145 /* do nothing */
146 }
147 if (!("set" in this)) {
148 // `set` is not defined on this.
149 this.set = undefined;
150 } else {
151 // `set` is already defined on this.
152 /* do nothing */
153 }
154 }
155 if (!("enumerable" in this)) {
156 // `enumerable` is not defined on this.
157 this.enumerable = false;
158 } else {
159 // `enumerable` is already defined on this.
160 /* do nothing */
161 }
162 if (!("configurable" in this)) {
163 // `configurable` is not defined on this.
164 this.configurable = false;
165 } else {
166 // `configurable` is already defined on this.
167 /* do nothing */
168 }
169 }
170
171 /** Gets whether this is an accessor descrtiptor. */
172 get isAccessorDescriptor() {
173 return this !== undefined && ("get" in this || "set" in this);
174 }
175
176 /** Gets whether this is a data descrtiptor. */
177 get isDataDescriptor() {
178 return this !== undefined &&
179 ("value" in this || "writable" in this);
180 }
181
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;
188 }
189
190 /**
191 * Gets whether this is a generic (not accessor or data)
192 * descrtiptor.
193 */
194 get isGenericDescriptor() {
195 return this !== undefined &&
196 !("get" in this || "set" in this || "value" in this ||
197 "writable" in this);
198 }
199 }
200
201 const coercePropertyDescriptorValue = (P, V) => {
202 switch (P) {
203 case "configurable":
204 case "enumerable":
205 case "writable":
206 return !!V;
207 case "value":
208 return V;
209 case "get":
210 if (V !== undefined && typeof V !== "function") {
211 throw new TypeError(
212 "Piscēs: Getters must be callable.",
213 );
214 } else {
215 return V;
216 }
217 case "set":
218 if (V !== undefined && typeof V !== "function") {
219 throw new TypeError(
220 "Piscēs: Setters must be callable.",
221 );
222 } else {
223 return V;
224 }
225 default:
226 return V;
227 }
228 };
229
230 const {
231 prototype: propertyDescriptorPrototype,
232 } = PropertyDescriptor;
233
234 const propertyDescriptorProxyHandler = Object.assign(
235 Object.create(null),
236 {
237 defineProperty(O, P, Desc) {
238 if (
239 P === "configurable" || P === "enumerable" ||
240 P === "writable" || P === "value" ||
241 P === "get" || P === "set"
242 ) {
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.
247 throw new TypeError(
248 "Piscēs: Property descriptor attributes must be data properties.",
249 );
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);
253 } else {
254 // Desc is not an accessor property descriptor and has no
255 // value.
256 /* do nothing */
257 }
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
264 // after defining P.
265 throw new TypeError(
266 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
267 );
268 } else {
269 // P can be safely defined on O.
270 return defineOwnProperty(O, P, desc);
271 }
272 } else {
273 // P is not a property descriptor attribute.
274 return defineOwnProperty(O, P, Desc);
275 }
276 },
277 set(O, P, V, Receiver) {
278 if (
279 P === "configurable" || P === "enumerable" ||
280 P === "writable" || P === "value" ||
281 P === "get" || P === "set"
282 ) {
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
291 // after defining P.
292 throw new TypeError(
293 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
294 );
295 } else {
296 // P can be safely defined on O.
297 //
298 // ☡ Receiver will be the *proxied* object, so passing it
299 // through to setPropertyValue here would produce an
300 // infinite loop.
301 //
302 // ☡ This has implications on objects with a proxied
303 // PropertyDescriptor in their prototype.
304 return setPropertyValue(O, P, newValue, O);
305 }
306 } else {
307 return setPropertyValue(O, P, V, Receiver);
308 }
309 },
310 setPrototypeOf(O, V) {
311 if (V !== propertyDescriptorPrototype) {
312 // V is not the property descriptor prototype.
313 return false;
314 } else {
315 // V is the property descriptor prototype.
316 return setPrototype(O, V);
317 }
318 },
319 },
320 );
321
322 return { PropertyDescriptor };
323 })();
324
325 export const {
326 /**
327 * Defines own properties on the provided object using the
328 * descriptors on the enumerable own properties of the provided
329 * additional objects.
330 *
331 * ※ This differs from Object.defineProperties in that it can take
332 * multiple source objects.
333 */
334 defineOwnProperties,
335 } = (() => {
336 const { defineProperties } = Object;
337 const { forEach: arrayForEach } = Array.prototype;
338 return {
339 defineOwnProperties: (O, ...sources) => {
340 call(
341 arrayForEach,
342 sources,
343 [(source) => defineProperties(O, source)],
344 );
345 return O;
346 },
347 };
348 })();
349
350 export const {
351 /**
352 * Defines an own property on the provided object on the provided
353 * property key using the provided property descriptor.
354 *
355 * ※ This is an alias for Object.defineProperty.
356 */
357 defineProperty: defineOwnProperty,
358
359 /**
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.
363 *
364 * ※ This is an alias for Object.freeze.
365 */
366 freeze,
367
368 /**
369 * Returns the property descriptor for the own property with the
370 * provided property key on the provided object, or null if none
371 * exists.
372 *
373 * ※ This is an alias for Object.getOwnPropertyDescriptor.
374 */
375 getOwnPropertyDescriptor,
376
377 /**
378 * Returns the property descriptors for the own properties on the
379 * provided object.
380 *
381 * ※ This is an alias for Object.getOwnPropertyDescriptors.
382 */
383 getOwnPropertyDescriptors,
384
385 /**
386 * Returns an array of string‐valued own property keys on the
387 * provided object.
388 *
389 * ☡ This includes both enumerable and non·enumerable properties.
390 *
391 * ※ This is an alias for Object.getOwnPropertyNames.
392 */
393 getOwnPropertyNames: getOwnPropertyStrings,
394
395 /**
396 * Returns an array of symbol‐valued own property keys on the
397 * provided object.
398 *
399 * ☡ This includes both enumerable and non·enumerable properties.
400 *
401 * ※ This is an alias for Object.getOwnPropertySymbols.
402 */
403 getOwnPropertySymbols,
404
405 /**
406 * Returns the prototype of the provided object.
407 *
408 * ※ This is an alias for Object.getPrototypeOf.
409 */
410 getPrototypeOf: getPrototype,
411
412 /**
413 * Returns whether the provided object has an own property with the
414 * provided property key.
415 *
416 * ※ This is an alias for Object.hasOwn.
417 */
418 hasOwn: hasOwnProperty,
419
420 /**
421 * Returns whether the provided object is extensible.
422 *
423 * ※ This is an alias for Object.isExtensible.
424 */
425 isExtensible,
426
427 /**
428 * Returns whether the provided object is frozen.
429 *
430 * ※ This is an alias for Object.isFrozen.
431 */
432 isFrozen,
433
434 /**
435 * Returns whether the provided object is sealed.
436 *
437 * ※ This is an alias for Object.isSealed.
438 */
439 isSealed,
440
441 /**
442 * Returns an array of key~value pairs for the enumerable,
443 * string‐valued property keys on the provided object.
444 *
445 * ※ This is an alias for Object.entries.
446 */
447 entries: namedEntries,
448
449 /**
450 * Returns an array of the enumerable, string‐valued property keys on
451 * the provided object.
452 *
453 * ※ This is an alias for Object.keys.
454 */
455 keys: namedKeys,
456
457 /**
458 * Returns an array of property values for the enumerable,
459 * string‐valued property keys on the provided object.
460 *
461 * ※ This is an alias for Object.values.
462 */
463 values: namedValues,
464
465 /**
466 * Returns a new object with the provided prototype and property
467 * descriptors.
468 *
469 * ※ This is an alias for Object.create.
470 */
471 create: objectCreate,
472
473 /**
474 * Returns a new object with the provided property keys and values.
475 *
476 * ※ This is an alias for Object.fromEntries.
477 */
478 fromEntries: objectFromEntries,
479
480 /**
481 * Marks the provided object as non·extensible, and returns the
482 * object.
483 *
484 * ※ This is an alias for Object.preventExtensions.
485 */
486 preventExtensions,
487
488 /**
489 * Marks the provided object as non·extensible and marks all its
490 * properties as nonconfigurable, and returns the object.
491 *
492 * ※ This is an alias for Object.seal.
493 */
494 seal,
495
496 /**
497 * Sets the values of the enumerable own properties of the provided
498 * additional objects on the provided object.
499 *
500 * ※ This is an alias for Object.assign.
501 */
502 assign: setPropertyValues,
503
504 /**
505 * Sets the prototype of the provided object to the provided value
506 * and returns the object.
507 *
508 * ※ This is an alias for Object.setPrototypeOf.
509 */
510 setPrototypeOf: setPrototype,
511 } = Object;
512
513 export const {
514 /**
515 * Removes the provided property key from the provided object and
516 * returns the object.
517 *
518 * ☡ This function differs from Reflect.deleteProperty and the
519 * `delete` operator in that it throws if the deletion is
520 * unsuccessful.
521 */
522 deleteOwnProperty,
523
524 /**
525 * Sets the provided property key to the provided value on the
526 * provided object and returns the object.
527 *
528 * ※ This function differs from Reflect.set in that it throws if the
529 * setting is unsuccessful.
530 */
531 setPropertyValue,
532 } = (() => {
533 const { deleteProperty, set } = Reflect;
534
535 return {
536 deleteOwnProperty: (O, P) => {
537 if (!deleteProperty(O, P)) {
538 throw new TypeError(
539 `Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`,
540 );
541 } else {
542 return O;
543 }
544 },
545 setPropertyValue: (O, P, V, Receiver = O) => {
546 if (!set(O, P, V, Receiver)) {
547 throw new TypeError(
548 `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`,
549 );
550 } else {
551 return O;
552 }
553 },
554 };
555 })();
556
557 export const {
558 /**
559 * Returns a new frozen shallow copy of the enumerable own properties
560 * of the provided object, according to the following rules :—
561 *
562 * - For data properties, create a nonconfigurable, nonwritable
563 * property with the same value.
564 *
565 * - For accessor properties, create a nonconfigurable accessor
566 * property with the same getter *and* setter.
567 *
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.
575 */
576 frozenCopy,
577 } = (() => {
578 const {
579 iterator: iteratorSymbol,
580 species: speciesSymbol,
581 } = Symbol;
582 const {
583 next: generatorIteratorNext,
584 } = getPrototype(function* () {}.prototype);
585 const propertyDescriptorEntryIterablePrototype = {
586 [iteratorSymbol]() {
587 return {
588 next: bind(generatorIteratorNext, this.generator(), []),
589 };
590 },
591 };
592 return {
593 frozenCopy: (O, constructor = O?.constructor) => {
594 if (O == null) {
595 // O is null or undefined.
596 throw new TypeError(
597 "Piscēs: Cannot copy properties of null or undefined.",
598 );
599 } else {
600 // O is not null or undefined.
601 //
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(
606 objectCreate(
607 species == null || !("prototype" in species)
608 ? null
609 : species.prototype,
610 objectFromEntries(
611 objectCreate(
612 propertyDescriptorEntryIterablePrototype,
613 {
614 generator: {
615 value: function* () {
616 const ownPropertyKeys = getOwnPropertyKeys(O);
617 for (
618 let i = 0;
619 i < ownPropertyKeys.length;
620 ++i
621 ) {
622 const P = ownPropertyKeys[i];
623 const Desc = getOwnPropertyDescriptor(O, P);
624 if (Desc.enumerable) {
625 // P is an enumerable property.
626 yield [
627 P,
628 "get" in Desc || "set" in Desc
629 ? {
630 configurable: false,
631 enumerable: true,
632 get: Desc.get,
633 set: Desc.set,
634 }
635 : {
636 configurable: false,
637 enumerable: true,
638 value: Desc.value,
639 writable: false,
640 },
641 ];
642 } else {
643 // P is not an enumerable property.
644 /* do nothing */
645 }
646 }
647 },
648 },
649 },
650 ),
651 ),
652 ),
653 );
654 }
655 },
656 };
657 })();
658
659 export const {
660 /**
661 * Returns an array of property keys on the provided object.
662 *
663 * ※ This is an alias for Reflect.ownKeys.
664 */
665 ownKeys: getOwnPropertyKeys,
666
667 /**
668 * Returns the value of the provided property key on the provided
669 * object.
670 *
671 * ※ This is an alias for Reflect.get.
672 */
673 get: getPropertyValue,
674
675 /**
676 * Returns whether the provided property key exists on the provided
677 * object.
678 *
679 * ※ This is an alias for Reflect.has.
680 *
681 * ※ This includes properties present on the prototype chain.
682 */
683 has: hasProperty,
684 } = Reflect;
685
686 /**
687 * Returns the provided value converted to an object.
688 *
689 * Null and undefined are converted to a new, empty object. Other
690 * primitives are wrapped. Existing objects are returned with no
691 * modification.
692 *
693 * ※ This is effectively a nonconstructible version of the Object
694 * constructor.
695 */
696 export const { toObject } = (() => {
697 const makeObject = Object;
698 return { toObject: ($) => makeObject($) };
699 })();
700
701 /**
702 * Returns the property key (symbol or string) corresponding to the
703 * provided value.
704 */
705 export const toPropertyKey = ($) => {
706 const key = toPrimitive($, "string");
707 return typeof key == "symbol" ? key : `${key}`;
708 };
This page took 0.271795 seconds and 5 git commands to generate.