Created
May 19, 2019 09:21
-
-
Save trusktr/dbdb09f9fe07e9ced59c8be87ef21894 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type Omit<T, K> = Pick<T, Exclude<keyof T, K>> | |
type ImplementationKeys = 'static' | 'private' | 'protected' | |
type FunctionToConstructor<T, TReturn> = T extends (...a: infer A) => void ? new (...a: A) => TReturn : never | |
// Note, void also works the same in place of unknown | |
type ReplaceCtorReturn<T, TReturn> = T extends new (...a: infer A) => unknown ? new (...a: A) => TReturn : never | |
type ConstructorOrDefault<T> = T extends {constructor: infer TCtor} ? TCtor : (() => void) | |
// `Id` is an identity type, but it is also used as a trick to expand the | |
// type given to it so that tooltips show the basic type rather then all the | |
// conditional/aliases used. | |
type Id<T> = {} & {[P in keyof T]: T[P]} | |
type Constructor<T = any, A extends any[] = any[]> = new (...a: A) => T | |
// Although the SuperType type definiton already checks that T extends from | |
// Constructor, the additional check in the generic paramters is useful so | |
// that we don't get an error about "never" which is hard to track down. The | |
// generic paramter will cause a more helpful and understandable error. | |
// TODO ensure that TInstance is InstanceType of TBase | |
// prettier-ignore | |
type SuperType<TInstance, TSuper extends Constructor<any>> = TSuper extends Constructor<infer I, infer A> | |
? {constructor: (...a: A) => I} & Id<InstanceType<TSuper>> | |
// {constructor: (...a: A) => I} & Id<Pick<InstanceType<TSuper>, keyof InstanceType<TSuper>>> | |
: never | |
// type SuperType< | |
// TInstance extends InstanceType<TSuper>, | |
// TSuper extends Constructor<any> | |
// > = TSuper extends Constructor<infer I, infer A> | |
// ? TInstance extends InstanceType<TSuper> | |
// ? {constructor: (...a: A) => I} & Id<InstanceType<TSuper>> | |
// : never | |
// : never | |
type SuperHelper = <T, TBase extends Constructor<any>>(self: T) => SuperType<T, TBase> | |
type PrivateHelper = <T>(self: T) => T extends {__: {private: infer TPrivate}} ? TPrivate : never | |
type PublicHelper = <T>(self: T) => Omit<T, ImplementationKeys> // TODO validate instance is public? | |
type ProtectedHelper = <T>(self: T) => T extends {__: {protected: infer TProtected}} ? TProtected : never | |
// type ProtectedHelper = <T>(self: T) => T extends {protected: infer TProtected} ? TProtected : never | |
type Statics<T> = T extends {static: infer TStatic} ? TStatic : {} | |
// type InheritedProtected<T> = T extends {protected: infer TProtected} ? TProtected : {} | |
type StaticsAndProtected<T> = Id< | |
Statics<T> | |
// & { | |
// __: {protected: InheritedProtected<T>} | |
// } | |
> | |
// type ExtractInheritedProtected<T> = T extends {__: infer TProtected} ? TProtected : {} | |
// type PickImplementationKeys<T> = Omit<T, ImplementationKeys> | |
// type PickImplementationKeys<T> = Id<Pick<T, ImplementationKeys>> | |
// type _PrivateImplementation<T> = Pick<T, Extract<keyof T, ImplementationKeys>> | |
type PickImplementationKeys<T> = Pick<T, Extract<keyof T, ImplementationKeys>> | |
// this moves the implementation keys off the constructor return type and | |
// onto a fake __ property, so that we can reference the __ type within the | |
// implementatin code, but so that the outside (public) doesn't see the fake | |
// __ property. | |
type LowClassThis<T> = Id<Omit<T, ImplementationKeys> & {__: PickImplementationKeys<T>}> | |
type OmitImplementationKeys<T> = Omit<T, ImplementationKeys> | |
function Class( | |
name: string | |
): { | |
extends<TBase extends Constructor, T>( | |
base: TBase, | |
members: ( | |
helpers: { | |
Super: SuperHelper | |
Public: PublicHelper | |
Protected: ProtectedHelper | |
Private: PrivateHelper | |
} | |
) => T & | |
Partial<InstanceType<TBase>> & | |
ThisType<LowClassThis<T & InstanceType<TBase> /* & ExtractInheritedProtected<TBase>*/>> | |
): T extends {constructor: infer TCtor} | |
? FunctionToConstructor<ConstructorOrDefault<T>, Id<InstanceType<TBase> & OmitImplementationKeys<T>>> & | |
Id<StaticsAndProtected<T> & Pick<TBase, keyof TBase>> | |
: ReplaceCtorReturn<TBase, Id<InstanceType<TBase> /*& Omit<T, 'constructor'>*/>> & // missing the T type here? | |
Id<StaticsAndProtected<T> & Pick<TBase, keyof TBase>> | |
} | |
function Class<T>( | |
name: string, | |
members: ( | |
helpers: {Public: PublicHelper; Protected: ProtectedHelper; Private: PrivateHelper; Super: never} // TODO Super is actually Object | |
) => T & ThisType<LowClassThis<T>> | |
): FunctionToConstructor<ConstructorOrDefault<T>, Id<OmitImplementationKeys<T>>> & Id<StaticsAndProtected<T>> | |
function Class<T>( | |
name: string, | |
members: T & ThisType<LowClassThis<T>> | |
): FunctionToConstructor<ConstructorOrDefault<T>, Id<OmitImplementationKeys<T>>> & Id<StaticsAndProtected<T>> | |
function Class(): any { | |
return null as any; | |
} | |
const Animal = Class('Animal', { | |
sound: '', | |
constructor(sound: string) { | |
this.sound = sound | |
}, | |
makeSound() { | |
console.log(this.sound) | |
}, | |
}) | |
const a = new Animal('') | |
a.makeSound() | |
const Dog = Class('Dog').extends(Animal, ({Super}) => ({ | |
constructor(size: 'small' | 'big') { | |
if (size === 'small') Super(this).constructor('woof') | |
if (size === 'big') Super(this).constructor('WOOF') | |
}, | |
// makeSound(d: number) { | |
// console.log(this.sound, d) | |
// }, | |
makeSound() { | |
Super(this).makeSound() | |
console.log(this.sound) | |
}, | |
bark() { | |
this.makeSound() | |
}, | |
other() { | |
this.bark() | |
}, | |
})) | |
type Dog = InstanceType<typeof Dog> | |
const smallDog: Dog = new Dog('small') | |
smallDog.bark() // "woof" | |
const bigDog = new Dog('big') | |
bigDog.bark() // "WOOF" | |
bigDog.bark() | |
bigDog.makeSound() | |
const Foo = Class('Foo', ({Public, Protected, Private}) => ({ | |
constructor(s: string) { | |
console.log(s) | |
}, | |
static: { | |
staticProp: 123, | |
staticMethod() { | |
console.log(Foo.staticProp) // 123 | |
}, | |
}, | |
publicProp: 'blah', | |
publicMethod(a: string) { | |
// console.log(this.constructor.staticProp) // TODO | |
console.log(a) | |
this | |
}, | |
protected: { | |
protectedProp: 456, | |
protectedMethod(a: string) { | |
console.log(a) | |
console.log(Protected(this).protectedProp) // TODO | |
}, | |
}, | |
private: { | |
privateProp: 789, | |
privateMethod() { | |
this.publicProp | |
this.publicMethod('') // Should this be allowed? Or do we just the below line to work ? | |
Public(this).publicMethod('') | |
}, | |
}, | |
test() { | |
this.publicMethod('') | |
console.log(this.publicProp) // 'blah' | |
let p = Protected(this) | |
console.log(p) | |
Protected(this).protectedMethod('') | |
console.log(Protected(this).protectedProp) // 456 | |
Private(this).privateMethod() | |
console.log(Private(this).privateProp) // 789 | |
return Private(this).privateProp | |
}, | |
})) | |
let foo = new Foo('') | |
console.log(foo.publicProp) | |
// console.log(foo.protectedProp) | |
// console.log(foo.privateProp) | |
Foo.staticMethod() | |
Foo.staticProp | |
const Bar = Class('Bar').extends(Foo, ({Public, Protected, Private, Super}) => ({ | |
constructor(sound: string) { | |
sound | |
}, | |
static: { | |
derivedStatic: 10, | |
}, | |
private: { | |
derivedPrivate: 10, | |
}, | |
protected: { | |
derivedProtected: 10, | |
}, | |
derivedPublicMethod() { | |
Protected(this).protectedMethod('') | |
Protected(this).protectedProp | |
Protected(this).derivedProtected | |
Private(this).derivedPrivate | |
this.test() | |
Public(this).test() | |
}, | |
test() { | |
// should not work. | |
const b = {} | |
Super(b).test() | |
return Super(this).test() | |
}, | |
})) | |
var bar = new Bar('') | |
bar.derivedPublicMethod() | |
bar.test() | |
Bar.derivedStatic | |
Bar.staticMethod() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment