JSdoc for js closures - javascript

Using jsdoc version 3.6.2
I have a project with tons of jsdoc documentation written, but unfortunately jsdoc won't parse it into the format I'm expecting.
We have numerous js files that create objects and then use function closures to define methods on those objects. I would like jsdoc to pick up those methods and associate them with the given module.
/** #module #mymodule*/
var mymodule = {};
/** This module is my very special module */
(function() {
var started = false;
/**
* #memberof module:mymodule // I have tried this, #name, #lends, etc...
* Start the module
*/
mymodule.start = function() {
started = true;
};
})();
It seems no matter what I do, I can't get the start method to actually show up as a documented method of the mymodule module. If I use #alias on the method, then it shows up as a global on the jsdoc index page, but that doesn't make for a very readable index page if it just dumps 10k links.
Anyone know how I can get this method to show up on the mymodule.html page?

Related

Closure Compiler - best practice for JavaScript library projects?

I am trying to use Closure Compiler to minimize and validate my JavaScript library and I am struggling with one issue. I created a small project to highlight the problem. Here is the externs file with public API of my library. I defined it according to:
https://developers.google.com/closure/compiler/docs/externs-and-exports
/**
* #fileoverview Public API of my-lib.js
*
* #externs
*/
const myLib = {};
myLib.foo = function() {};
And here is the implementation:
// const myLib = {};
myLib.foo = function() {
console.log("foo");
};
So the problem is that if I uncomment the first line, I am getting this error:
my-lib.js:1:6: ERROR - [JSC_REDECLARED_VARIABLE_ERROR] Illegal redeclared variable: myLib
1| const myLib = {};
^^^^^^^^^^
If I don't, the output looks like this:
(function(){myLib.foo=function(){console.log("foo")};})()
Which is good, because myLib.foo didn't get renamed so the externs are working, but in the same time the myLib namespace has not been created.
What would be the best practice for solving such an issue, or if there is none, maybe there is some workaround instead?
I pushed this example to github:
https://github.com/morisil/closure-compiler-lib-example
Externs provide definitions for symbols that the compiler should consider to be already existing.
There are a couple of solutions:
/** #suppress {const,duplicate} */
const myLib = {}
or use a separate name internally and assign it in a way the compiler won't check:
globalThis['myLib'] = myInternalName;

Grouping Functions In JSDoc Generated Documentation

I'm using documentationjs (which uses jsdoc under the hood) to handle the generation of docs for a lib I'm working on. My lib is written is ES6 and is fully functional, and at present the documentation generated is an alphabetical list of all the functions from all the modules in the lib. This makes it very hard to find what you are looking for. How should I use jsdoc comments so that functions from one file are grouped together in the documentation?
For example, given the following file …
/**
* Docs for alpha
*/
export const alpha = () {};
/**
* Docs for beta
*/
export const beta = () {};
/**
* Docs for charlie
*/
export const charlie = () {};
… how should I use jsdoc comments to ensure the three functions are grouped together under 'Example' in the documentation?
I have tried defining a module at the top of the class: /** #module Example */ but although this generates an item called 'Example' in the docs, the functions are not grouped underneath it.
I have tried adding #memberof Example to the documentation of the individual functions, but this has no effect.
I am aware of this question, but it doesn't work for me, possibly because of the ES6 imports. There is no mention of its use with #module in the docs.
It appears that documentationjs doesn't support JSDoc style grouping in its generated docs, however, it is possible to group functions using a slightly different syntax. I discovered this through trial and error due to documentationjs's (ironically) poor documentation:
/** #module Example **/
/**
* #memberof Example
*/
const alpha = () => {};
Note: there is no module: prefix for the #member argument.
Perhaps you can try this workaround for now, until jsdoc implements grouping.
How to display Javascript methods in a 'group' in JSDOC?
I think you need to use #module in this way:
/** #module myModule */
/** will be module:myModule~foo */
var foo = 1;
/** will be module:myModule.bar */
var bar = function() {};
As described here.

No exported symbols with es6 modules library compiled by Closure Compiler

