@v4fire/core
Version:
V4Fire core library
151 lines (134 loc) • 3.51 kB
text/typescript
/*!
* V4Fire Core
* https://github.com/V4Fire/Core
*
* Released under the MIT license
* https://github.com/V4Fire/Core/blob/master/LICENSE
*/
/**
* [[include:core/functools/trait/README.md]]
* @packageDocumentation
*/
/**
* Derives the provided traits to a class.
* The function is used to organize multiple implementing interfaces with the support of default methods.
*
* @decorator
* @param traits
*
* @example
* ```typescript
* abstract class Interface1 {
* abstract bar(): string;
*
* /**
* * The method that have the default implementation.
* * The implementation is placed as a static method.
* *\/
* bla(a: number): number {
* return Object.throw();
* };
*
* /**
* * The default implementation for `Interface1.bla`.
* * As the first parameter, the method is taken a context.
* *
* * @see Interface1.bla
* *\/
* static bla: AddSelf<iFoo['bla'], Baz> = (self, a) => a + 1;
* }
*
* abstract class Interface2 {
* abstract bar2(): string;
*
* bla2(a: number): string {
* return Object.throw();
* };
*
* /** @see Interface2.bla2 *\/
* static bla2: AddSelf<iFoo2['bla2'], Baz> = (self, a) => a.toFixed();
* }
*
* interface Baz extends Trait<typeof iFoo>, Trait<typeof iFoo2> {
*
* }
*
* @derive(iFoo, iFoo2)
* class Baz implements iFoo, iFoo2 {
* bar() {
* return '1';
* }
*
* bar2() {
* return '1';
* }
* }
*
* console.log(new Baz().bla2(2.9872));
* ```
*/
export function derive(...traits: Function[]) {
return (target: Function): void => {
const
proto = target.prototype;
for (let i = 0; i < traits.length; i++) {
const
originalTrait = traits[i],
chain = getTraitChain(originalTrait);
for (let i = 0; i < chain.length; i++) {
const
[trait, keys] = chain[i];
for (let i = 0; i < keys.length; i++) {
const
key = keys[i],
defMethod = Object.getOwnPropertyDescriptor(trait, key),
traitMethod = Object.getOwnPropertyDescriptor(trait.prototype, key);
const canDerive =
defMethod != null &&
traitMethod != null &&
!(key in proto) &&
Object.isFunction(defMethod.value) && (
Object.isFunction(traitMethod.value) ||
// eslint-disable-next-line @typescript-eslint/unbound-method
Object.isFunction(traitMethod.get) || Object.isFunction(traitMethod.set)
);
if (canDerive) {
const newDescriptor: PropertyDescriptor = {
enumerable: false,
configurable: true
};
if (Object.isFunction(traitMethod.value)) {
Object.assign(newDescriptor, {
writable: true,
// eslint-disable-next-line func-name-matching
value: function defaultMethod(...args: unknown[]) {
return originalTrait[key](this, ...args);
}
});
} else {
Object.assign(newDescriptor, {
get() {
return originalTrait[key](this);
},
set(value: unknown) {
originalTrait[key](this, value);
}
});
}
Object.defineProperty(proto, key, newDescriptor);
}
}
}
}
function getTraitChain<T extends Array<[Function, string[]]>>(
trait: Nullable<object>,
methods: T = Object.cast([])
): T {
if (!Object.isFunction(trait) || trait === Function.prototype) {
return methods;
}
methods.push([trait, Object.getOwnPropertyNames(trait)]);
return getTraitChain(Object.getPrototypeOf(trait), methods);
}
};
}