]> Lady’s Gitweb - Pisces/commitdiff
Add makeIllegalConstructor to functions.js
authorLady <redacted>
Mon, 4 Sep 2023 19:54:46 +0000 (15:54 -0400)
committerLady <redacted>
Mon, 4 Sep 2023 19:54:46 +0000 (15:54 -0400)
function.js
function.test.js

index d1d337a18d1c19d1602b9415e20f8fc2fb4c83d7..5da9f6fc4891a356b7ce5482d16d744477ee36fe 100644 (file)
@@ -27,6 +27,16 @@ export const {
    * ※ This is effectively an alias for `Function::call.bind`.
    */
   makeCallable,
+
+  /**
+   * Returns a constructor which throws whenever it is called but has
+   * the same `.name` and `.prototype` as the provided value.
+   *
+   * If a second argument is provided, the returned constructor will
+   * use that as its prototype; otherwise, it will use the prototype of
+   * the provided value.
+   */
+  makeIllegalConstructor,
 } = (() => {
   // ☡ Because these functions are used to initialize module constants,
   // they can’t depend on imports from elsewhere.
@@ -41,6 +51,7 @@ export const {
     create: objectCreate,
     defineProperties: defineOwnProperties,
     getPrototypeOf: getPrototype,
+    setPrototypeOf: setPrototype,
   } = Object;
   const { [ITERATOR]: arrayIterator } = Array.prototype;
   const {
@@ -71,6 +82,31 @@ export const {
         length: { value: $.length + 1 },
         name: { value: name ?? $.name ?? "" },
       }),
+    makeIllegalConstructor: ($, proto = undefined) => {
+      const constructor = function () {
+        throw new TypeError("Illegal constructor");
+      };
+      if ($ == null && proto === undefined) {
+        // The provided argument is nullish and no explicit prototype
+        // was provided.
+        //
+        // Do not modify the prototype of the generated constructor.
+        /* do nothing */
+      } else {
+        // The provided argument is not nullish or an explicit
+        // prototype was provided.
+        //
+        // Set the prototype of the generated constructor to match.
+        setPrototype(
+          constructor,
+          proto === undefined ? getPrototype($) : proto,
+        );
+      }
+      return defineOwnProperties(constructor, {
+        name: { value: $?.name ?? "" },
+        prototype: { value: $?.prototype ?? {}, writable: false },
+      });
+    },
   };
 })();
 
index 35f7f37bc385dd538b82803e945449e20d9a90b7..2260b5a216ab666c818e7a89f9ce3e2e8960db7d 100644 (file)
@@ -8,6 +8,7 @@
 // file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
 
 import {
+  assert,
   assertEquals,
   assertStrictEquals,
   assertThrows,
@@ -23,6 +24,7 @@ import {
   isCallable,
   isConstructor,
   makeCallable,
+  makeIllegalConstructor,
   ordinaryHasInstance,
 } from "./function.js";
 
@@ -427,6 +429,112 @@ describe("makeCallable", () => {
   });
 });
 
+describe("makeIllegalConstructor", () => {
+  it("[[Call]] returns a constructor", () => {
+    assert(isConstructor(makeIllegalConstructor()));
+  });
+
+  it("[[Call]] works as expected when provided with no arguments", () => {
+    const constructor = makeIllegalConstructor();
+    assertStrictEquals(
+      Object.getPrototypeOf(constructor),
+      Function.prototype,
+    );
+    assertStrictEquals(
+      Object.getPrototypeOf(constructor.prototype),
+      Object.prototype,
+    );
+    assertEquals(constructor.prototype, {});
+    assert(
+      !Object.getOwnPropertyDescriptor(constructor, "prototype")
+        .writable,
+    );
+    assertStrictEquals(constructor.name, "");
+  });
+
+  it("[[Call]] returns a correctly‐formed constructor when provided one argument", () => {
+    const constructorPrototype = Object.create(null, {
+      name: { value: "etaoin" },
+      prototype: { value: {} },
+    });
+    const constructor = makeIllegalConstructor(
+      Object.create(constructorPrototype),
+    );
+    assert(isConstructor(constructor));
+    assertStrictEquals(
+      Object.getPrototypeOf(constructor),
+      constructorPrototype,
+    );
+    assert(Object.hasOwn(constructor, "prototype"));
+    assertEquals(
+      constructor.prototype,
+      constructorPrototype.prototype,
+    );
+    assert(
+      !Object.getOwnPropertyDescriptor(constructor, "prototype")
+        .writable,
+    );
+    assertStrictEquals(constructor.name, "etaoin");
+  });
+
+  it("[[Call]] allows the second argument to override the prototype of the constructor", () => {
+    const constructorPrototype = Object.create(null, {
+      name: { value: "etaoin" },
+      prototype: { value: {} },
+    });
+    const expectedPrototype = Object.create(null, {
+      name: { value: "shrdlu" },
+      prototype: { value: {} },
+    });
+    const constructor = makeIllegalConstructor(
+      Object.create(constructorPrototype),
+      expectedPrototype,
+    );
+    assert(isConstructor(constructor));
+    assertStrictEquals(
+      Object.getPrototypeOf(constructor),
+      expectedPrototype,
+    );
+    assert(Object.hasOwn(constructor, "prototype"));
+    assertEquals(
+      constructor.prototype,
+      constructorPrototype.prototype,
+    );
+    assert(
+      !Object.getOwnPropertyDescriptor(constructor, "prototype")
+        .writable,
+    );
+    assertStrictEquals(constructor.name, "etaoin");
+  });
+
+  it("[[Construct]] throws an error", () => {
+    assertThrows(() => new makeIllegalConstructor(function () {}));
+  });
+
+  describe("~", () => {
+    it("[[Call]] throws an error", () => {
+      assertThrows(() => {
+        makeIllegalConstructor(function () {})();
+      });
+    });
+
+    it("[[Construct]] throws an error", () => {
+      assertThrows(() => {
+        makeIllegalConstructor(function () {})();
+      });
+    });
+  });
+
+  describe(".name", () => {
+    it("[[Get]] returns the correct name", () => {
+      assertStrictEquals(
+        makeIllegalConstructor.name,
+        "makeIllegalConstructor",
+      );
+    });
+  });
+});
+
 describe("ordinaryHasInstance", () => {
   it("[[Call]] walks the prototype chain", () => {
     const constructor = class {
This page took 0.024742 seconds and 4 git commands to generate.