How to document javascript higher order function?
Asked Answered
C

1

6

I have the following higher order function for wrapping contructors:

/**
 * Wrapper for calling constructor with given parameters
 *
 * @param {Class} Cls
 * @returns {function} Wrapper on constructor which creates an instance of given Class
 */
function constructorWrapper(Cls) {
    return (...args) => new Cls(...args);
}

So if I have a class MyClass, I can do the following:

exports.MyClass = MyClass;
exports.myClass = constructorWrapper(MyClass);

Now the class can be instantiated in the following 2 ways after importing:

const instance1 = new MyClass(param1, param2);
const instance2 = myClass(param1, param2);

In vscode, instance1 will have intellisense support but instance2 won't. How do I document the function/export so that the objects created using the wrapper are recognised as instances of the class?

Columniation answered 11/4, 2017 at 3:48 Comment(1)
Javascript sometimes not really good documented on vscode, use Typescript instead :(Artichoke
R
4

You could make IntelliSense available by forcing the type of myClass:

/** @type {function(T1, T2): MyClass} */
exports.myClass = constructorWrapper(MyClass);

If you wish to annotate constructorWrapper itself, however, that's not possible as of VSCode 1.11.1 (with TypeScript 2.2). While JSDoc supports generics:

/**
 * Wrapper for calling constructor with given parameters
 *
 * @param {function(new:T, ...*)} Cls The class constructor.
 * @returns {function(...*): T} Wrapper of the class constructor
 * @template T
 */
function constructorWrapper(Cls) {
    return (...args) => new Cls(...args);
}

and the inferred type is indeed correct:

<code>function constructorWrapper<T>(Cls: new (...arg1: any[]) => T): (...arg0: any[]) => T</code>

Somehow the two "T" becomes disconnected, making myClass = constructorWrapper(MyClass) to adopt the type signature (...arg0: any[]) => T. What T? Well we don't know, treat it as any and no IntelliSense then.

<code>myClass: (...arg0: any[]) => T</code>

VSCode's JSDoc-based IntelliSense is based on TypeScript, and I think this is a bug in TypeScript's handling of @template as of 2.2.

If you are not constrained to ES6-only-development, I recommend you rewrite it entirely in TypeScript instead. Then you'll get the expected IntelliSense, plus type safety and many other benefits.

Note that since TypeScript 2.2 does not support variadic generics yet the arguments can't be perfectly forwarded, thus the input to the myClass cannot be type-checked. That means you still need to manually annotate the type of myClass to get perfect information.

Redbreast answered 13/4, 2017 at 13:39 Comment(6)
Thank you for taking the time to answer this. However, manually annotating the type of myClass did not work in vscode. However, I suspect that this is a bug/missing feature and there is no better solution. I am upvoting the answer anyway.Columniation
@SuhasK Manually adding the @type works for me on VSCode 1.11.1 with TypeScript 2.2.2. Maybe a problem in your import statement?Redbreast
Indeed it works if the class definition is present in the same file where it is being exported. If it is being imported and then exported(as is my case), intellisense does not work even with the manual type annotation. ibb.co/m5qW0kColumniation
@SuhasK Maybe try (a) use the syntax import * as exp from './base'? (b) Restart VSCode? I don't have other ideas if these still don't work :).Redbreast
No.. That did not help. But I'll keep the manual type annotations with the hope that they'll work in the future.Columniation
@SuhasK I noticed that the type of myClass is inferred as (arg0: any, arg1: any) => any. In the custom type annotation, you need to provide types which TypeScript understands, e.g. /** @type {function(string, number): MyClass} */, otherwise they will all become any. Also, the MyClass needs to be a name visible inside the module, if you wrote exports.ClassA = ClassB then you should use ClassB as the type, not ClassA.Redbreast

© 2022 - 2024 — McMap. All rights reserved.