From: Lady Date: Sun, 4 Sep 2022 21:47:15 +0000 (-0700) Subject: Treat object function args more consistently X-Git-Tag: 0.1.3^0 X-Git-Url: https://git.ladys.computer/Pisces/commitdiff_plain/c1364974d7add579702210b7dfe8396ea20c0ccf?ds=inline;hp=20911ecea4d7c09ac104d4e059975444bb4238d7 Treat object function args more consistently Functions like getOwnPropertyKeys now do not require that their arguments are objects, but *do* require that they are not null or undefined. Similarly, toObject now throws for null and undefined values, rather than returning an empty object. Use `toObject($ ?? {})` if you need the old behaviour. --- diff --git a/object.js b/object.js index a34423e..f68909e 100644 --- a/object.js +++ b/object.js @@ -573,26 +573,62 @@ export const { * Removes the provided property key from the provided object and * returns the object. * - * ☡ This function differs from Reflect.deleteProperty and the + * ※ This function differs from Reflect.deleteProperty and the * `delete` operator in that it throws if the deletion is * unsuccessful. + * + * ※ This function throws if the first argument is not an object. */ deleteOwnProperty, + /** + * Returns an array of property keys on the provided object. + * + * ※ This is effectively an alias for Reflect.ownKeys, except that + * it does not require that the argument be an object. + */ + getOwnPropertyKeys, + + /** + * Returns the value of the provided property key on the provided + * object. + * + * ※ This is effectively an alias for Reflect.get, except that it + * does not require that the argument be an object. + */ + getPropertyValue, + + /** + * Returns whether the provided property key exists on the provided + * object. + * + * ※ This is effectively an alias for Reflect.has, except that it + * does not require that the argument be an object. + * + * ※ This includes properties present on the prototype chain. + */ + hasProperty, + /** * Sets the provided property key to the provided value on the * provided object and returns the object. * * ※ This function differs from Reflect.set in that it throws if the * setting is unsuccessful. + * + * ※ This function throws if the first argument is not an object. */ setPropertyValue, } = (() => { - const { deleteProperty, set } = Reflect; + const { deleteProperty, get, has, ownKeys, set } = Reflect; return { deleteOwnProperty: (O, P) => { - if (!deleteProperty(O, P)) { + if (type(O) !== "object") { + throw new TypeError( + `Piscēs: Tried to set property but provided value was not an object: ${V}`, + ); + } else if (!deleteProperty(O, P)) { throw new TypeError( `Piscēs: Tried to delete property from object but [[Delete]] returned false: ${P}`, ); @@ -600,8 +636,16 @@ export const { return O; } }, + getOwnPropertyKeys: (O) => ownKeys(toObject(O)), + getPropertyValue: (O, P, Receiver = O) => + get(toObject(O), P, Receiver), + hasProperty: (O, P) => has(toObject(O), P), setPropertyValue: (O, P, V, Receiver = O) => { - if (!set(O, P, V, Receiver)) { + if (type(O) !== "object") { + throw new TypeError( + `Piscēs: Tried to set property but provided value was not an object: ${V}`, + ); + } else if (!set(O, P, V, Receiver)) { throw new TypeError( `Piscēs: Tried to set property on object but [[Set]] returned false: ${P}`, ); @@ -714,46 +758,38 @@ export const { }; })(); -export const { - /** - * Returns an array of property keys on the provided object. - * - * ※ This is an alias for Reflect.ownKeys. - */ - ownKeys: getOwnPropertyKeys, - - /** - * Returns the value of the provided property key on the provided - * object. - * - * ※ This is an alias for Reflect.get. - */ - get: getPropertyValue, - - /** - * Returns whether the provided property key exists on the provided - * object. - * - * ※ This is an alias for Reflect.has. - * - * ※ This includes properties present on the prototype chain. - */ - has: hasProperty, -} = Reflect; +export const getMethod = (V, P) => { + const func = getPropertyValue(V, P); + if (func == null) { + return undefined; + } else if (typeof func !== "function") { + throw new TypeError(`Piscēs: Method not callable: ${P}`); + } else { + return func; + } +}; /** * Returns the provided value converted to an object. * - * Null and undefined are converted to a new, empty object. Other - * primitives are wrapped. Existing objects are returned with no - * modification. + * Existing objects are returned with no modification. * - * ※ This is effectively a nonconstructible version of the Object - * constructor. + * ☡ This function throws a TypeError if its argument is null or + * undefined. */ export const { toObject } = (() => { const makeObject = Object; - return { toObject: ($) => makeObject($) }; + return { + toObject: ($) => { + if ($ == null) { + throw new TypeError( + `Piscēs: Cannot convert ${$} into an object.`, + ); + } else { + return makeObject($); + } + }, + }; })(); /** diff --git a/object.test.js b/object.test.js index 2b6cbb8..efef4d8 100644 --- a/object.test.js +++ b/object.test.js @@ -22,6 +22,10 @@ import { defineOwnProperties, deleteOwnProperty, frozenCopy, + getMethod, + getOwnPropertyKeys, + getPropertyValue, + hasProperty, LazyLoader, PropertyDescriptor, setPropertyValue, @@ -875,6 +879,80 @@ describe("frozenCopy", () => { }); }); +describe("getMethod", () => { + it("[[Call]] gets a method", () => { + const method = () => {}; + assertStrictEquals(getMethod({ method }, "method"), method); + }); + + it("[[Call]] works for values coercible to objects", () => { + assertEquals(getMethod("", "toString"), String.prototype.toString); + }); + + it("[[Call]] throws for null and undefined", () => { + assertThrows(() => getMethod(null, "valueOf")); + assertThrows(() => getMethod(undefined, "valueOf")); + }); + + it("[[Call]] throws if the resulting value isn’t callable", () => { + assertThrows(() => getMethod({ "failure": true }, "failure")); + }); +}); + +describe("getOwnPropertyKeys", () => { + it("[[Call]] gets own (but not inherited) property keys", () => { + assertEquals(getOwnPropertyKeys({ success: true }), ["success"]); + }); + + it("[[Call]] works for values coercible to objects", () => { + assertEquals(getOwnPropertyKeys("foo"), ["0", "1", "2", "length"]); + }); + + it("[[Call]] throws for null and undefined", () => { + assertThrows(() => getOwnPropertyKeys(null)); + assertThrows(() => getOwnPropertyKeys(undefined)); + }); +}); + +describe("getPropertyValue", () => { + it("[[Call]] gets property values on the provided object", () => { + assertStrictEquals( + getPropertyValue({ success: true }, "success"), + true, + ); + }); + + it("[[Call]] works for values coercible to objects", () => { + assertStrictEquals( + getPropertyValue("", "toString"), + String.prototype.toString, + ); + }); + + it("[[Call]] throws for null and undefined", () => { + assertThrows(() => getPropertyValue(null, "valueOf")); + assertThrows(() => getPropertyValue(undefined, "valueOf")); + }); +}); + +describe("hasProperty", () => { + it("[[Call]] gets whether a property exists on the provided object", () => { + assertStrictEquals( + hasProperty({ success: "etaoin" }, "success"), + true, + ); + }); + + it("[[Call]] works for values coercible to objects", () => { + assertStrictEquals(hasProperty("", "toString"), true); + }); + + it("[[Call]] throws for null and undefined", () => { + assertThrows(() => hasProperty(null, "valueOf")); + assertThrows(() => hasProperty(undefined, "valueOf")); + }); +}); + describe("setPropertyValue", () => { it("[[Call]] sets the provided property on the provided object", () => { const obj = {}; @@ -935,9 +1013,9 @@ describe("toObject", () => { assertStrictEquals(toObject(obj), obj); }); - it("returns a new object for nullish values", () => { - assertEquals(toObject(null), {}); - assertEquals(toObject(void {}), {}); + it("throws for nullish values", () => { + assertThrows(() => toObject(null)); + assertThrows(() => toObject(void {})); }); it("returns a wrapper object for other primitives", () => {