SPECIES,
toFunctionName,
toLength,
- toPrimitive,
- toPropertyDescriptor,
type,
UNDEFINED,
} from "./value.js";
const {
apply: call,
deleteProperty,
+ defineProperty: reflectDefineProperty,
get,
getOwnPropertyDescriptor: reflectGetOwnPropertyDescriptor,
has,
ownKeys,
set,
+ setPrototypeOf: reflectSetPrototypeOf,
} = Reflect;
/**
*/
export const getOwnPropertyDescriptor = (O, P) => {
const desc = objectGetOwnPropertyDescriptor(O, P);
- return desc === UNDEFINED ? UNDEFINED : toPropertyDescriptor(desc);
+ return desc === UNDEFINED
+ ? UNDEFINED
+ : toPropertyDescriptorRecord(desc);
};
/**
*/
export const isExtensibleObject = (O) => isExtensible(O);
+export const {
+ /**
+ * Returns whether the provided value is a property descriptor record
+ * as created by `toPropertyDescriptor`.
+ *
+ * ※ This function is provided to enable inspection of whether an
+ * object uses the property descriptor record proxy implementation,
+ * not as a general test of whether an object satisfies the
+ * requirements for property descriptors. In most cases, a more
+ * specific test, like `isAccessorDescriptor`, `isDataDescriptor`, or
+ * `isGenericDescriptor`, is preferrable.
+ */
+ isPropertyDescriptorRecord,
+
+ /**
+ * Converts the provided value to a property descriptor record.
+ *
+ * ※ The prototype of a property descriptor record is always `null`.
+ *
+ * ※ Actually constructing a property descriptor object using this
+ * class is only necessary if you need strict guarantees about the
+ * types of its properties; the resulting object is proxied to ensure
+ * the types match what one would expect from composing
+ * FromPropertyDescriptor and ToPropertyDescriptor in the Ecmascript
+ * specification.
+ */
+ toPropertyDescriptorRecord,
+} = (() => {
+ const proxyConstructor = Proxy;
+ const { add: weakSetAdd, has: weakSetHas } = WeakSet.prototype;
+ const propertyDescriptorRecords = new WeakSet();
+ const coercePropertyDescriptorValue = (P, V) => {
+ switch (P) {
+ case "configurable":
+ case "enumerable":
+ case "writable":
+ return !!V;
+ case "value":
+ return V;
+ case "get":
+ if (V !== undefined && typeof V !== "function") {
+ throw new TypeError(
+ "Piscēs: Getters must be callable.",
+ );
+ } else {
+ return V;
+ }
+ case "set":
+ if (V !== undefined && typeof V !== "function") {
+ throw new TypeError(
+ "Piscēs: Setters must be callable.",
+ );
+ } else {
+ return V;
+ }
+ default:
+ return V;
+ }
+ };
+ const propertyDescriptorProxyHandler = objectFreeze(
+ assign(
+ create(null),
+ {
+ defineProperty(O, P, Desc) {
+ if (
+ P === "configurable" || P === "enumerable" ||
+ P === "writable" || P === "value" ||
+ P === "get" || P === "set"
+ ) {
+ // P is a property descriptor attribute.
+ const desc = assign(objectCreate(null), Desc);
+ if ("get" in desc || "set" in desc) {
+ // Desc is an accessor property descriptor.
+ throw new TypeError(
+ "Piscēs: Property descriptor attributes must be data properties.",
+ );
+ } else if ("value" in desc || !(P in O)) {
+ // Desc has a value or P does not already exist on O.
+ desc.value = coercePropertyDescriptorValue(
+ P,
+ desc.value,
+ );
+ } else {
+ // Desc is not an accessor property descriptor and has no
+ // value, but an existing value is present on O.
+ /* do nothing */
+ }
+ const isAccessorDescriptor = "get" === P || "set" === P ||
+ "get" in O || "set" in O;
+ const isDataDescriptor = "value" === P ||
+ "writable" === P ||
+ "value" in O || "writable" in O;
+ if (isAccessorDescriptor && isDataDescriptor) {
+ // Both accessor and data attributes will be present on O
+ // after defining P.
+ throw new TypeError(
+ "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
+ );
+ } else {
+ // P can be safely defined on O.
+ return reflectDefineProperty(O, P, desc);
+ }
+ } else {
+ // P is not a property descriptor attribute.
+ return reflectDefineProperty(O, P, Desc);
+ }
+ },
+ setPrototypeOf(O, V) {
+ if (V !== null) {
+ // V is not the property descriptor prototype.
+ return false;
+ } else {
+ // V is the property descriptor prototype.
+ return reflectSetPrototypeOf(O, V);
+ }
+ },
+ },
+ ),
+ );
+
+ return {
+ isPropertyDescriptorRecord: ($) =>
+ call(weakSetHas, propertyDescriptorRecords, [$]),
+ toPropertyDescriptorRecord: (Obj) => {
+ if (type(Obj) !== "object") {
+ // The provided value is not an object.
+ throw new TypeError(
+ `Piscēs: Cannot convert primitive to property descriptor: ${O}.`,
+ );
+ } else {
+ // The provided value is an object.
+ const desc = create(null);
+ if ("enumerable" in Obj) {
+ // An enumerable property is specified.
+ defineOwnDataProperty(desc, "enumerable", !!Obj.enumerable);
+ } else {
+ // An enumerable property is not specified.
+ /* do nothing */
+ }
+ if ("configurable" in Obj) {
+ // A configurable property is specified.
+ defineOwnDataProperty(
+ desc,
+ "configurable",
+ !!Obj.configurable,
+ );
+ } else {
+ // A configurable property is not specified.
+ /* do nothing */
+ }
+ if ("value" in Obj) {
+ // A value property is specified.
+ defineOwnDataProperty(desc, "value", Obj.value);
+ } else {
+ // A value property is not specified.
+ /* do nothing */
+ }
+ if ("writable" in Obj) {
+ // A writable property is specified.
+ defineOwnDataProperty(desc, "writable", !!Obj.writable);
+ } else {
+ // A writable property is not specified.
+ /* do nothing */
+ }
+ if ("get" in Obj) {
+ // A get property is specified.
+ const getter = Obj.get;
+ if (getter !== UNDEFINED && typeof getter !== "function") {
+ // The getter is not callable.
+ throw new TypeError("Piscēs: Getters must be callable.");
+ } else {
+ // The getter is callable.
+ defineOwnDataProperty(desc, "get", Obj.get);
+ }
+ } else {
+ // A get property is not specified.
+ /* do nothing */
+ }
+ if ("set" in Obj) {
+ // A set property is specified.
+ const setter = Obj.set;
+ if (setter !== UNDEFINED && typeof setter !== "function") {
+ // The setter is not callable.
+ throw new TypeError("Piscēs: Setters must be callable.");
+ } else {
+ // The setter is callable.
+ defineOwnDataProperty(desc, "set", Obj.set);
+ }
+ } else {
+ // A set property is not specified.
+ /* do nothing */
+ }
+ if (
+ ("get" in desc || "set" in desc) &&
+ ("value" in desc || "writable" in desc)
+ ) {
+ // Both accessor and data attributes have been defined.
+ throw new TypeError(
+ "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
+ );
+ } else {
+ // The property descriptor is valid.
+ const record = new proxyConstructor(
+ desc,
+ propertyDescriptorProxyHandler,
+ );
+ call(weakSetAdd, propertyDescriptorRecords, [record]);
+ return record;
+ }
+ }
+ },
+ };
+})();
+
/**
* Returns whether the provided value is an unfrozen object.
*
return object($);
}
};
-
-/**
- * Returns the property key (symbol or string) corresponding to the
- * provided value.
- */
-export const toPropertyKey = ($) => {
- const key = toPrimitive($, "string");
- return typeof key === "symbol" ? key : `${key}`;
-};
import {
assert,
assertEquals,
+ assertNotStrictEquals,
assertSpyCall,
assertSpyCalls,
assertStrictEquals,
isArraylikeObject,
isConcatSpreadableObject,
isExtensibleObject,
+ isPropertyDescriptorRecord,
isUnfrozenObject,
isUnsealedObject,
LazyLoader,
setPropertyValues,
setPrototype,
toObject,
- toPropertyKey,
+ toPropertyDescriptorRecord,
} from "./object.js";
describe("LazyLoader", () => {
});
});
+describe("isPropertyDescriptorRecord", () => {
+ it("[[Call]] returns true for objects created by toPropertyDescriptorRecord", () => {
+ assertStrictEquals(
+ isPropertyDescriptorRecord(toPropertyDescriptorRecord({})),
+ true,
+ );
+ });
+
+ it("[[Get]] returns false for other objects", () => {
+ assertStrictEquals(
+ isPropertyDescriptorRecord(Object.create(null)),
+ false,
+ );
+ });
+
+ it("[[Get]] returns false for undefined", () => {
+ assertStrictEquals(isPropertyDescriptorRecord(undefined), false);
+ });
+
+ it("[[Construct]] throws an error", () => {
+ assertThrows(() => new isPropertyDescriptorRecord({}));
+ });
+
+ describe(".length", () => {
+ it("[[Get]] returns the correct length", () => {
+ assertStrictEquals(isPropertyDescriptorRecord.length, 1);
+ });
+ });
+
+ describe(".name", () => {
+ it("[[Get]] returns the correct name", () => {
+ assertStrictEquals(
+ isPropertyDescriptorRecord.name,
+ "isPropertyDescriptorRecord",
+ );
+ });
+ });
+});
+
describe("isUnfrozenObject", () => {
it("[[Call]] returns true for unfrozen objects", () => {
assertStrictEquals(isUnfrozenObject({}), true);
});
});
-describe("toPropertyKey", () => {
- it("returns a string or symbol", () => {
- const sym = Symbol();
- assertStrictEquals(toPropertyKey(sym), sym);
- assertStrictEquals(
- toPropertyKey(new String("success")),
- "success",
- );
+describe("toPropertyDescriptorRecord", () => {
+ it("[[Call]] creates a new property descriptor record", () => {
+ const obj = {};
+ const desc = toPropertyDescriptorRecord(obj);
+ assertEquals(obj, desc);
+ assertNotStrictEquals(obj, desc);
});
- it("favours the `toString` representation", () => {
- assertStrictEquals(
- toPropertyKey({
- toString() {
- return "success";
- },
- valueOf() {
- return "failure";
- },
+ it("[[Call]] coerces the values", () => {
+ assertEquals(
+ toPropertyDescriptorRecord({
+ configurable: undefined,
+ enumerable: undefined,
+ writable: undefined,
}),
- "success",
+ { configurable: false, enumerable: false, writable: false },
);
});
+ it("[[Construct]] throws for primitives", () => {
+ assertThrows(() => toPropertyDescriptorRecord(undefined));
+ assertThrows(() => toPropertyDescriptorRecord("failure"));
+ });
+
it("[[Construct]] throws an error", () => {
- assertThrows(() => new toPropertyKey(""));
+ assertThrows(() => new toPropertyDescriptorRecord({}));
});
describe(".length", () => {
it("[[Get]] returns the correct length", () => {
- assertStrictEquals(toPropertyKey.length, 1);
+ assertStrictEquals(toPropertyDescriptorRecord.length, 1);
});
});
describe(".name", () => {
it("[[Get]] returns the correct name", () => {
- assertStrictEquals(toPropertyKey.name, "toPropertyKey");
+ assertStrictEquals(
+ toPropertyDescriptorRecord.name,
+ "toPropertyDescriptorRecord",
+ );
+ });
+ });
+
+ describe("~configurable", () => {
+ it("[[DefineOwnProperty]] coerces to a boolean", () => {
+ const desc = toPropertyDescriptorRecord({});
+ Object.defineProperty(desc, "configurable", {});
+ assertStrictEquals(desc.configurable, false);
+ });
+
+ it("[[DefineOwnProperty]] throws for accessor properties", () => {
+ const desc = toPropertyDescriptorRecord({});
+ assertThrows(() =>
+ Object.defineProperty(desc, "configurable", { get: undefined })
+ );
+ });
+
+ it("[[Set]] coerces to a boolean", () => {
+ const desc = toPropertyDescriptorRecord({});
+ desc.configurable = undefined;
+ assertStrictEquals(desc.configurable, false);
+ });
+
+ it("[[Delete]] works", () => {
+ const desc = toPropertyDescriptorRecord({ configurable: false });
+ delete desc.configurable;
+ assert(!("configurable" in desc));
+ });
+ });
+
+ describe("~enumerable", () => {
+ it("[[DefineOwnProperty]] coerces to a boolean", () => {
+ const desc = toPropertyDescriptorRecord({});
+ Object.defineProperty(desc, "enumerable", {});
+ assertStrictEquals(desc.enumerable, false);
+ });
+
+ it("[[DefineOwnProperty]] throws for accessor properties", () => {
+ const desc = toPropertyDescriptorRecord({});
+ assertThrows(() =>
+ Object.defineProperty(desc, "enumerable", { get: undefined })
+ );
+ });
+
+ it("[[Set]] coerces to a boolean", () => {
+ const desc = toPropertyDescriptorRecord({});
+ desc.enumerable = undefined;
+ assertStrictEquals(desc.enumerable, false);
+ });
+
+ it("[[Delete]] works", () => {
+ const desc = toPropertyDescriptorRecord({ enumerable: false });
+ delete desc.enumerable;
+ assert(!("enumerable" in desc));
+ });
+ });
+
+ describe("~get", () => {
+ it("[[DefineOwnProperty]] works", () => {
+ const desc = toPropertyDescriptorRecord({});
+ Object.defineProperty(desc, "get", {});
+ assertStrictEquals(desc.get, undefined);
+ });
+
+ it("[[DefineOwnProperty]] throws for accessor properties", () => {
+ const desc = toPropertyDescriptorRecord({});
+ assertThrows(() =>
+ Object.defineProperty(desc, "get", { get: undefined })
+ );
+ });
+
+ it("[[DefineOwnProperty]] throws if not callable or undefined", () => {
+ const desc = toPropertyDescriptorRecord({});
+ assertThrows(
+ () => Object.defineProperty(desc, "get", { value: null }),
+ );
+ });
+
+ it("[[DefineOwnProperty]] throws if a data property is defined", () => {
+ const desc = toPropertyDescriptorRecord({ value: undefined });
+ assertThrows(() => Object.defineProperty(desc, "get", {}));
+ });
+
+ it("[[Set]] works", () => {
+ const desc = toPropertyDescriptorRecord({});
+ const fn = () => {};
+ desc.get = fn;
+ assertStrictEquals(desc.get, fn);
+ });
+
+ it("[[Set]] throws if not callable or undefined", () => {
+ const desc = toPropertyDescriptorRecord({});
+ assertThrows(() => desc.get = null);
+ });
+
+ it("[[Set]] throws if a data property is defined", () => {
+ const desc = toPropertyDescriptorRecord({ value: undefined });
+ assertThrows(() => desc.get = undefined);
+ });
+
+ it("[[Delete]] works", () => {
+ const desc = toPropertyDescriptorRecord({ get: undefined });
+ delete desc.get;
+ assert(!("get" in desc));
+ });
+ });
+
+ describe("~set", () => {
+ it("[[DefineOwnProperty]] works", () => {
+ const desc = toPropertyDescriptorRecord({});
+ Object.defineProperty(desc, "set", {});
+ assertStrictEquals(desc.set, undefined);
+ });
+
+ it("[[DefineOwnProperty]] throws for accessor properties", () => {
+ const desc = toPropertyDescriptorRecord({});
+ assertThrows(() =>
+ Object.defineProperty(desc, "set", { get: undefined })
+ );
+ });
+
+ it("[[DefineOwnProperty]] throws if not callable or undefined", () => {
+ const desc = toPropertyDescriptorRecord({});
+ assertThrows(
+ () => Object.defineProperty(desc, "set", { value: null }),
+ );
+ });
+
+ it("[[DefineOwnProperty]] throws if a data property is defined", () => {
+ const desc = toPropertyDescriptorRecord({ value: undefined });
+ assertThrows(() => Object.defineProperty(desc, "set", {}));
+ });
+
+ it("[[Set]] works", () => {
+ const desc = toPropertyDescriptorRecord({});
+ const fn = (_) => {};
+ desc.set = fn;
+ assertStrictEquals(desc.set, fn);
+ });
+
+ it("[[Set]] throws if not callable or undefined", () => {
+ const desc = toPropertyDescriptorRecord({});
+ assertThrows(() => desc.set = null);
+ });
+
+ it("[[Set]] throws if a data property is defined", () => {
+ const desc = toPropertyDescriptorRecord({ value: undefined });
+ assertThrows(() => desc.set = undefined);
+ });
+
+ it("[[Delete]] works", () => {
+ const desc = toPropertyDescriptorRecord({ set: undefined });
+ delete desc.set;
+ assert(!("set" in desc));
+ });
+ });
+
+ describe("~value", () => {
+ it("[[DefineOwnProperty]] works", () => {
+ const desc = toPropertyDescriptorRecord({});
+ Object.defineProperty(desc, "value", {});
+ assertStrictEquals(desc.value, undefined);
+ });
+
+ it("[[DefineOwnProperty]] throws for accessor properties", () => {
+ const desc = toPropertyDescriptorRecord({});
+ assertThrows(() =>
+ Object.defineProperty(desc, "value", { get: undefined })
+ );
+ });
+
+ it("[[DefineOwnProperty]] throws if an accessor property is defined", () => {
+ const desc = toPropertyDescriptorRecord({ get: undefined });
+ assertThrows(() => Object.defineProperty(desc, "value", {}));
+ });
+
+ it("[[Set]] works", () => {
+ const desc = toPropertyDescriptorRecord({});
+ desc.value = "success";
+ assertStrictEquals(desc.value, "success");
+ });
+
+ it("[[Set]] throws if an accessor property is defined", () => {
+ const desc = toPropertyDescriptorRecord({ get: undefined });
+ assertThrows(() => desc.value = null);
+ });
+
+ it("[[Delete]] works", () => {
+ const desc = toPropertyDescriptorRecord({ value: undefined });
+ delete desc.value;
+ assert(!("value" in desc));
+ });
+ });
+
+ describe("~writable", () => {
+ it("[[DefineOwnProperty]] coerces to a boolean", () => {
+ const desc = toPropertyDescriptorRecord({});
+ Object.defineProperty(desc, "writable", {});
+ assertStrictEquals(desc.writable, false);
+ });
+
+ it("[[DefineOwnProperty]] throws for accessor properties", () => {
+ const desc = toPropertyDescriptorRecord({});
+ assertThrows(() =>
+ Object.defineProperty(desc, "writable", { get: undefined })
+ );
+ });
+
+ it("[[DefineOwnProperty]] throws if an accessor property is defined", () => {
+ const desc = toPropertyDescriptorRecord({ get: undefined });
+ assertThrows(() => Object.defineProperty(desc, "writable", {}));
+ });
+
+ it("[[Set]] coerces to a boolean", () => {
+ const desc = toPropertyDescriptorRecord({});
+ desc.writable = undefined;
+ assertStrictEquals(desc.writable, false);
+ });
+
+ it("[[Set]] throws if an accessor property is defined", () => {
+ const desc = toPropertyDescriptorRecord({ get: undefined });
+ assertThrows(() => desc.writable = false);
+ });
+
+ it("[[Delete]] works", () => {
+ const desc = toPropertyDescriptorRecord({ writable: false });
+ delete desc.writable;
+ assert(!("writable" in desc));
});
});
});
!("get" in Desc || "set" in Desc || "value" in Desc ||
"writable" in Desc);
-export const {
- /**
- * Returns whether the provided value is a property descriptor record
- * as created by `toPropertyDescriptor`.
- *
- * ※ This function is provided to enable inspection of whether an
- * object uses the property descriptor record proxy implementation,
- * not as a general test of whether an object satisfies the
- * requirements for property descriptors. In most cases, a more
- * specific test, like `isAccessorDescriptor`, `isDataDescriptor`, or
- * `isGenericDescriptor`, is preferrable.
- */
- isPropertyDescriptorRecord,
-
- /**
- * Converts the provided value to a property descriptor record.
- *
- * ※ The prototype of a property descriptor record is always `null`.
- *
- * ※ Actually constructing a property descriptor object using this
- * class is only necessary if you need strict guarantees about the
- * types of its properties; the resulting object is proxied to ensure
- * the types match what one would expect from composing
- * FromPropertyDescriptor and ToPropertyDescriptor in the Ecmascript
- * specification.
- */
- toPropertyDescriptor,
-} = (() => {
- const {
- assign: setPropertyValues,
- create: objectCreate,
- defineProperty: defineOwnProperty,
- } = Object;
- const {
- apply: call,
- defineProperty: reflectDefineProperty,
- setPrototypeOf: reflectSetPrototypeOf,
- } = Reflect;
- const proxyConstructor = Proxy;
- const { add: weakSetAdd, has: weakSetHas } = WeakSet.prototype;
- const propertyDescriptorRecords = new WeakSet();
- const coercePropertyDescriptorValue = (P, V) => {
- switch (P) {
- case "configurable":
- case "enumerable":
- case "writable":
- return !!V;
- case "value":
- return V;
- case "get":
- if (V !== undefined && typeof V !== "function") {
- throw new TypeError(
- "Piscēs: Getters must be callable.",
- );
- } else {
- return V;
- }
- case "set":
- if (V !== undefined && typeof V !== "function") {
- throw new TypeError(
- "Piscēs: Setters must be callable.",
- );
- } else {
- return V;
- }
- default:
- return V;
- }
- };
- const propertyDescriptorProxyHandler = Object.freeze(
- Object.assign(
- Object.create(null),
- {
- defineProperty(O, P, Desc) {
- if (
- P === "configurable" || P === "enumerable" ||
- P === "writable" || P === "value" ||
- P === "get" || P === "set"
- ) {
- // P is a property descriptor attribute.
- const desc = setPropertyValues(objectCreate(null), Desc);
- if ("get" in desc || "set" in desc) {
- // Desc is an accessor property descriptor.
- throw new TypeError(
- "Piscēs: Property descriptor attributes must be data properties.",
- );
- } else if ("value" in desc || !(P in O)) {
- // Desc has a value or P does not already exist on O.
- desc.value = coercePropertyDescriptorValue(
- P,
- desc.value,
- );
- } else {
- // Desc is not an accessor property descriptor and has no
- // value, but an existing value is present on O.
- /* do nothing */
- }
- const isAccessorDescriptor = "get" === P || "set" === P ||
- "get" in O || "set" in O;
- const isDataDescriptor = "value" === P ||
- "writable" === P ||
- "value" in O || "writable" in O;
- if (isAccessorDescriptor && isDataDescriptor) {
- // Both accessor and data attributes will be present on O
- // after defining P.
- throw new TypeError(
- "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
- );
- } else {
- // P can be safely defined on O.
- return reflectDefineProperty(O, P, desc);
- }
- } else {
- // P is not a property descriptor attribute.
- return reflectDefineProperty(O, P, Desc);
- }
- },
- setPrototypeOf(O, V) {
- if (V !== null) {
- // V is not the property descriptor prototype.
- return false;
- } else {
- // V is the property descriptor prototype.
- return reflectSetPrototypeOf(O, V);
- }
- },
- },
- ),
- );
-
- return {
- isPropertyDescriptorRecord: ($) =>
- call(weakSetHas, propertyDescriptorRecords, [$]),
- toPropertyDescriptor: (Obj) => {
- if (type(Obj) !== "object") {
- // The provided value is not an object.
- throw new TypeError(
- `Piscēs: Cannot convert primitive to property descriptor: ${O}.`,
- );
- } else {
- // The provided value is an object.
- const desc = objectCreate(null);
- if ("enumerable" in Obj) {
- // An enumerable property is specified.
- defineOwnProperty(desc, "enumerable", {
- configurable: true,
- enumerable: true,
- value: !!Obj.enumerable,
- writable: true,
- });
- } else {
- // An enumerable property is not specified.
- /* do nothing */
- }
- if ("configurable" in Obj) {
- // A configurable property is specified.
- defineOwnProperty(desc, "configurable", {
- configurable: true,
- enumerable: true,
- value: !!Obj.configurable,
- writable: true,
- });
- } else {
- // A configurable property is not specified.
- /* do nothing */
- }
- if ("value" in Obj) {
- // A value property is specified.
- defineOwnProperty(desc, "value", {
- configurable: true,
- enumerable: true,
- value: Obj.value,
- writable: true,
- });
- } else {
- // A value property is not specified.
- /* do nothing */
- }
- if ("writable" in Obj) {
- // A writable property is specified.
- defineOwnProperty(desc, "writable", {
- configurable: true,
- enumerable: true,
- value: !!Obj.writable,
- writable: true,
- });
- } else {
- // A writable property is not specified.
- /* do nothing */
- }
- if ("get" in Obj) {
- // A get property is specified.
- const getter = Obj.get;
- if (getter !== UNDEFINED && typeof getter !== "function") {
- // The getter is not callable.
- throw new TypeError("Piscēs: Getters must be callable.");
- } else {
- // The getter is callable.
- defineOwnProperty(desc, "get", {
- configurable: true,
- enumerable: true,
- value: getter,
- writable: true,
- });
- }
- } else {
- // A get property is not specified.
- /* do nothing */
- }
- if ("set" in Obj) {
- // A set property is specified.
- const setter = Obj.set;
- if (setter !== UNDEFINED && typeof setter !== "function") {
- // The setter is not callable.
- throw new TypeError("Piscēs: Setters must be callable.");
- } else {
- // The setter is callable.
- defineOwnProperty(desc, "set", {
- configurable: true,
- enumerable: true,
- value: setter,
- writable: true,
- });
- }
- } else {
- // A set property is not specified.
- /* do nothing */
- }
- if (
- ("get" in desc || "set" in desc) &&
- ("value" in desc || "writable" in desc)
- ) {
- // Both accessor and data attributes have been defined.
- throw new TypeError(
- "Piscēs: Property descriptors cannot specify both accessor and data attributes.",
- );
- } else {
- // The property descriptor is valid.
- const record = new proxyConstructor(
- desc,
- propertyDescriptorProxyHandler,
- );
- call(weakSetAdd, propertyDescriptorRecords, [record]);
- return record;
- }
- }
- },
- };
-})();
-
export const {
/**
* Returns the primitive value of the provided object per its
"Piscēs: Unable to convert object to primitive",
);
},
- toFunctionName: ($, prefix = undefined) => {
+ toFunctionName: ($, prefix = UNDEFINED) => {
const key = toPrimitive($, "string");
const name = (() => {
if (typeof key === "symbol") {
// The provided value is a symbol; format its description.
const description = call(getSymbolDescription, key, []);
- return description === undefined ? "" : `[${description}]`;
+ return description === UNDEFINED ? "" : `[${description}]`;
} else {
// The provided value not a symbol; convert it to a string
// property key.
return `${key}`;
}
})();
- return prefix !== undefined ? `${prefix} ${name}` : name;
+ return prefix !== UNDEFINED ? `${prefix} ${name}` : name;
},
toPrimitive: ($, preferredType = "default") => {
const hint = `${preferredType}`;
);
} else if (type($) === "object") {
// The provided value is an object.
- const exoticToPrim = $[TO_PRIMITIVE] ?? undefined;
- if (exoticToPrim !== undefined) {
+ const exoticToPrim = $[TO_PRIMITIVE] ?? UNDEFINED;
+ if (exoticToPrim !== UNDEFINED) {
// The provided value has an exotic primitive conversion
// method.
if (typeof exoticToPrim !== "function") {
};
})();
+/**
+ * Returns the property key (symbol or string) corresponding to the
+ * provided value.
+ */
+export const toPropertyKey = ($) => {
+ const key = toPrimitive($, "string");
+ return typeof key === "symbol" ? key : `${key}`;
+};
+
/**
* Returns a lowercase string identifying the type of the provided
* value.
// file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
import {
- assert,
assertEquals,
- assertNotStrictEquals,
assertStrictEquals,
assertThrows,
describe,
isDataDescriptor,
isFullyPopulatedDescriptor,
isGenericDescriptor,
- isPropertyDescriptorRecord,
ITERATOR,
LN10,
LN2,
toIndex,
toLength,
toPrimitive,
- toPropertyDescriptor,
+ toPropertyKey,
type,
UNDEFINED,
UNSCOPABLES,
});
});
-describe("isPropertyDescriptorRecord", () => {
- it("[[Call]] returns true for objects created by toPropertyDescriptor", () => {
- assertStrictEquals(
- isPropertyDescriptorRecord(toPropertyDescriptor({})),
- true,
- );
- });
-
- it("[[Get]] returns false for other objects", () => {
- assertStrictEquals(
- isPropertyDescriptorRecord(Object.create(null)),
- false,
- );
- });
-
- it("[[Get]] returns false for undefined", () => {
- assertStrictEquals(isPropertyDescriptorRecord(undefined), false);
- });
-
- it("[[Construct]] throws an error", () => {
- assertThrows(() => new isPropertyDescriptorRecord({}));
- });
-
- describe(".length", () => {
- it("[[Get]] returns the correct length", () => {
- assertStrictEquals(isPropertyDescriptorRecord.length, 1);
- });
- });
-
- describe(".name", () => {
- it("[[Get]] returns the correct name", () => {
- assertStrictEquals(
- isPropertyDescriptorRecord.name,
- "isPropertyDescriptorRecord",
- );
- });
- });
-});
-
describe("ordinaryToPrimitive", () => {
it("[[Call]] prefers `valueOf` by default", () => {
const obj = {
});
});
-describe("toPropertyDescriptor", () => {
- it("[[Call]] creates a new property descriptor record", () => {
- const obj = {};
- const desc = toPropertyDescriptor(obj);
- assertEquals(obj, desc);
- assertNotStrictEquals(obj, desc);
+describe("toPropertyKey", () => {
+ it("returns a string or symbol", () => {
+ const sym = Symbol();
+ assertStrictEquals(toPropertyKey(sym), sym);
+ assertStrictEquals(
+ toPropertyKey(new String("success")),
+ "success",
+ );
});
- it("[[Call]] coerces the values", () => {
- assertEquals(
- toPropertyDescriptor({
- configurable: undefined,
- enumerable: undefined,
- writable: undefined,
+ it("favours the `toString` representation", () => {
+ assertStrictEquals(
+ toPropertyKey({
+ toString() {
+ return "success";
+ },
+ valueOf() {
+ return "failure";
+ },
}),
- { configurable: false, enumerable: false, writable: false },
+ "success",
);
});
- it("[[Construct]] throws for primitives", () => {
- assertThrows(() => toPropertyDescriptor(undefined));
- assertThrows(() => toPropertyDescriptor("failure"));
- });
-
it("[[Construct]] throws an error", () => {
- assertThrows(() => new toPropertyDescriptor({}));
+ assertThrows(() => new toPropertyKey(""));
});
describe(".length", () => {
it("[[Get]] returns the correct length", () => {
- assertStrictEquals(toPropertyDescriptor.length, 1);
+ assertStrictEquals(toPropertyKey.length, 1);
});
});
describe(".name", () => {
it("[[Get]] returns the correct name", () => {
- assertStrictEquals(
- toPropertyDescriptor.name,
- "toPropertyDescriptor",
- );
- });
- });
-
- describe("~configurable", () => {
- it("[[DefineOwnProperty]] coerces to a boolean", () => {
- const desc = toPropertyDescriptor({});
- Object.defineProperty(desc, "configurable", {});
- assertStrictEquals(desc.configurable, false);
- });
-
- it("[[DefineOwnProperty]] throws for accessor properties", () => {
- const desc = toPropertyDescriptor({});
- assertThrows(() =>
- Object.defineProperty(desc, "configurable", { get: undefined })
- );
- });
-
- it("[[Set]] coerces to a boolean", () => {
- const desc = toPropertyDescriptor({});
- desc.configurable = undefined;
- assertStrictEquals(desc.configurable, false);
- });
-
- it("[[Delete]] works", () => {
- const desc = toPropertyDescriptor({ configurable: false });
- delete desc.configurable;
- assert(!("configurable" in desc));
- });
- });
-
- describe("~enumerable", () => {
- it("[[DefineOwnProperty]] coerces to a boolean", () => {
- const desc = toPropertyDescriptor({});
- Object.defineProperty(desc, "enumerable", {});
- assertStrictEquals(desc.enumerable, false);
- });
-
- it("[[DefineOwnProperty]] throws for accessor properties", () => {
- const desc = toPropertyDescriptor({});
- assertThrows(() =>
- Object.defineProperty(desc, "enumerable", { get: undefined })
- );
- });
-
- it("[[Set]] coerces to a boolean", () => {
- const desc = toPropertyDescriptor({});
- desc.enumerable = undefined;
- assertStrictEquals(desc.enumerable, false);
- });
-
- it("[[Delete]] works", () => {
- const desc = toPropertyDescriptor({ enumerable: false });
- delete desc.enumerable;
- assert(!("enumerable" in desc));
- });
- });
-
- describe("~get", () => {
- it("[[DefineOwnProperty]] works", () => {
- const desc = toPropertyDescriptor({});
- Object.defineProperty(desc, "get", {});
- assertStrictEquals(desc.get, undefined);
- });
-
- it("[[DefineOwnProperty]] throws for accessor properties", () => {
- const desc = toPropertyDescriptor({});
- assertThrows(() =>
- Object.defineProperty(desc, "get", { get: undefined })
- );
- });
-
- it("[[DefineOwnProperty]] throws if not callable or undefined", () => {
- const desc = toPropertyDescriptor({});
- assertThrows(
- () => Object.defineProperty(desc, "get", { value: null }),
- );
- });
-
- it("[[DefineOwnProperty]] throws if a data property is defined", () => {
- const desc = toPropertyDescriptor({ value: undefined });
- assertThrows(() => Object.defineProperty(desc, "get", {}));
- });
-
- it("[[Set]] works", () => {
- const desc = toPropertyDescriptor({});
- const fn = () => {};
- desc.get = fn;
- assertStrictEquals(desc.get, fn);
- });
-
- it("[[Set]] throws if not callable or undefined", () => {
- const desc = toPropertyDescriptor({});
- assertThrows(() => desc.get = null);
- });
-
- it("[[Set]] throws if a data property is defined", () => {
- const desc = toPropertyDescriptor({ value: undefined });
- assertThrows(() => desc.get = undefined);
- });
-
- it("[[Delete]] works", () => {
- const desc = toPropertyDescriptor({ get: undefined });
- delete desc.get;
- assert(!("get" in desc));
- });
- });
-
- describe("~set", () => {
- it("[[DefineOwnProperty]] works", () => {
- const desc = toPropertyDescriptor({});
- Object.defineProperty(desc, "set", {});
- assertStrictEquals(desc.set, undefined);
- });
-
- it("[[DefineOwnProperty]] throws for accessor properties", () => {
- const desc = toPropertyDescriptor({});
- assertThrows(() =>
- Object.defineProperty(desc, "set", { get: undefined })
- );
- });
-
- it("[[DefineOwnProperty]] throws if not callable or undefined", () => {
- const desc = toPropertyDescriptor({});
- assertThrows(
- () => Object.defineProperty(desc, "set", { value: null }),
- );
- });
-
- it("[[DefineOwnProperty]] throws if a data property is defined", () => {
- const desc = toPropertyDescriptor({ value: undefined });
- assertThrows(() => Object.defineProperty(desc, "set", {}));
- });
-
- it("[[Set]] works", () => {
- const desc = toPropertyDescriptor({});
- const fn = (_) => {};
- desc.set = fn;
- assertStrictEquals(desc.set, fn);
- });
-
- it("[[Set]] throws if not callable or undefined", () => {
- const desc = toPropertyDescriptor({});
- assertThrows(() => desc.set = null);
- });
-
- it("[[Set]] throws if a data property is defined", () => {
- const desc = toPropertyDescriptor({ value: undefined });
- assertThrows(() => desc.set = undefined);
- });
-
- it("[[Delete]] works", () => {
- const desc = toPropertyDescriptor({ set: undefined });
- delete desc.set;
- assert(!("set" in desc));
- });
- });
-
- describe("~value", () => {
- it("[[DefineOwnProperty]] works", () => {
- const desc = toPropertyDescriptor({});
- Object.defineProperty(desc, "value", {});
- assertStrictEquals(desc.value, undefined);
- });
-
- it("[[DefineOwnProperty]] throws for accessor properties", () => {
- const desc = toPropertyDescriptor({});
- assertThrows(() =>
- Object.defineProperty(desc, "value", { get: undefined })
- );
- });
-
- it("[[DefineOwnProperty]] throws if an accessor property is defined", () => {
- const desc = toPropertyDescriptor({ get: undefined });
- assertThrows(() => Object.defineProperty(desc, "value", {}));
- });
-
- it("[[Set]] works", () => {
- const desc = toPropertyDescriptor({});
- desc.value = "success";
- assertStrictEquals(desc.value, "success");
- });
-
- it("[[Set]] throws if an accessor property is defined", () => {
- const desc = toPropertyDescriptor({ get: undefined });
- assertThrows(() => desc.value = null);
- });
-
- it("[[Delete]] works", () => {
- const desc = toPropertyDescriptor({ value: undefined });
- delete desc.value;
- assert(!("value" in desc));
- });
- });
-
- describe("~writable", () => {
- it("[[DefineOwnProperty]] coerces to a boolean", () => {
- const desc = toPropertyDescriptor({});
- Object.defineProperty(desc, "writable", {});
- assertStrictEquals(desc.writable, false);
- });
-
- it("[[DefineOwnProperty]] throws for accessor properties", () => {
- const desc = toPropertyDescriptor({});
- assertThrows(() =>
- Object.defineProperty(desc, "writable", { get: undefined })
- );
- });
-
- it("[[DefineOwnProperty]] throws if an accessor property is defined", () => {
- const desc = toPropertyDescriptor({ get: undefined });
- assertThrows(() => Object.defineProperty(desc, "writable", {}));
- });
-
- it("[[Set]] coerces to a boolean", () => {
- const desc = toPropertyDescriptor({});
- desc.writable = undefined;
- assertStrictEquals(desc.writable, false);
- });
-
- it("[[Set]] throws if an accessor property is defined", () => {
- const desc = toPropertyDescriptor({ get: undefined });
- assertThrows(() => desc.writable = false);
- });
-
- it("[[Delete]] works", () => {
- const desc = toPropertyDescriptor({ writable: false });
- delete desc.writable;
- assert(!("writable" in desc));
+ assertStrictEquals(toPropertyKey.name, "toPropertyKey");
});
});
});