oligofren
oligofren

Reputation: 22923

How do I type a static member of a function?

Having static member of a function is quite a common thing in javascript. Say this example:

function createSpy(fn){
  return function spy() {
    if(!spy.callCount) spy.callCount = 0;
    spy.callCount++;
    fn.apply(arguments);
  }
}

Here the functions created by createSpy have a static variable callCount. I have trouble finding out how to type these kinds of objects correctly in TypeScript, as the only examples I have seen of such a thing is the explicit support for static members in a TypeScript class.

Say I have an existing js library and would like to add proper typing for the resulting types in a library.d.ts file, what would I need in order to get the following to pass?

const mySpy: Spy = createSpy(myUtil);

mySpy();
console.log(mySpy.callCount === 1);

Upvotes: 0

Views: 64

Answers (1)

Aluan Haddad
Aluan Haddad

Reputation: 31803

The best you can do in a TypeScript implementation is

function createSpy<A extends any[]>(fn: (...args: A) => void) {
    function spy(...args: A): void {
        spy.callCount++;
        fn(...args);
    };
    spy.callCount = 0;
    return spy;
}

Playground Link

The vastly improved, although still somewhat lacking assignment syntax leveraged above was introduced in TypeScript 3.1 - Properties declarations on functions.

If you are writing a declaration file for an existing JavaScript implementation you have a few options.

A decent one aligned with your example usage is

type Spy<F extends (...args: any[]) => void> = F & { callCount: number };

declare function createSpy<F extends (...args: any[]) => void>(fn: F): Spy<F>;

Which would be consumed as

function myUtil() { console.log('something useful'); }

const mySpy: Spy<typeof myUtil> = createSpy(myUtil);

mySpy();
console.log(mySpy.callCount === 1);

but consumers should be encouraged to take advantage of type inference

const mySpy = createSpy(myUtil); // Spy<() => void>

mySpy();
console.log(mySpy.callCount === 1);

Alternately, and considering the benefits of inference, we could might opt to write it in a single declaration

declare function createSpy<F extends (...args: any[]) => void>(fn: F): F & {
  callCount: number
};

Upvotes: 1

Related Questions