Tagged unions in closure compiler - javascript

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?

Related

Annotate generic ES6 class with JSDoc

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')

js-doc / google-closure-compiler how to document passed enum object itself?

e.g.
/**
* Super my enum
* #enum {number}
*/
var MyEnum = {
ONE: 1,
TWO: 2
};
/**
* #param {what type is it?} enumObj
*/
function showEnum(enumObj) {
console.log(enumObj);
}
//show the enum definition object
showEnum(MyEnum);
how to describe parameter type as not the value/instance of MyEnum, but as the MyEnum object itself?
Use !MyEnum where the ! means "non-null".
/**
* #param {!MyEnum} enumObj
*/
function showEnum(enumObj) {
console.log(enumObj);
}
the solution i tested in WebStorm and VSCode for autocompletion is to use typeof MyEnum.
Its still invalid JSDoc, but supported for autocompletion by IDEs.
/**
* #param {typeof MyEnum} enumObj
*/
function showEnum(enumObj) {
console.log(enumObj);
}

Google closure compiler, JSC_INEXISTENT_PROPERTY issue with mixin/extend

The code below throws a warning/error when compiled with google closure compiler advanced mode.
JSC_INEXISTENT_PROPERTY: Property getJerseyNumber never defined on player
Any ideas how we can fix this?
var getDetails = {
getJerseyNumber: function() {
return Math.random();
}
};
/**
* #param {Object} source
* #param {Object} delta
*/
function mixIn(source, delta) {
for (var i in delta) {
source[i] = delta[i];
}
}
/**
* #type {{name: string , sport: string}}
*/
var player = {
name: 'Tom Brady',
sport: 'Football'
};
mixIn(player, /** #lends {player} */ getJerseyNumber);
alert(player.getJerseyNumber());
Using #lends work with object literals only. So adding this line will work, but any other ideas?
mixIn(player, /** #lends {player} */ {
getJerseyNumber: function() {
return Math.random();
}
});
According to the Closure documentation,
#lends
Indicates that the keys of an object literal should be treated as properties of some other object. This annotation should only appear on object literals.
This is described in more detail by jsdoc-toolkit.
For your example, I would use classes for common behavior.
/**
* #constructor
* #param {string} name
* #param {string} sport
*/
var Player = function(name, sport) {
this.name = name;
this.sport = sport;
};
Player.prototype.getJerseyNumber = function() {
return Math.random();
};
var player = new Player('Tom Brady', 'Football');
alert(player.getJerseyNumber());
Of course, I imagine your example has some additional complexity, but without code to represent that complexity, I can only guess what that is.
You may be after the proposed (but as yet nonexistant) #mixin annotation.

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

Recursive types in closure compiler

Is there a way to define a recursive type in closure compiler's typing syntax? In other words, could I define a type that includes itself in its definition?
/**
* A node on the tree.
* #type {{left: (Tree|null)}}
*/
var Tree = {
left: null
};
(side note: yes, this definition of a tree is wrong)
Although the above compiles with simple optimizations, it fails with advanced throwing the following error:
JSC_TYPE_PARSE_ERROR: Bad type annotation. Unknown type Tree at line 3 character 17
* #type {{left: (Tree|null)}}
Just a guess but can you put the type over the left part.
/**
* Not sure what goes here
*/
var Tree = {
/**
* #type {Tree}
*/
left: null
};
Also don't you need a constructor for a type
/**
* #constructor
*/
var Tree = function(){
this.left = new Tree()
};
/**
* #type {Tree}
*/
Tree.prototype.left = null;
Just so you know, you've now got infinite recursion if you instantiate the type with
var test = new Tree();
/**
* #typedef{{left:Tree}}
*/
var Tree;
or
/**
* #interface
*/
var Tree =function(){}
/** #type {Tree} */
Tree.prototype.left;

Categories

Resources