Starting point: Many js files are successfully compiled (no warning/error) by Closure Compiler (ADVANCED_OPTIMIZATIONS level) in a single library file.
In these js files:
goog.require and goog.provide are used to import/export things between them
/** #export */ is used in front of whatever (const/function/Class/var) is required outside the library.
Some HTML files include the library and some non compiled js accessing successfully to all whatever defined in this library.
What I want: move to es6 module syntax
What I did for each js file:
goog.require replaced by import with the list of Class, function from another js file
goog.provide removed and export added in front of each Class, function etc. required by another js file
Try 1: no change for the /** #export */ each time whatever is required outside the library.
Try 2: all /** #export */ whatever replaced by goog.exportSymbol('whatever', whatever)
This is sucessfully compiled (no warning/error, still with ADVANCED_OPTIMIZATIONS level).
The problem: now, for the same HTML files, all the whatever defined in the library are seen "undefined" by the browser. Indeed, when I type Object.keys( window ) in the console, I can see all the symbol names changed by the compiler (aa, ba, ca etc.) but none of my exported symbol whatever.
Example: demoVisitors is a const array defined in the library and required outside.
Before in the library file, I could see ... w("demoVisitors",[Oa,La,Ma,Na]); ... and the content was properly visible in the HTML page. After the es6 module change, I can see ... H("demoVisitors$$module$filemane",Oa); ... (filename being the file name in which demoVisitors is defined) for try 1 and H("demoVisitors",[Na,Ka,La,Ma]); for try 2. demoVisitors is undefined in the browser for the same page.
After further investigations, I found the solution.
Although loaded in the browser without any error in the console (except undefined whatever of course), my library was not executed. I simply moved the closure library ahead of the file stack to be compiled and my library was then properly executed by the browser with my symbols properly exported. See below for more details.
The 3 ways to export symbols are working in compiled es6 modules: /** #export */ whatever, goog.exportSymbol('whatever', whatever), window['whatever'] = whatever. The first 2 being a handy way for the third one (for root symbols).
Nevertheless /** #export */ myClass generates an unfriendly unobfuscated names like myClass$$module$source-filename-with-path.
To get the unobfuscated name myClass, avoid a goog function within my code and enable neatly both compiled/uncompiled mode, I removed the /** #export */ and add unobfuscateSymbol('myClass', myClass) after class myClass { ... }. It's my "own" function directly inspired from exportSymbol function defined in the closure library. This is only required for root symbols like classes, you can keep /** #export */ for all the symbols (properties, functions etc.) defined in the class.
Here is the source code:
export function unobfuscateSymbol(publicPath, object, objectToExportTo = window) {
// Same code can be used compiled and not compiled so unobfuscation only in case of obfuscation
if (unobfuscateSymbol.name !== 'unobfuscateSymbol') {
const /** Array<string> */ parts = publicPath.split('.');
let /** Object */ objToExportTo = objectToExportTo;
let /** string */ part;
const /** number */ nbOfParts = parts.length;
for (let /** number */ i = 0; i < nbOfParts; i += 1) {
part = parts[i];
if ((i === (nbOfParts - 1)) && object) {
objToExportTo[part] = object;
} else if (objectToExportTo[part] && objectToExportTo[part] !== Object.prototype[part]) {
objToExportTo = objectToExportTo[part];
} else {
objToExportTo[part] = {};
objToExportTo = objToExportTo[part];
}
}
}
}
How I identified the issue in details:
To understand the export issue, I tried to put a breakpoint in the exportSymbol function defined in the closure library while loading my HTML test page in the browser: no break...
I double checked this execution issue by adding a console.log("my library is being executed"): I was able to see the message in the goog.require/goog.provide version of my library but not in the es6 import/export version. Without execution, of course, no symbol exported.
I wrapped my library with a IIFE. Closure compiler argument: --output_wrapper "(function(){%output%})()" and an execution error message in my library appeared in the browser console. I discovered that goog.global, the base namespace for the Closure library, was undefined at the break time.
I moved the closure library ahead of the file stack to be compiled. Closure compiler arguments: –js closure-lib-path/base.js –js myfile1.js –js myfile2.js … to make sure compiled goog stuff is before the first exportSymbol call generated by the first /** #export */ compiled.
Execution and exports were OK then in the browser. I just added the unobfuscateSymbol function as described above to get the same friendly exported names as for the goog.require/goog.provide version of my library.

