Can I restrict the argument of a method decorator generically by the type of the method it is applied to?
I have tried to get hold of the type of the method the decorator is applied to by using TypedPropertyDescriptor<Method>
. However, the type system does not complain.
function MyCache<Method extends (...args: any[]) => Promise<any>>(keyFn: (...args: Parameters<Method>) => string) {
return function (target: object, methodName: string, descriptor: TypedPropertyDescriptor<Method>) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: Parameters<Method>) {
const cacheKey = keyFn(...args);
return originalMethod?.apply(this, args);
} as Method;
};
}
class CounterService {
// This should error as the signature of this function does not match the argument list of the method
@MyCache((id: string) => `test-${id}`)
async getCounter(config: { valid: boolean }, value: number): Promise<number> {
return 0;
}
}
Try it here Typescript Playground
CodePudding user response:
You need to reverse your thinking a bit. You can't restrict the argument to the decorator, but you can restrict the method the decorator is applied to. This is due to the fact that the decorator factory function call is checked first, then the resulting decorator is checked against the target.
The first change we need to make is to remove the use of Parameters
, as this will prevent inference from the arguments passed to MyCache
. Then if we just change the TypedPropertyDescriptor
type argument, we get the desired error:
function MyCache<A extends any[]>(
keyFn: (...args: A) => string
) {
return function (target: object, methodName: string, descriptor: TypedPropertyDescriptor<(...args: A) => Promise<any>>) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: A) {
const cacheKey = keyFn(...args);
return originalMethod!.apply(this, args);
};
};
}
class CounterService {
// Error
@MyCache((id: string) => `test-${id}`)
async getCounter(config: { valid: boolean }, value: number): Promise<number> {
return 0;
}
// ok
@MyCache((id: string) => `test-${id}`)
async getCounter2(id: string): Promise<number> {
return 0;
}
}