Reputation: 538
I wonder how to correctly type method decorators in Typescript. I have a simple decorator which adds string param as a first parameter to the decorated function. It's working just fine the problem is that I don't know how to type it correctly and whether it's even possible?
const task = (
target: unknown,
propertyKey: string,
descriptor: TypedPropertyDescriptor<(...args: unknown[]) => unknown>,
): TypedPropertyDescriptor<(name: string, ...args: unknown[]) => unknown> => {
const original = descriptor.value
descriptor.value = function (...args: unknown[]) {
return original?.call(this, 'Mark', ...args)
}
// what's wrong with this return?
return descriptor
}
class Test {
constructor(){
// should not report an error
this.printName()
}
@task
printName(name: string) {
console.log(name)
}
}
new Test()
Playground available here
Upvotes: 2
Views: 2492
Reputation: 5112
As @cdimitroulas said, it's not possible, decorators don't change the Typescript type.
Still, we can address some of your questions:
// what's wrong with this return?
const task = (
target: unknown,
propertyKey: string,
// you said here that descriptor is this type
descriptor: TypedPropertyDescriptor<(...args: unknown[]) => unknown>,
// yet, you said that the type is different here, namely that the first argument is of type string (it was unknown)
): TypedPropertyDescriptor<(name: string, ...args: unknown[]) => unknown> => {
const original = descriptor.value
descriptor.value = function (...args: unknown[]) {
return original?.call(this, 'Mark', ...args)
}
// you're returning the same descriptor that was passed to the decorator
return descriptor
}
Typescript expects to find the same type but it was different, so it tries to compare them to see if one is assignable to the other but it's not. unknown
is not assignable to string
. unknown
is only assignable to unknown
or any
.
Knowing that a decorator won't change the type anyway, you can omit the return type completely and let Typescript infer that for you
const task = (
target: unknown,
propertyKey: string,
descriptor: TypedPropertyDescriptor<(...args: unknown[]) => unknown>,
) => {
const original = descriptor.value
descriptor.value = function (...args: unknown[]) {
return original?.call(this, 'Mark', ...args)
}
return descriptor
}
Next, Typescript still isn't happy with that:
@task // --> Type '(name: string) => void' is not assignable to type '(...args: unknown[]) => unknown'
printName(name: string) {
console.log(name)
}
As said earlier, unknown
cannot be assigned to string
. So either we change unknown
to any
(unsafe) or better yet, we change the signature to TypedPropertyDescriptor<(name: string) => void>
const task = (
target: any,
propertyKey: string,
descriptor: TypedPropertyDescriptor<(name: string) => void>,
) => {
const original = descriptor.value
descriptor.value = function () {
return original?.call(this, 'Mark')
}
return descriptor
}
As for this line
this.printName()
the best you can do is making the name prop optional. The decorator is basically setting the prop for us so in that case, we can remove the prop altogether although I know it's not a real case, just an example to illustrate your point.
Upvotes: 2
Reputation: 2539
It's not possible to modify a function's type signature using a decorator.
See relevant discussions here and here.
Upvotes: 1