Class util refactoring
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Julien Valverdé
2023-12-09 02:50:24 +01:00
parent c6de7004d2
commit e814fa8916
5 changed files with 91 additions and 73 deletions

View File

@@ -1,68 +0,0 @@
import { AbstractClass } from "type-fest"
/**
* Copies all own properties and methods (excluding "length" and "prototype") of one class to another class.
*
* @param from The class whose properties and methods are to be copied.
* @param to The class to which the properties and methods are to be copied.
*/
export function copyClassProperties(
from: AbstractClass<any, any>,
to: AbstractClass<any, any>,
) {
Object.getOwnPropertyNames(from).forEach(name => {
if (name === "length"
|| name === "prototype"
)
return
Object.defineProperty(
to,
name,
Object.getOwnPropertyDescriptor(from, name) || Object.create(null),
)
})
Object.getOwnPropertyNames(from.prototype).forEach(name => {
Object.defineProperty(
to.prototype,
name,
Object.getOwnPropertyDescriptor(from.prototype, name) || Object.create(null),
)
})
}
export const flattenClass = <
C extends AbstractClass<any, any>
>(class_: C) =>
getInheritanceHierarchy(class_)
.reduce((flattened, current) => {
copyClassProperties(current, flattened)
return flattened
}, class {}) as C
/**
* Returns an array of classes representing the inheritance hierarchy of a given class constructor. The array includes the given class constructor itself and all its parent classes in the order of inheritance.
*
* @param class_ The class constructor for which to generate the inheritance hierarchy.
* @returns An array of class constructors representing the inheritance hierarchy of `Class`.
*/
export function getInheritanceHierarchy(
class_: AbstractClass<any, any>
): AbstractClass<any, any>[] {
const parent = Object.getPrototypeOf(class_)
return isClass(parent)
? [...getInheritanceHierarchy(parent), class_]
: [class_]
}
/**
* Determines whether a given value is a class constructor or not by checking if its `toString` method returns a string matching the pattern of a class definition.
*
* @param el The value to check for class constructor status.
* @returns A boolean indicating whether `el` is a class constructor or not.
*/
export const isClass = (el: { toString: () => string }) =>
Boolean(el.toString().match(/^class(?: [.\S]+)?(?: extends [.\S]+)? {[\s\S]*}$/))

View File

@@ -1,3 +1 @@
export * from "./class"
export * from "./trait" export * from "./trait"
export * from "./utils"

View File

@@ -1,6 +1,5 @@
import { AbstractClass, Class, UnionToIntersection } from "type-fest" import { AbstractClass, Class, UnionToIntersection } from "type-fest"
import { copyClassProperties, flattenClass } from "./class" import { StaticMembers, copyClassProperties, flattenClass } from "./util/class"
import { StaticMembers } from "./utils"
export type Trait<T> = export type Trait<T> =

90
src/util/class.ts Normal file
View File

@@ -0,0 +1,90 @@
import { AbstractClass } from "type-fest"
/**
* Represents the static members of a class.
*
* @template C - The type of the class for which static members are extracted.
* @typeparam The static members of the class.
*/
export type StaticMembers<C> = Pick<C, keyof C>
/**
* 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<any, any>,
to: AbstractClass<any, any>,
) {
Object.getOwnPropertyNames(from).forEach(name => {
if (name === "length"
|| name === "prototype"
)
return
Object.defineProperty(
to,
name,
Object.getOwnPropertyDescriptor(from, name) || Object.create(null),
)
})
Object.getOwnPropertyNames(from.prototype).forEach(name => {
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.
*
* @template C - The type of the class to be flattened, extending AbstractClass<any, any>.
* @param C - The class to be flattened.
* @returns A new class with properties flattened from the entire inheritance hierarchy.
*/
export function flattenClass<
C extends AbstractClass<any, any>
>(class_: C) {
return getInheritanceHierarchy(class_)
.reduce((flattened, current) => {
copyClassProperties(current, flattened)
return flattened
}, class {}) as C
}
/**
* 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.
*/
export function getInheritanceHierarchy(
class_: AbstractClass<any, any>
): AbstractClass<any, any>[] {
const parent = Object.getPrototypeOf(class_)
return isClass(parent)
? [...getInheritanceHierarchy(parent), class_]
: [class_]
}
/**
* Checks if a given element appears to be a class based on its string representation.
*
* @param el - The element to check for being a class.
* @returns `true` if the element is likely a class; otherwise, `false`.
*/
export function isClass(el: { toString: () => string }) {
return Boolean(el.toString().match(/^class(?: [.\S]+)?(?: extends [.\S]+)? {[\s\S]*}$/))
}

View File

@@ -1 +0,0 @@
export type StaticMembers<C> = Pick<C, keyof C>