Commenting JavaScript for Google Closure Compiler for Singleton - javascript

I am trying to understand the JSDoc style for documenting JavaScript that is used with the JavaScript Closure Compiler. I have the JavaScript code below
// ==ClosureCompiler==
// #compilation_level ADVANCED_OPTIMIZATIONS
// ==/ClosureCompiler==
(function(){
/**
* #type Array.<string>
* #private
*/
var sb = [];
/**
* #const
* #type{{append: function(string): SingletonStringBuffer, toString: function(): string}}
*/
window['SingletonStringBuffer'] = {
/**
* #param {string} text
* #return {SingletonStringBuffer}
*/
append: function(text){
sb.push(text);
return SingletonStringBuffer;
},
/**
* #return {string}
*/
toString: function(){
return sb.join("");
}
};
}());
When I do an advanced compile on this code I am receiving 2 warnings.
JSC_TYPE_PARSE_ERROR: Bad type annotation. Unknown type SingletonStringBuffer at line 10 character 35
* #type{{append: function(string): SingletonStringBuffer, toString: function()...
^ JSC_TYPE_PARSE_ERROR: Bad type annotation. Unknown type SingletonStringBuffer at line 15 character 11
* #return {SingletonStringBuffer}
^
The function append returns a deference to the encapsulating object. The variable that it is returning ,SingletonStringBuffer, is declared... so I am not sure what is wrong or how to correct it.

You haven't created a named type as far as the compiler is concerned. For this case, I would expect you to create an interface:
/** #interface */
function StringBuffer() {
}
/**
* #param {string} text
* #return {StringBuffer}
*/
StringBuffer.prototype.append;
etc
This can be declared either in the code (if you are using advanced mode it will be stripped) or in your extern files (if you want the type without the code in simple mode).
You can then use it like so (in your case):
(function(){
/**
* #type Array.<string>
* #private
*/
var sb = [];
/**
* #const
* #type {StringBuffer}
*/
window['SingltonStringBuffer'] = {
/**
* #param {string} text
* #return {StringBuffer}
*/
append: function(text){
sb.push(text);
return SingltonStringBuffer;
},
/**
* #return {string}
*/
toString: function(){
return sb.join("");
}
};
}());

singletons work differently in closure. I have not seen an explicit annotation for it, but the compiler (in advanced mode) has some understanding of certain built-in functions
Singletons would be declared via the goog.addSingletonGetter function, here is a code sample
/**
* #constructor
* #extends {path.to.BaseClass}
*/
path.to.MyClass = function() {
goog.base(this);
};
goog.inherits(path.to.MyClass, path.to.BaseClass);
goog.addSingletonGetter(path.to.MyClass);
and that be it.
PS
you are getting the bad annotation because {SingltonStringBuffer} is never declared as a class.
PPS.
Some rambling on post the fact.
I suspect (but this is untested) that making the constructer private might work. Notice the trailing underscore in the example
/**
* #private -> NOTE THIS IS IN NO WAY VERIFIED
* #constructor
* #extends {path.to.BaseClass}
*/
path.to.MyClass_ = function() {
goog.base(this);
};
goog.inherits(path.to.MyClass, path.to.BaseClass);
goog.addSingletonGetter(path.to.MyClass);

Related

How to type a JavaScript function with JSDoc + TypeScript?