Correct way to avoid 'Unresolved variable ...' errors when using variables returned as a JSON object from an API [duplicate]

I have a function that takes data from server:
function getData(data){
console.log(data.someVar);
}
WebStorm says that someVar is an unresolved variable.
How can I get rid of such warnings?
I see several options:
Suppress warnings in IDE settings;
Add a JSON source file with fields (details);
Use array-like syntax: data['some_unres_var'];
Also, WebStorm is offering me to create namespace for the "data" (add an annotation like /** #namespace data.some_unres_var*/), create such field, or rename it.
Use JSDoc:
/**
* #param {{some_unres_var:string}} data
*/
function getData(data){
console.log(data.some_unres_var);
}
JSDoc the object. Then its members.
/**
* #param data Information about the object.
* #param data.member Information about the object's members.
*/
function getData(data){
console.log(data.member);
}
#property for local variables (non parameters).
Tested in PyCharm. #Nicholi confirms it works in WebStorm.
The {{ member:type }} syntax Andreas suggested may conflict with Django templates.
Thanks to Jonny Buchanan's answer citing the #param wiki.
To document arrays of objects, use [] brackets as JSDoc suggests:
/**
* #param data
* #param data.array_member[].foo
*/
All other answers are incorrect for the general case. What if you don't get data as a parameter? You don't have JSDoc then:
function niceApiCall(parameters) {
const result = await ... // HTTP call to the API here
for (const e of result.entries) {
.. // decorate each entry in the result
}
return result;
}
WebStorm will warn that "result.entries" is an unresolved variable (field).
The general solution is to add an #namespace declaration:
function niceApiCall(parameters) {
/** #namespace result.entries **/
const result = await ... // HTTP call to the API here
for (const e of result.entries) {
.. // decorate each entry in the result
}
return result;
}
Destructuring use, Luke.
function getData(data){
const {member} = data;
console.log(member);
}
using a dummy js file with anonymous function expression returning the json literal, as written at http://devnet.jetbrains.com/message/5366907, may be a solution. I can also suggest creating a fake variable that will hold this json value, and use this var as a value of #param annotation to let WebStorm know what the actual type is. Like:
var jsontext = {"some_unres_var":"val"};
/** #param {jsontext} data */
function getData(data){
console.log(data.some_unres_var);
}
See also http://devnet.jetbrains.com/message/5504337#5504337
To remove the warnings on The WebStorm IDE you can simply uncheck the inspection options for:
Unresolved Javascript function
Unresolved Javascript variable
ps. this will remove the warnings on the IDE, that I don't think is the best idea, because we will lost one of the best utilities in a IDE like Webstorm, which can worsen the quality of our code.
Even so, if you want to follow in the menu: File > Settings > Editor > Inspections we can disable the Javascript warnings
Like the following picture:

Bad type annotation. Unknown type

