fix(mixer): rewrite mixins management (#333)

* fix: declared properties

* fix: idk mixer

* fix(mixer): tbh I really hate runtimes

* fix: test

* test: change constructor test
This commit is contained in:
Marcos Susaña 2025-02-26 20:36:56 -04:00 committed by GitHub
parent 7745ec942c
commit 7af24bf043
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 27 additions and 69 deletions

View File

@ -1,75 +1,30 @@
/**
* Gets the descriptors of a class.
* @param c The class to get the descriptors of.
* @returns The descriptors of the class.
*/
function getDenoDescriptors(c: TypeClass) {
const protos = [c.prototype];
let v = c;
while ((v = Object.getPrototypeOf(v))) {
if (v.prototype) protos.push(v.prototype);
const IgnoredProps = ['constructor', 'prototype', 'name'];
export function copyProperties(target: InstanceType<TypeClass>, source: TypeClass) {
const keys = Reflect.ownKeys(source);
for (const key of keys) {
if (IgnoredProps.includes(key as string)) continue;
if (key in target) continue;
const descriptor = Object.getOwnPropertyDescriptor(source, key);
if (descriptor) {
Object.defineProperty(target, key, descriptor);
}
}
return protos.map(x => Object.getOwnPropertyDescriptors(x));
}
/**
* Gets the descriptors of a class.
* @param c The class to get the descriptors of.
* @returns The descriptors of the class.
*/
function getNodeDescriptors(c: TypeClass) {
let proto = c.prototype;
const result: Record<string, TypedPropertyDescriptor<unknown> | PropertyDescriptor>[] = [];
while (proto) {
const descriptors = Object.getOwnPropertyDescriptors(proto);
// @ts-expect-error this is not a function in all cases
if (descriptors.valueOf.configurable) break;
result.push(descriptors);
proto = Object.getPrototypeOf(proto);
}
return result;
}
function getDescriptors(c: TypeClass) {
//@ts-expect-error
// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
return typeof Deno === 'undefined' ? getNodeDescriptors(c) : getDenoDescriptors(c);
}
/**
* Mixes a class with other classes.
* @param args The classes to mix.
* @returns The mixed class.
*/
export function Mixin<T, C extends TypeClass[]>(...args: C): C[number] & T {
const Base = args[0];
export function Mixin<T, C extends TypeClass[]>(...mixins: C): C[number] & T {
const Base = mixins[0];
class MixedClass extends Base {
constructor(...constructorArgs: any[]) {
super(...constructorArgs);
for (const mixin of args.slice(1)) {
const descriptors = getDescriptors(mixin).reverse();
for (const desc of descriptors) {
// @ts-expect-error
Object.assign(this, new desc.constructor.value(...constructorArgs));
for (const key in desc) {
if (key === 'constructor') continue;
if (key in MixedClass.prototype) continue;
const descriptor = desc[key];
if (descriptor.value) {
// @ts-expect-error
MixedClass.prototype[key] = descriptor.value;
} else if (descriptor.get || descriptor.set) {
Object.defineProperty(MixedClass.prototype, key, {
get: descriptor.get,
set: descriptor.set,
});
}
}
constructor(...args: any[]) {
super(...args);
for (const mixin of mixins.slice(1)) {
// @ts-expect-error
const mixinInstance = new mixin(...args);
copyProperties(this, mixinInstance);
let proto = Object.getPrototypeOf(mixinInstance);
while (proto && proto !== Object.prototype) {
copyProperties(this, proto);
proto = Object.getPrototypeOf(proto);
}
}
}

View File

@ -3,7 +3,7 @@ import type { UsingClient } from '../../commands';
import { toCamelCase } from '../../common';
/** */
export abstract class Base {
export class Base {
constructor(client: UsingClient) {
Object.assign(this, { client });
}

View File

@ -138,7 +138,10 @@ describe('mix decorator', () => {
it('should handle constructor arguments', () => {
class MixinWithConstructor {
constructor(public name: string) {}
name: string;
constructor(name: string) {
this.name = name.slice(0, 2);
}
getName() {
return this.name;