From d8e509f5403b2670fede001266c553996c7ee29d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Wed, 13 Dec 2023 19:55:14 +0100 Subject: [PATCH] Refactoring --- src/tests.ts | 38 ++++++++++++++--- src/trait.ts | 27 ++++++++---- src/util/class.ts | 105 +++++++++++++++++++++++++--------------------- 3 files changed, 110 insertions(+), 60 deletions(-) diff --git a/src/tests.ts b/src/tests.ts index 0e88cba..a959718 100644 --- a/src/tests.ts +++ b/src/tests.ts @@ -1,4 +1,24 @@ -import { mixTraits } from "./trait" +import { AbstractClass } from "type-fest" +import { expresses } from "./trait" + + +function inspectClass(class_: AbstractClass) { + Object.getOwnPropertyNames(class_).forEach(name => { + console.log( + "[static]", + name, + Object.getOwnPropertyDescriptor(class_, name) + ) + }) + + Object.getOwnPropertyNames(class_.prototype).forEach(name => { + console.log( + "[prototype]", + name, + Object.getOwnPropertyDescriptor(class_.prototype, name) + ) + }) +} abstract class Identified { @@ -9,12 +29,18 @@ abstract class Identified { } } +class ImplementsIdentifiable extends Identified { + id!: ID +} + + abstract class Permissible { + static readonly defaultPermissions: string[] = [] permissions: string[] = [] } -class User extends mixTraits( +class User extends expresses( Identified, // Identified, Permissible, @@ -27,11 +53,13 @@ class User extends mixTraits( } } -console.log(Permissible.constructor()) +console.log(new User(BigInt(1))) + +// console.log(Permissible.constructor()) // console.log(Object.getOwnPropertyNames(User.prototype)) -const user1 = new User(BigInt(1)) -const user2 = new User(BigInt(2)) +// const user1 = new User(BigInt(1)) +// const user2 = new User(BigInt(2)) // console.log(user1.equals(user2)) // console.log(user1.permissions) diff --git a/src/trait.ts b/src/trait.ts index 22f199d..2b4b178 100644 --- a/src/trait.ts +++ b/src/trait.ts @@ -1,5 +1,5 @@ import { AbstractClass, Class, UnionToIntersection } from "type-fest" -import { StaticMembers, copyClassProperties, flattenClass } from "./util/class" +import { StaticMembers, copyProperties, flattenClass, getInheritanceHierarchy } from "./util/class" export type Trait = @@ -40,7 +40,7 @@ export const extendAndApplyTraits = < traits: Traits, ) => traits.reduce((class_, trait) => { - copyClassProperties(flattenClass(trait), class_) + copyProperties(flattenClass(trait), class_) return class_ }, class extends classToExtend {}) as ( AbstractClass< @@ -61,15 +61,25 @@ export const extendAndApplyTraits = < ) -export const mixTraits = < +export function expresses< Traits extends readonly Trait[] >( ...traits: Traits -) => - traits.reduce((class_, trait) => { - copyClassProperties(flattenClass(trait), class_) - return class_ - }, class {}) as ( +) { + const class_ = class {} + + traits.forEach(trait => { + getInheritanceHierarchy(trait).forEach(current => { + copyProperties( + current, + class_, + ["name", "length"], + ["constructor"], + ) + }) + }) + + return class_ as unknown as ( Trait< UnionToIntersection< InstanceType< @@ -84,3 +94,4 @@ export const mixTraits = < > > ) +} diff --git a/src/util/class.ts b/src/util/class.ts index aecd83d..518c960 100644 --- a/src/util/class.ts +++ b/src/util/class.ts @@ -10,47 +10,6 @@ import { AbstractClass } from "type-fest" export type StaticMembers = Pick -/** - * Copies properties from one class to another, including static and prototype properties. - * - * @param from - The source class to copy properties from. - * @param to - The destination class to copy properties to. - */ -export function copyClassProperties( - from: AbstractClass, - to: AbstractClass, -) { - Object.getOwnPropertyNames(from).forEach(name => { - // console.log(from, to, name, Object.getOwnPropertyDescriptor(from, name)) - - if (name === "name" - || name === "length" - || name === "prototype" - ) - return - - Object.defineProperty( - to, - name, - Object.getOwnPropertyDescriptor(from, name) || Object.create(null), - ) - }) - - Object.getOwnPropertyNames(from.prototype).forEach(name => { - // console.log(from, to, name, Object.getOwnPropertyDescriptor(from, name)) - - if (name === "constructor") - return - - Object.defineProperty( - to.prototype, - name, - Object.getOwnPropertyDescriptor(from.prototype, name) || Object.create(null), - ) - }) -} - - /** * Flattens the inheritance hierarchy of a given class by copying all properties * from its superclass chain into a single object. @@ -62,11 +21,16 @@ export function copyClassProperties( export function flattenClass< C extends AbstractClass >(class_: C) { - return getInheritanceHierarchy(class_) - .reduce((flattened, current) => { - copyClassProperties(current, flattened) - return flattened - }, class {}) as C + const flattenedClass = class {} as unknown as C + + getInheritanceHierarchy(class_).forEach(current => { + copyProperties(current, flattenedClass) + }) + + copyProperty(class_, flattenedClass, "name") + copyProperty(class_.prototype, flattenedClass.prototype, "constructor") + + return flattenedClass } @@ -74,7 +38,7 @@ export function flattenClass< * Retrieves the inheritance hierarchy of a given class, including itself. * * @param class_ - The class for which the inheritance hierarchy is retrieved. - * @returns An array representing the inheritance hierarchy. + * @returns An array representing the inheritance hierarchy, ordered from the furthest in the hierarchy to `class_` itself. */ export function getInheritanceHierarchy( class_: AbstractClass @@ -96,3 +60,50 @@ export function getInheritanceHierarchy( export function isClass(el: { toString: () => string }) { return Boolean(el.toString().match(/^class(?: [.\S]+)?(?: extends [.\S]+)? {[\s\S]*}$/)) } + + +/** + * Copies properties from one class to another, including static and prototype properties. + * + * @param from - The source class to copy properties from. + * @param to - The destination class to copy properties to. + */ +export function copyProperties( + from: AbstractClass, + to: AbstractClass, + ignoreKeys: string[] = [], + ignorePrototypeKeys: string[] = [], +) { + Object.getOwnPropertyNames(from).forEach(name => { + if (name === "prototype" + || ignoreKeys.find(v => v === name) + ) + return + + // console.log(from, to, name, Object.getOwnPropertyDescriptor(from, name)) + + copyProperty(from, to, name) + }) + + Object.getOwnPropertyNames(from.prototype).forEach(name => { + if (ignorePrototypeKeys.find(v => v === name)) + return + + // console.log(from, to, name, Object.getOwnPropertyDescriptor(from, name)) + + copyProperty(from.prototype, to.prototype, name) + }) +} + + +export function copyProperty( + from: unknown, + to: unknown, + name: string, +) { + Object.defineProperty( + to, + name, + Object.getOwnPropertyDescriptor(from, name) || Object.create(null), + ) +}