I'm trying to use tsc with plain, Vanilla JS and I'm stumped on how to declare the type a function.
It seems like it should be this simple:
/** #type PersonGreet */
person.greet = function greet(other) {
return `Hello ${other.name}, my name is ${person.name}!`;
};
Edit: /** #type PersonGreet */ is correct. The current behavior is a bug in tsc. The selected answer below gives valid workarounds.
Reduced Test Case
Ignore the fact that someone may want to refactor this into using classes or prototypes or some such - it serves well as a demonstration of the problem.
Repo: https://github.com/BeyondCodeBootcamp/hello-tsc
"use strict";
/**
* #typedef {Object} Person
* #property {String} name
* #property {PersonGreet} greet
*/
/**
* #typedef {Function} PersonGreet
* #param {Person} other
* #returns {String}
*/
let Person = {};
/**
* Creates a person
* #param {Object} p
* #param {String} p.name
* #returns {Person}
*/
Person.create = function (p) {
let person = {};
person.name = p.name;
/////////////////////////////////////////////////////////////////////////////////
//
// error TS7006: Parameter 'other' implicitly has an 'any' type. <======= WRONG!
//
/////////////////////////////////////////////////////////////////////////////////
/** #type PersonGreet */
person.greet = function greet(other) {
return `Hello ${other.name}, my name is ${person.name}!`;
};
return person;
};
module.exports = Person;
Incorrectly Typed as "any"
When I run tsc to check it gives an error about an implicit any:
tsc -p jsconfig.json
person.js:28:33 - error TS7006: Parameter 'other' implicitly has an 'any' type.
28 person.greet = function greet(other) {
~~~~~
Found 1 error in person.js:28
What to do?
To me this seems like a bug in tsc... but this is JS 101 stuff, surely there must be a way to type a function?
What annotation do I need to use to declare the function's type? Or can tsc / tsserver / typescript just not handle this kind of rudimentary use of JS?
Solution 1
You should use #callback instead of #function like so:
"use strict";
/**
* #typedef {Object} Person
* #property {String} name
* #property {PersonGreet} greet
*/
/**
* #callback PersonGreet
* #param {Person} other
* #returns {String}
*/
let Person = {};
/**
* Creates a person
* #param {Object} p
* #param {String} p.name
* #returns {Person}
*/
Person.create = function (p) {
let person = {};
person.name = p.name;
/** #type {PersonGreet} */
person.greet = function greet(other) {
return `Hello ${other.name}, my name is ${person.name}!`;
};
return person;
};
module.exports = Person;
Solution 2
Or, declare a #typedef using typescript syntax like the following:
/**
* #typedef {(other: Person) => string} PersonGreet
*/

JSDoc3: How to document a AMD module that returns a function

I'm trying to find a way to document AMD modules using JSDoc3.
/**
* Module description.
*
* #module path/to/module
*/
define(['jquery', 'underscore'], function (jQuery, _) {
/**
* #param {string} foo Foo-Description
* #param {object} bar Bar-Description
*/
return function (foo, bar) {
// insert code here
};
});
Sadly none of the patterns listed on http://usejsdoc.org/howto-commonjs-modules.html work for me.
How can I generate a proper documentation that lists the parameters and return value of the function exported by the module?
As of the latest stable version (3.2.2) I do not think there is a way to do use jsdoc to produce documentation that will show that the module itself accepts parameters and returns some value. The closest I can come to the ideal is this:
/**
* Module description.
*
* #module path/to/module
*/
define(['jquery', 'underscore'], /** #lends module:path/to/module */
function (jQuery, _) {
/**
* The following function documents the parameters that the module
* takes and its return value. Do not call as
* <code>module.self(...)</code> but as <code>module()</code>.
*
* #param {string} foo Foo-Description
* #param {object} bar Bar-Description
*/
return function self(foo, bar) {
// insert code here
};
});
The generated documentation for the module will have an extra inner function named self.
The following seems to generate a result which looks quite acceptable:
/**
* Module description
*
* #module path/to/module
*/
define(['jquery', 'underscore'], function (jQuery, _) {
/**
* Description for function.
*
* #param {string} foo Foo-Description
* #param {object} bar Bar-Description
*/
var exports = function () {
// insert code here
};
return exports;
});
Which describes the module and the function with something like:
require("path/to/module")(foo, bar)
That's not perfect for AMD modules but I think the reader of the documentation is able to understand what the module exports.

Closure Compiler does not always enforce type safety?

I'm having an issue with closure compiler where it's not enforcing strict type checks for some strange reason. It correctly checks type safety when setting a variable where it's declared, but is failing to throw an type error when passing an object.
/**
* #public
* #param x {number}
*/
SomeClass.prototype.setterMethod = function(x) {
this.var1 = x;
};
var a = new SomeClass();
a.setterMethod({}); // SHOULD THROW AN ERROR!!!!!!
Why isn't closure compiler enforcing type safety here? It correctly functions if I declare the variable:
this.var1 = {}; // correctly throws an error
However it is not enforcing strict type safety checks on function parameters, or when setting the class member variable outside of the member declaration. I've posted the full code and output below. Is there a way to force closure to force these types of checks? Or am I doing something wrong here?
// ==ClosureCompiler==
// #compilation_level ADVANCED_OPTIMIZATIONS
// #warning_level VERBOSE
// #output_file_name default.js
// ==/ClosureCompiler==
/**
* #class SomeClass
* #constructor
*/
function SomeClass() {
/**
* #protected
* #type {number}
*/
this.var1;
};
/**
* #public
* #param x {number}
*/
SomeClass.prototype.setterMethod = function(x) {
this.var1 = x;
};
/**
* #public
* #returns {number}
*/
SomeClass.prototype.getterMethod = function() {
return this.var1;
};
/**
* #type {SomeClass}
*/
var a = new SomeClass();
a.setterMethod({});
console.log(a.getterMethod());
// output: -- NO WARNINGS!!!!
// var a=new function(){};a.a={};console.log(a.a);
There are two issues occurring:
The Closure-compiler web service runs in a type of "demo" mode and assumes all undeclared variables are external. There is currently no way to disable this. Testing with the command line compiler doesn't show the same issues.
Your JSDoc paramater annotation is not correct. It should be #param {number} x (you have the type and name reversed).

Adding sub-properties to an existing property-list in jsdoc

I am trying to automate a particular module in our JS library and am stuck at a point where I want to define a set of properties (lets say an object that goes as construction parameter of a class).
/**
* This function initiates world peace!
* #constructor
* #param {object} defaults - The options to initiate peace.
* #param {number} defaults.issues - The number of issues being taken up.
* #param {string} defaults.source - The name of the location where process starts.
*/
var WorldPeace = function (defaults) {
// code here
};
It is well and good had all properties of the construction was defined at one location. Unfortunately, my code has a number of modules contributing to that construction properties. Lets say, at some other portion of the code (in a later file) causes to have a couple of more properties
* #param {Date} defaults.start - The date when the process started.
* #param {Date} defaults.stop - The date when the process should stop.
How do I go about adding to the original set of properties that I had previously defined for WorldPeace function? Doing something like a mixin or subclassing the properties would be going overboard! As such, if I can simply inject to a property list definition it would be great.
The easiest method is to use a record type:
/**
* This function initiates world peace!
* #constructor
* #param {{issues: number, source: string}} defaults - options to initiate peace.
*/
var WorldPeace = function (defaults) {
// code here
};
You could also implement an interface:
/** #interface */
var WordPeaceDefaults;
/** #type {number} */
WorldPeaceDefaults.prototype.issues;
/** #type {string} */
WorldPeaceDefaults.prototype.source;
/**
* This function initiates world peace!
* #constructor
* #param {WorldPeaceDefaults} defaults - options to initiate peace.
*/
var WorldPeace = function (defaults) {
// code here
};
/**
* #constructor
* #implements {WorldPeaceDefaults}
*/
function MyWorldPeaceDefaults() {}
/** #type {number} */
MyWorldPeaceDefaults.prototype.issues = 0;
/** #type {string} */
MyWorldPeaceDefaults.prototype.source = '';
WordPeace(new MyWorldPeaceDefaults);
Another way to do it would be to use a typedef:
/**
* #typedef {{
* issues: number,
* source: string
* }}
*/
var WorldPeaceOptions;
/**
* #constructor
* #param {WorldPeaceOptions} defaults
*/
var WorldPeace = function (defaults) {
// code here
};

How to document anonymous functions (closure) with jsdoc-toolkit

I am trying to document my code using JSDoc-toolkit. My code starts by being wrapped with a self-executing anonymous function. How in the world do I document this? I've spent nearly all day on this. JS Docs will not recognize anything inside of the anonymous function closure due to it not knowing what to do with it. It breaks and none of my comments come through.
My code looks something like this.
/**
* #fileoverview BLA BLA BLA
*/
/**
* This is where I don't know what to put.
*/
(function () {
"use strict";
/** or here */
var stlib = function (param, param, param) {
/** or here */
var share = {
/** or here */
config: {
button: DOM Element,
property: blablabla
},
init: function () { ...some init code here}
};
share.init();
};
widgets.add("share", stlib);
}());
Thank you!
You can use #namespace with #name and #lends like this:
/**
* #name MyNamespace
* #namespace Hold all functionality
*/
(function () {
"use strict";
/** #lends MyNamespace*/
var stlib = function (param, param, param) { ...All of my code...};
}());
You can't document nested functions directly. But you can do something like this:
/**
* #module foobar
*/
/**
* #function
* #author Baa
* #name hello
* #description Output a greeting
* #param {String} name - The name of the person to say hello
*/
(function hello(name) {
/**
* #function
* #author Baz
* #inner
* #private
* #memberof module:foobar
* #description Check if the argument is a string (see: {#link module:foobar~hello})
* #param {String} string - The string
* #returns {String} Returns true if string is valid, false otherwise
*/
var isString = function checkString(string) { return typeof string === 'string'; };
if (isString(name))
console.log('Hello ' + name + '!');
}('Mr. Bubbles'));
Here I'm setting checkString as private and inner to be descriptive (since nested functions can't be described), And then I pass in -p to document private functions. Finally, I add a link to the parent function for reference.
I think jsdoc is unnecessarily finicky and needs to be replaced with something better. It's a port of javadoc, so it has a lot of things that are relevant to Java but not JS, and vice versa. There are very common JS idioms, like closures or nested functions, that are hard or impossible to document.
I always check my namepaths and debug using the --explain flag.

Categories