It's not very clear from the question what you want to achieve, but from my understanding you want the type of this
to be modified by the Foo
function for the class that is passed as an argument.
You can't alter the type of this
for all members of a class except through inheritance. You can however change the type of this
for an object literal that is passed to a function by using ThisType<T>
. ThisType<T>
is a special marker for the compiler (it's magic) and the compiler will use T
as the type of this
for any object literal that is assigned to ThisType<T>
.
function Foo<T>(...cons: T[]) {
// Infer the type of the object literal passed in in TClass
// use ThisType to let the compiler know this should contain
// all memebers of the object literal plus a merger of all memebrs passed in to cons
return function <TClass>(classMember: TClass & ThisType<TClass & UnionToIntersection<T>>) : TClass & UnionToIntersection<T> {
return null as any; // implementation
}
}
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
class FakeClass {
inject: string; // ignore this property doesn't matter
}
const TestFoo = Foo(new FakeClass())({
TestTypes() {
this.inject // this has the members of FakeClass
}
})
TestFoo.TestTypes();
Note This solution needs noImplicitThis: true
to work. Also UnionToIntersection
is taken from here
Another option if you want to use a class, is to focus on the inheritance part. We can create a type to act as a fake base class for the class you pass in to Foo
. You can't pass in the class directly, you will need to pass in a function which will be called by Foo
with the base class to be used for the new class.
function Foo<T>(...cons: T[]) {
return function <TClass>(classMember: (base: Constructor<UnionToIntersection<T>>) => Constructor<TClass>) : Constructor<TClass> {
return classMember(class {
constructor() {
Object.assign(this, ...cons);
}
} as any)
}
}
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type Constructor<T> = new (...args: any[]) => T
class FakeClass {
inject: string;
}
const TestFoo = Foo(new FakeClass())(b=> class extends b {
TestTypes() {
this.inject
}
})
new TestFoo().TestTypes();
Edit
From what we discussed in comments you want the result to be a class and take in other classes to act as mixins. We can modify solution 1 to do this
function Foo<T extends Constructor<any>[]>(cons: T) {
return function <TClass>(classMember: TClass & ThisType<TClass & MergeConstructorTypes<T>>): Constructor<TClass & MergeConstructorTypes<T>> {
// Naive implementation, you might want to do better here
var cls = class {
constructor() {
cons.forEach(c => c.apply(this));
}
};
cons.forEach(c => Object.assign(cls.prototype, c.prototype));
Object.assign(cls.prototype, classMember);
return cls as any;
}
}
type Constructor<T> = new (...args: any[]) => T
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type MergeConstructorTypes<T extends Constructor<any>[]> =
UnionToIntersection<InstanceType<T[number]>>;
class FakeClassA {
inject: string = "A"; // ignore this property doesn't matter
logA() {
console.log(this.inject);
}
}
class FakeClassB {
inject2: string = "B"; // ignore this property doesn't matter
logB() {
console.log(this.inject2);
}
}
const TestFoo = Foo([FakeClassA, FakeClassB])({
TestTypes() {
console.log(this.inject)
console.log(this.inject2)
this.logA();
this.logB();
}
})
new TestFoo().TestTypes();
Note The faked base class solution can also work in this case as well, but you expressed interest in the fist solution so I exemplified it for that case. Also the solution above uses 3.0 features and again requires noImplicitThis:true
Hope it helps :)