Skip to content

Commit

Permalink
feat: klass.extends (no semantic yet)
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh-Cena committed Apr 24, 2022
1 parent 47d66a7 commit cc0b936
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 46 deletions.
68 changes: 27 additions & 41 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
function klassCreator(body, name) {
function klassCreator(body, name, SuperKlass) {
if (typeof body === "string") {
if (name) {
throw new Error(
`The klass creator already has a name bound as "${name}". You can't re-write its name.`,
);
} else {
throw new Error(
`The klass creator already has a super klass. Please bind the name before attaching super klass.`,
);
}
}
if (typeof body !== "object" || !body)
throw new Error("You can't create a klass with a non-object body.");
if (!isKlass(SuperKlass)) throw new Error("You can only extend klasses.");

const constructor = Object.hasOwn(body, "constructor")
? (() => {
Expand Down Expand Up @@ -58,15 +70,9 @@ function klassCreator(body, name) {
});
Object.defineProperty(SomeKlass, "name", {
value: name,
configurable: true,
enumerable: false,
writable: false,
});
Object.defineProperty(SomeKlass, "length", {
value: constructor.length,
configurable: true,
enumerable: false,
writable: false,
});
if (name) {
Object.defineProperty(SomeKlass.prototype, Symbol.toStringTag, {
Expand All @@ -80,44 +86,24 @@ function klassCreator(body, name) {

export default function klass(bodyOrName) {
if (typeof bodyOrName === "string") {
const nameBoundKlassCreator = (body) => {
if (typeof body === "string") {
throw new Error(
`The klass creator already has a name bound as "${bodyOrName}". You can't re-write its name.`,
);
}
return klassCreator(body, bodyOrName);
};
Object.defineProperty(nameBoundKlassCreator, "boundName", {
value: bodyOrName,
configurable: false,
enumerable: true,
writable: false,
const nameBoundKlassCreator = (body) =>
klassCreator(body, bodyOrName, null);
nameBoundKlassCreator.extends = (SuperKlass) => (body) =>
klassCreator(body, bodyOrName, SuperKlass);
[nameBoundKlassCreator, nameBoundKlassCreator.extends].forEach((o) => {
Object.defineProperty(o, "boundName", {
value: bodyOrName,
configurable: false,
enumerable: true,
writable: false,
});
});
// nameBoundKlassCreator.extends = extend;
return nameBoundKlassCreator;
}
return klassCreator(bodyOrName, "");
return klassCreator(bodyOrName, "", null);
}

// function extend(SuperKlass) {
// if (!isKlass(SuperKlass))
// throw new Error("You can only extend a klass.");
// // eslint-disable-next-line @typescript-eslint/no-this-alias, @typescript-eslint/no-invalid-this
// const klassCreator = this;
// function derivedKlassCreator(body) {
// // eslint-disable-next-line @typescript-eslint/no-invalid-this
// return function extendedKlass(...args) {
// const instance = NewKlass(...args);
// };
// }
// if (klassCreator.boundName)
// derivedKlassCreator.boundName = klassCreator.boundName;
// derivedKlassCreator.baseKlass = SuperKlass;
// return derivedKlassCreator;
// }

// klass.extends = extend;
klass.extends = (SuperKlass) => (body) => klassCreator(body, "", SuperKlass);

const klassMarker = Symbol("klass");

Expand All @@ -127,6 +113,6 @@ Object.defineProperty(klass, Symbol.hasInstance, { value: isKlass });

export function nеw(someKlass) {
if (!isKlass(someKlass))
throw new Error("nеw should only be called on klasses");
throw new Error("nеw should only be called on klasses.");
return someKlass;
}
6 changes: 1 addition & 5 deletions test/index.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @ts-check
/* eslint-disable no-restricted-syntax */

import klass, { nеw, isKlass } from "../src/index.js";

Expand All @@ -17,15 +18,13 @@ describe("klass constructor", () => {
const Animal = klass({ a: 1 });
expect(Object.keys(Animal)).toEqual([]);
expect(Object.getOwnPropertyNames(Animal)).toEqual(
// eslint-disable-next-line no-restricted-syntax
Object.getOwnPropertyNames(class {}),
);
});

it("throws if a klass is newed", () => {
const Animal = klass({ a: 1 });
// @ts-expect-error: for testing
// eslint-disable-next-line no-restricted-syntax
expect(() => new Animal()).toThrowErrorMatchingInlineSnapshot(
`"Please don't new a klass, because we hate new. Call it directly or use the \\"nеw\\" API."`,
);
Expand Down Expand Up @@ -78,11 +77,9 @@ describe("klass constructor", () => {
});

it("ignores existing prototypes of body", () => {
// eslint-disable-next-line no-restricted-syntax
class RealClass {
a = 1;
}
// eslint-disable-next-line no-restricted-syntax
const KlassClone = klass(new RealClass());
const instance = KlassClone();
expect(instance.a).toBe(1);
Expand Down Expand Up @@ -235,7 +232,6 @@ describe("name", () => {

describe("isKlass", () => {
it("rejects non-klasses", () => {
// eslint-disable-next-line no-restricted-syntax
expect(isKlass(class {})).toBe(false);
expect(isKlass({})).toBe(false);
const Foo = klass({});
Expand Down

0 comments on commit cc0b936

Please sign in to comment.