Added a possibility to make objects callable

This commit is contained in:
Kir_Antipov 2022-12-03 05:12:11 +00:00
parent 7882738bce
commit d263c15393
2 changed files with 96 additions and 0 deletions

View file

@ -0,0 +1,47 @@
/**
* A symbol representing the `call` function of a {@link Callable} object.
*/
export const CALL = Symbol.for("call");
/**
* Represents an object, which can be converted into a {@link Callable} one.
*/
interface SemiCallable {
/**
* A method that should be invoked, when an object is used as a function.
*/
[CALL](...args: unknown[]): unknown;
}
/**
* Represents an object, which can be called like a function.
*
* @template T - The type of the underlying object.
*/
export type Callable<T extends SemiCallable> = T & {
/**
* Redirects a call to the {@link CALL} function.
*/
(...args: Parameters<T[typeof CALL]>): ReturnType<T[typeof CALL]>;
};
/**
* Makes an object callable.
*
* @template T - The type of the object.
* @param obj - The object to make callable.
*
* @returns A new {@link Callable} object with the same properties as the original one, but which can be called like a function.
*/
export function makeCallable<T extends SemiCallable>(obj: T): Callable<T> {
/**
* Redirects a call to the {@link CALL} function.
*/
function call(...args: unknown[]): unknown {
return (call as unknown as T)[CALL](...args);
}
Object.assign(call, obj);
Object.setPrototypeOf(call, Object.getPrototypeOf(obj));
return call as unknown as Callable<T>;
}

View file

@ -0,0 +1,49 @@
import { CALL, makeCallable } from "@/utils/functions/callable";
describe("makeCallable", () => {
test("makes an object callable", () => {
const obj = {
[CALL]: (a: number, b: number) => a + b,
};
const callable = makeCallable(obj);
expect(callable(1, 2)).toBe(3);
});
test("preserves object properties", () => {
const obj = {
foo: 42,
[CALL](): number {
return this.foo;
},
};
const callable = makeCallable(obj);
expect(callable()).toBe(42);
expect(callable.foo).toBe(42);
});
test("preserves object prototype", () => {
class FooClass {
foo: number;
constructor(foo: number) {
this.foo = foo;
}
[CALL](): number {
return this.foo;
}
}
const obj = new FooClass(42);
const callable = makeCallable(obj);
expect(callable).toBeInstanceOf(FooClass);
expect(callable.foo).toBe(42);
expect(callable()).toBe(42);
});
});