]> Lady’s Gitweb - Pisces/blob - object.js
Treat object function args more consistently
[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 * An object whose properties are lazy‐loaded from the methods on the
15 * own properties of the provided object.
16 *
17 * This is useful when you are looking to reference properties on
18 * objects which, due to module dependency graphs, cannot be guaranteed
19 * to have been initialized yet.
20 *
21 * The resulting properties will have the same attributes (regarding
22 * configurability, enumerability, and writability) as the
23 * corresponding properties on the methods object. If a property is
24 * marked as writable, the method will never be called if it is set
25 * before it is gotten. By necessity, the resulting properties are all
26 * configurable before they are accessed for the first time.
27 *
28 * Methods will be called with the resulting object as their this
29 * value.
30 *
31 * LazyLoader objects have the same prototype as the passed methods
32 * object.
33 */
34 export class LazyLoader extends null {
35 /** Constructs a new LazyLoader object. */
36 constructor(loadMethods) {
37 const result = objectCreate(getPrototype(loadMethods));
38 const methodKeys = getOwnPropertyKeys(loadMethods);
39 for (let index = 0; index < methodKeys.length; ++index) {
40 const methodKey = methodKeys[index];
41 const { configurable, enumerable, writable } =
42 getOwnPropertyDescriptor(loadMethods, methodKey);
43 defineOwnProperty(result, methodKey, {
44 configurable: true,
45 enumerable,
46 get: () => {
47 const value = call(loadMethods[methodKey], result, []);
48 defineOwnProperty(result, methodKey, {
49 configurable,
50 enumerable,
51 value,
52 writable,
53 });
54 return value;
55 },
56 set: writable
57 ? ($) =>
58 defineOwnProperty(result, methodKey, {
59 configurable,
60 enumerable,
61 value: $,
62 writable,
63 })
64 : void {},
65 });
66 }
67 return result;
68 }
69 }
70
71 /**
72 * A property descriptor object.
73 *
74 * Actually constructing a property descriptor object using this class
75 * is only necessary if you need strict guarantees about the types of
76 * its properties; the resulting object is proxied to ensure the types
77 * match what one would expect from composing FromPropertyDescriptor
78 * and ToPropertyDescriptor in the Ecmascript specification.
79 *
80 * Otherwise, the instance properties and methods are generic.
81 */
82 export const { PropertyDescriptor } = (() => {
83 class PropertyDescriptor extends null {
84 /**
85 * Constructs a new property descriptor object from the provided
86 * object.
87 *
88 * The resulting object is proxied to enforce types (for example,
89 * its `enumerable` property, if defined, will always be a
90 * boolean).
91 */
92 //deno-lint-ignore constructor-super
93 constructor(O) {
94 if (type(O) !== "object") {
95 // The provided value is not an object.
96 throw new TypeError(
97 "Piscēs: Cannot convert primitive to property descriptor.",
98 );
99 } else {
100 // The provided value is an object.
101 const desc = objectCreate(propertyDescriptorPrototype);
102 if ("enumerable" in O) {
103 // An enumerable property is specified.
104 desc.enumerable = !!O.enumerable;
105 } else {
106 // An enumerable property is not specified.
107 /* do nothing */
108 }
109 if ("configurable" in O) {
110 // A configurable property is specified.
111 desc.configurable = !!O.configurable;
112 } else {
113 // A configurable property is not specified.
114 /* do nothing */
115 }
116 if ("value" in O) {
117 // A value property is specified.
118 desc.value = O.value;
119 } else {
120 // A value property is not specified.
121 /* do nothing */
122 }
123 if ("writable" in O) {
124 // A writable property is specified.
125 desc.writable = !!O.writable;
126 } else {
127 // A writable property is not specified.
128 /* do nothing */
129 }
130 if ("get" in O) {
131 // A get property is specified.
132 const getter = O.get;
133 if (getter !== undefined && typeof getter !== "function") {
134 // The getter is not callable.
135 throw new TypeError("Piscēs: Getters must be callable.");
136 } else {
137 // The getter is callable.
138 desc.get = getter;
139 }
140 } else {
141 // A get property is not specified.
142 /* do nothing */
143 }
144 if ("set" in O) {
145 // A set property is specified.
146 const setter = O.set;
147 if (setter !== undefined && typeof setter !== "function") {
148 // The setter is not callable.
149 throw new TypeError("Piscēs: Setters must be callable.");
150 } else {
151 // The setter is callable.
152 desc.set = setter;
153 }
154 } else {
155 // A set property is not specified.
156 /* do nothing */
157 }
158 if (
159 ("get" in desc || "set" in desc) &&
160 ("value" in desc || "writable" in desc)
161 ) {
162 // Both accessor and data attributes have been defined.
163 throw new TypeError(
164 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
165 );
166 } else {
167 // The property descriptor is valid.
168 return new Proxy(desc, propertyDescriptorProxyHandler);
169 }
170 }
171 }
172
173 /**
174 * Completes this property descriptor by setting missing values to
175 * their defaults.
176 *
177 * This method modifies this object and returns undefined.
178 */
179 complete() {
180 if (this !== undefined && !("get" in this || "set" in this)) {
181 // This is a generic or data descriptor.
182 if (!("value" in this)) {
183 // `value` is not defined on this.
184 this.value = undefined;
185 } else {
186 // `value` is already defined on this.
187 /* do nothing */
188 }
189 if (!("writable" in this)) {
190 // `writable` is not defined on this.
191 this.writable = false;
192 } else {
193 // `writable` is already defined on this.
194 /* do nothing */
195 }
196 } else {
197 // This is not a generic or data descriptor.
198 if (!("get" in this)) {
199 // `get` is not defined on this.
200 this.get = undefined;
201 } else {
202 // `get` is already defined on this.
203 /* do nothing */
204 }
205 if (!("set" in this)) {
206 // `set` is not defined on this.
207 this.set = undefined;
208 } else {
209 // `set` is already defined on this.
210 /* do nothing */
211 }
212 }
213 if (!("enumerable" in this)) {
214 // `enumerable` is not defined on this.
215 this.enumerable = false;
216 } else {
217 // `enumerable` is already defined on this.
218 /* do nothing */
219 }
220 if (!("configurable" in this)) {
221 // `configurable` is not defined on this.
222 this.configurable = false;
223 } else {
224 // `configurable` is already defined on this.
225 /* do nothing */
226 }
227 }
228
229 /** Gets whether this is an accessor descrtiptor. */
230 get isAccessorDescriptor() {
231 return this !== undefined && ("get" in this || "set" in this);
232 }
233
234 /** Gets whether this is a data descrtiptor. */
235 get isDataDescriptor() {
236 return this !== undefined &&
237 ("value" in this || "writable" in this);
238 }
239
240 /** Gets whether this is a fully‐populated property descriptor. */
241 get isFullyPopulated() {
242 return this !== undefined &&
243 ("value" in this && "writable" in this ||
244 "get" in this && "set" in this) &&
245 "enumerable" in this && "configurable" in this;
246 }
247
248 /**
249 * Gets whether this is a generic (not accessor or data)
250 * descrtiptor.
251 */
252 get isGenericDescriptor() {
253 return this !== undefined &&
254 !("get" in this || "set" in this || "value" in this ||
255 "writable" in this);
256 }
257 }
258
259 const coercePropertyDescriptorValue = (P, V) => {
260 switch (P) {
261 case "configurable":
262 case "enumerable":
263 case "writable":
264 return !!V;
265 case "value":
266 return V;
267 case "get":
268 if (V !== undefined && typeof V !== "function") {
269 throw new TypeError(
270 "Piscēs: Getters must be callable.",
271 );
272 } else {
273 return V;
274 }
275 case "set":
276 if (V !== undefined && typeof V !== "function") {
277 throw new TypeError(
278 "Piscēs: Setters must be callable.",
279 );
280 } else {
281 return V;
282 }
283 default:
284 return V;
285 }
286 };
287
288 const {
289 prototype: propertyDescriptorPrototype,
290 } = PropertyDescriptor;
291
292 const propertyDescriptorProxyHandler = Object.assign(
293 Object.create(null),
294 {
295 defineProperty(O, P, Desc) {
296 if (
297 P === "configurable" || P === "enumerable" ||
298 P === "writable" || P === "value" ||
299 P === "get" || P === "set"
300 ) {
301 // P is a property descriptor attribute.
302 const desc = new PropertyDescriptor(Desc);
303 if ("get" in desc || "set" in desc) {
304 // Desc is an accessor property descriptor.
305 throw new TypeError(
306 "Piscēs: Property descriptor attributes must be data properties.",
307 );
308 } else if ("value" in desc || !(P in O)) {
309 // Desc has a value or P does not already exist on O.
310 desc.value = coercePropertyDescriptorValue(P, desc.value);
311 } else {
312 // Desc is not an accessor property descriptor and has no
313 // value.
314 /* do nothing */
315 }
316 const isAccessorDescriptor = "get" === P || "set" === P ||
317 "get" in O || "set" in O;
318 const isDataDescriptor = "value" === P || "writable" === P ||
319 "value" in O || "writable" in O;
320 if (isAccessorDescriptor && isDataDescriptor) {
321 // Both accessor and data attributes will be present on O
322 // after defining P.
323 throw new TypeError(
324 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
325 );
326 } else {
327 // P can be safely defined on O.
328 return defineOwnProperty(O, P, desc);
329 }
330 } else {
331 // P is not a property descriptor attribute.
332 return defineOwnProperty(O, P, Desc);
333 }
334 },
335 set(O, P, V, Receiver) {
336 if (
337 P === "configurable" || P === "enumerable" ||
338 P === "writable" || P === "value" ||
339 P === "get" || P === "set"
340 ) {
341 // P is a property descriptor attribute.
342 const newValue = coercePropertyDescriptorValue(P, V);
343 const isAccessorDescriptor = "get" === P || "set" === P ||
344 "get" in O || "set" in O;
345 const isDataDescriptor = "value" === P || "writable" === P ||
346 "value" in O || "writable" in O;
347 if (isAccessorDescriptor && isDataDescriptor) {
348 // Both accessor and data attributes will be present on O
349 // after defining P.
350 throw new TypeError(
351 "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
352 );
353 } else {
354 // P can be safely defined on O.
355 //
356 // ☡ Receiver will be the *proxied* object, so passing it
357 // through to setPropertyValue here would produce an
358 // infinite loop.
359 //
360 // ☡ This has implications on objects with a proxied
361 // PropertyDescriptor in their prototype.
362 return setPropertyValue(O, P, newValue, O);
363 }
364 } else {
365 return setPropertyValue(O, P, V, Receiver);
366 }
367 },
368 setPrototypeOf(O, V) {
369 if (V !== propertyDescriptorPrototype) {
370 // V is not the property descriptor prototype.
371 return false;
372 } else {
373 // V is the property descriptor prototype.
374 return setPrototype(O, V);
375 }
376 },
377 },
378 );
379
380 return { PropertyDescriptor };
381 })();
382
383 export const {
384 /**
385 * Defines own properties on the provided object using the
386 * descriptors on the enumerable own properties of the provided
387 * additional objects.
388 *
389 * ※ This differs from Object.defineProperties in that it can take
390 * multiple source objects.
391 */
392 defineOwnProperties,
393 } = (() => {
394 const { defineProperties } = Object;
395 const { forEach: arrayForEach } = Array.prototype;
396 return {
397 defineOwnProperties: (O, ...sources) => {
398 call(
399 arrayForEach,
400 sources,
401 [(source) => defineProperties(O, source)],
402 );
403 return O;
404 },
405 };
406 })();
407
408 export const {
409 /**
410 * Defines an own property on the provided object on the provided
411 * property key using the provided property descriptor.
412 *
413 * ※ This is an alias for Object.defineProperty.
414 */
415 defineProperty: defineOwnProperty,
416
417 /**
418 * Marks the provided object as non·extensible and marks all its
419 * properties as nonconfigurable and (if data properties)
420 * nonwritable, and returns the object.
421 *
422 * ※ This is an alias for Object.freeze.
423 */
424 freeze,
425
426 /**
427 * Returns the property descriptor for the own property with the
428 * provided property key on the provided object, or null if none
429 * exists.
430 *
431 * ※ This is an alias for Object.getOwnPropertyDescriptor.
432 */
433 getOwnPropertyDescriptor,
434
435 /**
436 * Returns the property descriptors for the own properties on the
437 * provided object.
438 *
439 * ※ This is an alias for Object.getOwnPropertyDescriptors.
440 */
441 getOwnPropertyDescriptors,
442
443 /**
444 * Returns an array of string‐valued own property keys on the
445 * provided object.
446 *
447 * ☡ This includes both enumerable and non·enumerable properties.
448 *
449 * ※ This is an alias for Object.getOwnPropertyNames.
450 */
451 getOwnPropertyNames: getOwnPropertyStrings,
452
453 /**
454 * Returns an array of symbol‐valued own property keys on the
455 * provided object.
456 *
457 * ☡ This includes both enumerable and non·enumerable properties.
458 *
459 * ※ This is an alias for Object.getOwnPropertySymbols.
460 */
461 getOwnPropertySymbols,
462
463 /**
464 * Returns the prototype of the provided object.
465 *
466 * ※ This is an alias for Object.getPrototypeOf.
467 */
468 getPrototypeOf: getPrototype,
469
470 /**
471 * Returns whether the provided object has an own property with the
472 * provided property key.
473 *
474 * ※ This is an alias for Object.hasOwn.
475 */
476 hasOwn: hasOwnProperty,
477
478 /**
479 * Returns whether the provided object is extensible.
480 *
481 * ※ This is an alias for Object.isExtensible.
482 */
483 isExtensible,
484
485 /**
486 * Returns whether the provided object is frozen.
487 *
488 * ※ This is an alias for Object.isFrozen.
489 */
490 isFrozen,
491
492 /**
493 * Returns whether the provided object is sealed.
494 *
495 * ※ This is an alias for Object.isSealed.
496 */
497 isSealed,
498
499 /**
500 * Returns an array of key~value pairs for the enumerable,
501 * string‐valued property keys on the provided object.
502 *
503 * ※ This is an alias for Object.entries.
504 */
505 entries: namedEntries,
506
507 /**
508 * Returns an array of the enumerable, string‐valued property keys on
509 * the provided object.
510 *
511 * ※ This is an alias for Object.keys.
512 */
513 keys: namedKeys,
514
515 /**
516 * Returns an array of property values for the enumerable,
517 * string‐valued property keys on the provided object.
518 *
519 * ※ This is an alias for Object.values.
520 */
521 values: namedValues,
522
523 /**
524 * Returns a new object with the provided prototype and property
525 * descriptors.
526 *
527 * ※ This is an alias for Object.create.
528 */
529 create: objectCreate,
530
531 /**
532 * Returns a new object with the provided property keys and values.
533 *
534 * ※ This is an alias for Object.fromEntries.
535 */
536 fromEntries: objectFromEntries,
537
538 /**
539 * Marks the provided object as non·extensible, and returns the
540 * object.
541 *
542 * ※ This is an alias for Object.preventExtensions.
543 */
544 preventExtensions,
545
546 /**
547 * Marks the provided object as non·extensible and marks all its
548 * properties as nonconfigurable, and returns the object.
549 *
550 * ※ This is an alias for Object.seal.
551 */
552 seal,
553
554 /**
555 * Sets the values of the enumerable own properties of the provided
556 * additional objects on the provided object.
557 *
558 * ※ This is an alias for Object.assign.
559 */
560 assign: setPropertyValues,
561
562 /**
563 * Sets the prototype of the provided object to the provided value
564 * and returns the object.
565 *
566 * ※ This is an alias for Object.setPrototypeOf.
567 */
568 setPrototypeOf: setPrototype,
569 } = Object;
570
571 export const {
572 /**
573 * Removes the provided property key from the provided object and
574 * returns the object.
575 *
576 * ※ This function differs from Reflect.deleteProperty and the
577 * `delete` operator in that it throws if the deletion is
578 * unsuccessful.
579 *
580 * ※ This function throws if the first argument is not an object.
581 */
582 deleteOwnProperty,
583
584 /**
585 * Returns an array of property keys on the provided object.
586 *
587 * ※ This is effectively an alias for Reflect.ownKeys, except that
588 * it does not require that the argument be an object.
589 */
590 getOwnPropertyKeys,
591
592 /**
593 * Returns the value of the provided property key on the provided
594 * object.
595 *
596 * ※ This is effectively an alias for Reflect.get, except that it
597 * does not require that the argument be an object.
598 */
599 getPropertyValue,
600
601 /**
602 * Returns whether the provided property key exists on the provided
603 * object.
604 *
605 * ※ This is effectively an alias for Reflect.has, except that it
606 * does not require that the argument be an object.
607 *
608 * ※ This includes properties present on the prototype chain.
609 */
610 hasProperty,
611
612 /**
613 * Sets the provided property key to the provided value on the
614 * provided object and returns the object.
615 *
616 * ※ This function differs from Reflect.set in that it throws if the
617 * setting is unsuccessful.
618 *
619 * ※ This function throws if the first argument is not an object.
620 */
621 setPropertyValue,
622 } = (() => {
623 const { deleteProperty, get, has, ownKeys, set } = Reflect;
624
625 return {
626 deleteOwnProperty: (O, P) => {
627 if (type(O) !== "object") {
628 throw new TypeError(
629 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
630 );
631 } else if (!deleteProperty(O, P)) {
632 throw new TypeError(
633 `Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`,
634 );
635 } else {
636 return O;
637 }
638 },
639 getOwnPropertyKeys: (O) => ownKeys(toObject(O)),
640 getPropertyValue: (O, P, Receiver = O) =>
641 get(toObject(O), P, Receiver),
642 hasProperty: (O, P) => has(toObject(O), P),
643 setPropertyValue: (O, P, V, Receiver = O) => {
644 if (type(O) !== "object") {
645 throw new TypeError(
646 `Piscēs: Tried to set property but provided value was not an object: ${V}`,
647 );
648 } else if (!set(O, P, V, Receiver)) {
649 throw new TypeError(
650 `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`,
651 );
652 } else {
653 return O;
654 }
655 },
656 };
657 })();
658
659 export const {
660 /**
661 * Returns a new frozen shallow copy of the enumerable own properties
662 * of the provided object, according to the following rules :—
663 *
664 * - For data properties, create a nonconfigurable, nonwritable
665 * property with the same value.
666 *
667 * - For accessor properties, create a nonconfigurable accessor
668 * property with the same getter *and* setter.
669 *
670 * The prototype for the resulting object will be taken from the
671 * `prototype` property of the provided constructor, or the
672 * `prototype` of the `constructor` of the provided object if the
673 * provided constructor is undefined. If the used constructor has a
674 * nonnullish `Symbol.species`, that will be used instead. If the
675 * used constructor or species is nullish or does not have a
676 * `prototype` property, the prototype is set to null.
677 */
678 frozenCopy,
679 } = (() => {
680 const {
681 iterator: iteratorSymbol,
682 species: speciesSymbol,
683 } = Symbol;
684 const {
685 next: generatorIteratorNext,
686 } = getPrototype(function* () {}.prototype);
687 const propertyDescriptorEntryIterablePrototype = {
688 [iteratorSymbol]() {
689 return {
690 next: bind(generatorIteratorNext, this.generator(), []),
691 };
692 },
693 };
694 return {
695 frozenCopy: (O, constructor = O?.constructor) => {
696 if (O == null) {
697 // O is null or undefined.
698 throw new TypeError(
699 "Piscēs: Cannot copy properties of null or undefined.",
700 );
701 } else {
702 // O is not null or undefined.
703 //
704 // (If not provided, the constructor will be the value of
705 // getting the `constructor` property of O.)
706 const species = constructor?.[speciesSymbol] ?? constructor;
707 return preventExtensions(
708 objectCreate(
709 species == null || !("prototype" in species)
710 ? null
711 : species.prototype,
712 objectFromEntries(
713 objectCreate(
714 propertyDescriptorEntryIterablePrototype,
715 {
716 generator: {
717 value: function* () {
718 const ownPropertyKeys = getOwnPropertyKeys(O);
719 for (
720 let i = 0;
721 i < ownPropertyKeys.length;
722 ++i
723 ) {
724 const P = ownPropertyKeys[i];
725 const Desc = getOwnPropertyDescriptor(O, P);
726 if (Desc.enumerable) {
727 // P is an enumerable property.
728 yield [
729 P,
730 "get" in Desc || "set" in Desc
731 ? {
732 configurable: false,
733 enumerable: true,
734 get: Desc.get,
735 set: Desc.set,
736 }
737 : {
738 configurable: false,
739 enumerable: true,
740 value: Desc.value,
741 writable: false,
742 },
743 ];
744 } else {
745 // P is not an enumerable property.
746 /* do nothing */
747 }
748 }
749 },
750 },
751 },
752 ),
753 ),
754 ),
755 );
756 }
757 },
758 };
759 })();
760
761 export const getMethod = (V, P) => {
762 const func = getPropertyValue(V, P);
763 if (func == null) {
764 return undefined;
765 } else if (typeof func !== "function") {
766 throw new TypeError(`Piscēs: Method not callable: ${P}`);
767 } else {
768 return func;
769 }
770 };
771
772 /**
773 * Returns the provided value converted to an object.
774 *
775 * Existing objects are returned with no modification.
776 *
777 * ☡ This function throws a TypeError if its argument is null or
778 * undefined.
779 */
780 export const { toObject } = (() => {
781 const makeObject = Object;
782 return {
783 toObject: ($) => {
784 if ($ == null) {
785 throw new TypeError(
786 `Piscēs: Cannot convert ${$} into an object.`,
787 );
788 } else {
789 return makeObject($);
790 }
791 },
792 };
793 })();
794
795 /**
796 * Returns the property key (symbol or string) corresponding to the
797 * provided value.
798 */
799 export const toPropertyKey = ($) => {
800 const key = toPrimitive($, "string");
801 return typeof key == "symbol" ? key : `${key}`;
802 };
This page took 0.190889 seconds and 5 git commands to generate.