David Wolever
David Wolever

Reputation: 154494

TypeScript: type-safe class decorator that handles static methods?

How can I write a type-safe class decorator that correctly handles static methods?

Specifically, this decorator will work for classes without static methods:

interface ITypeOf<T> {
    new(...args: any[]): T
}

function decorate<T>(cls: ITypeOf<T>): ITypeOf<T> {
    return cls
}

But ITypeOf doesn't consider static attributes, so the compile fails with this error when it's applied to a class with static attributes:

@decorate
class Foo {
    static bar() {
        return 42
    }
}

Yields the error:

Unable to resolve signature of class decorator when called as an expression.
  Type 'ITypeOf<Foo>' is not assignable to type 'typeof Foo'.
    Property 'bar' is missing in type 'ITypeOf<Foo>'.
function decorate<T>(cls: ITypeOf<T>): ITypeOf<T>

Here's a working example: http://www.typescriptlang.org/play/#src=interface…

How can I write a type-safe class decorator that works for classes with static members?

Upvotes: 3

Views: 2688

Answers (2)

artem
artem

Reputation: 51629

How can I write a type-safe class decorator that works for classes with static members?

It depends on what are you going to do inside the decorator.

You can simply have decorator completely generic like this:

function decorate<C>(cls: C) {
    return cls;
}

However this does not capture any information about C, so you can't do much with cls inside decorate.

Or you can have separate generic parameters, one for the "instance side" of cls and another one for the "static side":

interface ITypeOf<T> {
    new(...args: any[]): T
}

function decorate<I, S extends ITypeOf<I>>(cls: S): S {
    return cls
}


@decorate
class Foo {
    static bar() {
        return 42
    }

}

Upvotes: 0

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249636

The return type of the decorator must be compatible with the class type it is decorating as the return value of the decorator will replace the class at runtime. To achieve this, we must use a generic parameter representing the class itself, that will be both the type of the parameter and the return type. We will thus be returning a class with the same structure as the input class (including the statics) and the compiler will be satisfied:

function decorate<TCtor extends ITypeOf<any>>(cls: TCtor): TCtor {
    return cls
}

@decorate
class Foo {
    static bar() {
        return 42
    }
}

To add restrictions on the class to be decorated we can be more restrictive of the type parameter to ITypeOf:

// decorated class must have a field x: numeber
function decorate<TCtor extends ITypeOf<{ x: number }>>(cls: TCtor): TCtor {
    return cls
}

@decorate // error no instance x
class Foo { }
@decorate // ok
class Boo { x!: number }

We can also add restrictions as to static members

// decorated class must have a static field x: numeber
function decorate<TCtor extends ITypeOf<any> & { x: number }>(cls: TCtor): TCtor {
    return cls
}

@decorate // error no static x
class Foo { }
@decorate // ok
class Boo { static x: number }

Upvotes: 5

Related Questions