Annotate generic ES6 class with JSDoc - javascript

I want to document a generic map-like data structure with JSDoc. First of all, note that this structure does not extend Map.
A more general question would be: How do I annotate a generic ES6 class construct correctly?
Consider the following code. The marked piece of the annotation produces the error in the comment.
/** #type {MyMap<string, number>} */
// ^^^^^^^^^^^^^^^^^^^^^ [js] Type 'MyMap' is not generic.
const map = new MyMap();
That’s the definition of MyMap:
// #ts-check
/**
* #template K, V
*/
class MyMap {
constructor() {
this._map = new Map();
}
}
What do I need to do so that MyMap counts as a generic type? How do I expose K and V as the generic types of the structure?
(Note: I use Visual Studio Code to verify the annotations by adding // #ts-check to the top of my JavaScript file.)

First of all, as stated in the question, the #template annotation to make the MyMap class generic is already correct. Below, MyMap has two generic types Key and Value. Those types can be used inside the class, for example, by setting the type of the instance property this._map to /** #type {Map<Key, Value>} */, etc. To demonstrate, I added two simple wrapper methods around Map.prototype.set and Map.prototype.get that rely on those types.
// #ts-check
/**
* #template Key, Value
*/
class MyMap {
constructor() {
/** #type {Map<Key, Value>} */ this._map = new Map();
}
/**
* #param {Key} key
* #param {Value} value
*/
set(key, value) {
this._map.set(key, value)
}
/**
* #param {Key} key
* #returns {Value | undefined}
*/
get(key) {
return this._map.get(key)
}
}
/** #type {MyMap<string, number>} */ const map = new MyMap();
map.set('key', 1)
map.get('key')

Related

How jsdoc describes static class method which returns SAME CLASS variable

This is very simple example of what I want to get. My question is about #returns tag. What should I write there?
class Base{
/**
* #returns {QUESTION: WHAT SHOUL BE HIRE??}
*/
static method(){
return new this()
}
}
class Sub extends Base{
}
let base= Base.method() // IDE should understand that base is instance of Base
let sub= Sub.method() // IDE should understand that sub is instance of Sub
There is no "relative" type.
You could fairly trivally modify the extended class;
/** #extends {Base} */
class Sub extends Base{
/** #returns {Sub} */
static method(){
return super.method();
}
}
Or perhaps use a third type, an #interface that defines this method's existence
/** #interface */
class I {
method() {}
}
/** #implements {I} */
class Base {
/** #returns {I} */
static method(){
return new this();
}
}
/**
* #extends {Base}
* #implements {I}
*/
class Sub {
/** #returns {I} */
static method(){
return new this();
}
}
I know that this is a very old question and that in most circumstances projects needing a polymorphic this for static methods would now be using TypeScript with the workaround as listed here.
Several of our browser based projects are still using pure ES module JavaScript without any build step and introducing a build step for this use case seems over the top. On these projects we are also using Visual Studio Code's 'Implicit Project Config: Check JS' to enable type checking. Having to cast the return of every extended static method is a real pain!
The following workaround works well in Visual Studio Code for JavaScript with JSDoc:
/**
* #template T
* #param {T} Class
* #returns {{
* method: () => InstanceType<T>
* } & T}
*/
// #ts-ignore
const fixPolymorphicThis = Class => Class
class Base {
static method() {
return new this()
}
}
const Sub = fixPolymorphicThis(class Sub extends Base { })
let base = Base.method() // IDE should understand that base is instance of Base
let sub = Sub.method() // IDE should understand that sub is instance of Sub
console.assert(sub instanceof Base)
console.assert(sub instanceof Sub)

How to specify template type of inherited class?

Consider a generic collection:
/**
* A collection of items of the same type
* #template TSingleItem
* */
class ItemsCollection {
/** #type {TSingleItem[]} **/
get items() {
return [createSingleItem()];
}
/**
* Creates a new item based on the implementation type
* #param {string} param
* #returns {TSingleItem}
*/
createSingleItem(param) {
throw new Error("Pure virtual method call!");
}
}
Now we implement it as:
class Item {
constructor(name) {
this.name = name;
}
}
class Items extends ItemsCollection {
createSingleItem(param) {
return new Item(param);
}
}
How do I tell JSDoc to assume that items on that inherited class is Item[] not TSingleItem[]?
I tried this above the class:
/**
* #extends {ItemsCollection<Item>}
* */
class Items extends ItemsCollection
That didn't help in visual studio at least. What's the correct syntax? Bonus points if it works with Visual Studio intellisense.
Turns out this is the correct code, as also seen in this nice tutorial:
/**
* #extends {ItemsCollection<Item>}
* */
class Items extends ItemsCollection
It does also work in Visual Studio 2017, just took a while to catch on. I'm leaving this Q&A instead of deleting because it wasn't exactly obvious or easy to google to confirm it.

JSDoc, treat POJO as instance of Class

I have a class and a function that takes instance of that class or a similar POJO object as argument.
I want to annotate this function using JSDoc.
class Test {
constructor(a, b) {
this.a = a;
this.b = b;
}
}
/**
* #param {Test} test
*/
function handleTest(test) {
console.log(test.a, test.b);
}
// Webstorm complains that argument is not of type Test
handleTest({
a: 'this is a'
});
Using #param {Test} test almost works... but WebStorm complains that POJO isn't assignable to type Test.
Is there some JSDoc trick I can do to make it clear that both instance of Test and a Test-like object are both OK?
Use type union:
/**
* #param {Test|{a,b}} test
*/
function handleTest(test) {
console.log(test.a, test.b);
}
The |{a,b} part of the type denotes separate allowable types. You could just use {Test|{}} but then the IDE doesn't know that a and b are expected properties.
That's the shorthand version; you could also define what "Test-like" objects are in case you plan on using and documenting TestLike objects in more than one place:
/**
* #typedef {object} TestLike
* #property {string} a - some property a
* #property {string} b - some property b
*/
/**
* #param {Test|TestLike} test
*/
handleTest(test) {
// ...
}

Tagged unions in closure compiler

I'm currently in the process of comparing the Google Closure Compiler and the Flow static type checker, in terms of expressiveness. What I like about the latter is that it apparently can represent tagged unions quite nicely. The manual gives this example:
type Result = Done | Error; // a disjoint union type with two cases
type Done = { status: 'done', answer: Matrix };
type Error = { status: 'error', message: string };
Is there a way to do something like this using Closure Compiler? That means some way to force certain proprties of an object to not only be of a specific type, but have a fixed value? And to use that value for type inference, to distinguish between different options in a union? I found no documentation to this effect.
There isn't an exact translation. Here's how I would write this:
/** #enum {string} */
var ResultStatus = {
DONE: 'done',
ERROR: 'error'
};
/** #record */
var Done = function() {};
/** #type {ResultStatus} */
Done.prototype.status;
/** #type {Matrix} */
Done.prototype.answer;
/** #record */
var Error = function() {};
/** #type {ResultStatus} */
Error.prototype.status;
/** #type {string} */
Error.prototype.message;
/** #type {Result|Error} */
var Result;
This is a very verbose syntax but provides the best type checking. Any object with those properties is assumed to match the type. Extra properties are allowed.
There's a much shorter syntax with slightly different behavior: typedefs.
/** #enum {string} */
var ResultStatus = {
DONE: 'done',
ERROR: 'error'
};
/**
* #typedef{{
* status: ResultStatus
* answer: Matrix
* }}
*/
var Done;
/**
* #typedef{{
* status: ResultStatus
* message: string
* }}
*/
var Error;
/** #type {Result|Error} */
var Result;
There are several other ways to write these types as well. Which you choose depends on what you want checked. For instance, do you want a warning if you misspell (or try to add) a property? Do you want an exact match on both property names or types, or are extra properties allowed?

Documenting a private constructor with JSDoc

I've got a class where individual methods may be called statically but will return a new instance of class in order to chain, for example:
var builder = ns
.setState('a', 'A')
.setState('b', 'B');
Where Builder is defined as such:
/**
* #module Builder
*/
/**
* #class Builder
*/
/**
* #private
*/
function Builder() {
this.state = {
query: {}
};
}
Builder.prototype = {
/**
* #param {string} k - The key
* #param {object} v - The value
* #return {Builder}
*/
setState: function(k, v) {
var that = (this instanceof Builder) ? this : new Builder();
that[k] = v;
return that;
}
// Other properties and methods…
}
The Builder constructor is never supposed to be called explicitly by user code and thus I'd like it not to show up in the docs. However, all of the combinations I've tried with JSDoc tags (e.g. #private, #constructs, etc.) can't seem to suppress it from the built docs.
From version 3.5.0 of jsDoc, you can use tag #hideconstructor on the class, telling jsDoc not to include the constructor into documentation.
/**
* #class Builder
*
* #hideconstructor
*/
function Builder() {
// implementation
}
You should be able to use the #ignore directive to achieve this. From the docs:
The #ignore tag indicates that a symbol in your code should never appear in the documentation. This tag takes precedence over all others.
http://usejsdoc.org/tags-ignore.html

Categories

Resources