Вам просто нужно добавить еще одно условие для длины параметров. Вы можете извлечь тип параметров в виде кортежа, используя Parameters
. Кортеж будет иметь свойство length с числовым литералом, поэтому вы можете проверить, что length
extends 1
:
type ContextService<T, U extends BaseContext> = {
[K in keyof T]: T[K] extends AnyFunction ?
T[K] extends ContextFn<U, ReturnType<T[K]>> ?
Parameters<T[K]>["length"] extends 1 ? T[K]
: never : never
: T[K]
};
class BarService implements ContextService<BarService, SomeContext> {
test: string = 'test';
// error: not assignable to type never
updateBar(): string {
return '';
}
updateBar2(p: ContextObj<SomeContext>): string { // ok
return '';
}
}
Может быть полезно дать подсказку пользователю о проблеме, хотя пользовательские ошибки не поддерживаются, это будет довольно близко:
type ContextService<T, U extends BaseContext> = {
[K in keyof T]: T[K] extends AnyFunction ?
T[K] extends ContextFn<U, ReturnType<T[K]>> ?
Parameters<T[K]>["length"] extends 1 ? T[K]
: ["Method must have exactly one parameter of type ", ContextObj<U>, "Found Parameters:", Parameters<T[K]>]
: ["Parameters types not macthed, expected [", ContextObj<U>, "] Found Parameters:", Parameters<T[K]>]
: T[K]
};
class BarService implements ContextService<BarService, SomeContext> {
test: string = 'test';
// error: not assignable to type never
updateBar(): string {// Type '() => string' is missing the following properties from type '["Method must have exactly one parameter of type ", ContextObj<SomeContext>, "Found Parameters:", []]'
return '';
}
updateBar2(p: ContextObj<SomeContext>): string {
return '';
}
updateBar3(p: SomeContext): string { // Type '(p: SomeContext) => string' is missing the following properties from type '["Parameters types not macthed, expected [", ContextObj<SomeContext>, "] Found Parameters:", [SomeContext]]'
return '';
}
}
Редактировать
Ответ на комментарий: можно ли это сделать с помощью декораторов?
Ответ - да, мы можем захватить в качестве параметров типа класс, к которому применяется декоратор, а также ключ, к которому он применяется, и использовать ту же логику, что и ранее для ключа свойства. Просто на этот раз мы прикрепляем ошибку к ключу, переданному в декоратор:
type Check<TSig extends (...a: any[]) => any, T, K extends keyof T> =
T[K] extends (...a: any[]) => any ?
T[K] extends TSig ?
Parameters<T[K]>["length"] extends 1 ? unknown
: ["Method must have exactly one parameter of type ", Parameters<TSig>, "Found Parameters:", Parameters<T[K]>]
: ["Parameters types not macthed, expected [", Parameters<TSig>, "] Found Parameters:", Parameters<T[K]>]
: unknown
function ensureSignatire<TSig extends (...a: any[]) => any>() {
return function <TTarget, TKey extends keyof TTarget>(target: TTarget, key: TKey & Check<TSig, TTarget, TKey>) {
}
}
class BarService {
test: string = 'test';
@ensureSignatire<ContextFn<SomeContext, any>>() // Type '"updateBar"' is not assignable to type '["Method must have exactly one parameter of type ", [ContextObj<SomeContext>], "Found Parameters:", []]'.
updateBar(): string {
return '';
}
@ensureSignatire<ContextFn<SomeContext, any>>() //ok
updateBar2(p: ContextObj<SomeContext>): string {
return '';
}
@ensureSignatire<ContextFn<SomeContext, any>>() // Type '"updateBar3"' is not assignable to type '["Parameters types not macthed, expected [", [ContextObj<SomeContext>], "] Found Parameters:", [SomeContext]]'
updateBar3(p: SomeContext): string {
return '';
}
}