I keep getting the warning mentioned above but can't figure out why. I've added deps.js and deps.js contains a reference to the type.
Here is the offending code:
goog.provide("MyCompany.MyApp.dom");
MyCompany.MyApp.dom.getArticleAmountInput_=function(){
return document.getElementById("articleAmount");
};
/**
* Gets the article amount input value
* #type {function(this:MyCompany.MyApp.dom):number} */
MyCompany.MyApp.dom.getArticleAmount=function(){
var tmp=this.getArticleAmountInput_();
return (tmp)?tmp.value():undefined;
};
In deps.js:
goog.addDependency('../../MyCompany/MyApp/dom.js', [ 'MyCompany.MyApp.dom'], []);
Code loads in html so it does find the class at runtime. Here is how I compile the code:
java -jar ../../compiler.jar \
--compilation_level=ADVANCED_OPTIMIZATIONS \
--formatting=PRETTY_PRINT \
--warning_level=VERBOSE \
--summary_detail_level=3 \
--js=MyCompany/MyApp/dom.js \
--js=closure/goog/deps.js \
--js_output_file=out.js
And it keeps giving me the warning:
WARNING - Bad type annotation. Unknown type MyCompany.MyApp.dom
[update]
Tried to leave out the goog.provide completely and leave out the js=closure/goog/deps.js when compiling and changed everything to lower case but keep getting the same warning on this code:
//goog.provide("mycompany.myapp.dom");
var mycompany={};
mycompany.myapp={};
mycompany.myapp.dom={};
mycompany.myapp.dom.getArticleAmountInput_=function(){
return document.getElementById("articleAmount");
};
/**
* Gets the article amount input value
* #type {function(this:mycompany.myapp.dom):number} */
mycompany.myapp.dom.getArticleAmount=function(){
var tmp=this.getArticleAmountInput_();
return (tmp)?tmp.value():undefined;
};
[update]
The thing is that when I add a typedef I get warnings that myapp.dom is never defined but when I let the code determine what the type is I get Bad type annotation.
Tried to add a typedef like so:
/**
* #typedef {{getArticleAmount:function(this:myapp.dom):?number}}
*/
myapp.dom;
But don't realy see why as goog.provide should create the myapp.dom and the compiler should know that.
[update]
I have created the following myapp.workflow from constructors. Now the compiler should recognize the type and Eclipse can code assist. Not sure if this is the way to do it but I'll try to re factor a small project.
Setting up the main types in types.js
// source: js/mmyapp/types.js
goog.provide("myapp.types");
/** #constructor */
var gooblediegoog=function(){};
/** #constructor */
gooblediegoog.prototype.WorkFlow=function(){};
/** #constructor */
gooblediegoog.prototype.Dom=function(){};
myapp.types=new gooblediegoog();
A file that isn't used at all in my code but tells Eclipse how to auto complete:
// source: js/myapp/forCodeAssist.js
/** #const {boolean} */
var ALLWAYSFALSE=false;
if(ALLWAYSFALSE){
/**needed for Eclipse autocomplete
* #constructor
*/
var MyApp=function(){};
MyApp.prototype.types=new gooblediegoog();
Window.prototype.myapp=new MyApp();
MyApp.prototype.workflow=new myapp.types.WorkFlow();
MyApp.prototype.dom=new myapp.types.Dom();
}
An implementation of workflow:
// source: js/myapp/workflow.js
goog.provide("myapp.workflow");
goog.require("myapp.types");
goog.require("myapp.dom");
/** #returns number|undefined */
myapp.types.WorkFlow.prototype.createOrder=function(){
return myapp.dom.getArticleAmout();
};
myapp.workflow=new myapp.types.WorkFlow();
window['console'].log(myapp.workflow.createOrder());
This can be converted to a myapp.workflow.createOrder=... syntax by replacing myapp.types.WorkFlow.prototype with myapp.workflow, removing myapp.workflow=new myapp.types.WorkFlow() and removing the goog.require("myapp.types"). Maybe this can be automated in the build/compile process if needed.
I am not sure if creating a single object with the help from a constructor function is much more expensive than just having goog.require create myapp.workflow and adding properties to it as I used to do (and as it's done in closure library).
Anonymous types (namespaces or singletons) don't have specific types in Closure-compiler and cannot be used as a type name in an annotation. Only constructors and interfaces can be used as new type names. (typedefs can be used but are really just shorthand annotations)
In many cases the compiler will correctly infer the types and you will not need to annotate it. In your specific case, it looks like the problem is your use of the this keyword. Using the this keyword in a static object / namespace is dangerous and not supported. The compiler will raise a warning. While you can suppress the warning with a #this annotation, that is not the correct action here as you are not specifically specifying the this reference by using .call or .apply.
The solution is to use the full object name:
/**
* Gets the article amount input value
*/
mycompany.myapp.dom.getArticleAmount=function(){
var tmp=mycompany.myapp.dom.getArticleAmountInput_();
return (tmp)?tmp.value():undefined;
};
For the full technical details, reference the This this is your this post.

Categories

Resources