mirror of
https://github.com/Kir-Antipov/mc-publish.git
synced 2024-11-25 09:51:01 -05:00
Added a possibility to make objects callable
This commit is contained in:
parent
7882738bce
commit
d263c15393
2 changed files with 96 additions and 0 deletions
47
src/utils/functions/callable.ts
Normal file
47
src/utils/functions/callable.ts
Normal 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>;
|
||||
}
|
49
tests/unit/utils/functions/callable.spec.ts
Normal file
49
tests/unit/utils/functions/callable.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue