JSDoc Documenting repeated properties in exported object - javascript

I have several modules that all act the same way and export several functions inside of an object, like so:
module.exports = {
a:function(paramA,paramB) {
return 1;
},
b:function(paramC,paramD) {
return 'a';
}
}
They all follow the same schema (take these parameters, do things, and return this type). I'd like to be able to document all these files within the same file, so that documentation isn't repeated everywhere. The problem I'm running into is that if I create a #typedef with these functions specified, it is ignored if done like so:
/**
* #typedef {Object} myModuleType
* #property {functionType} a
*/
/**
* #module A
* #type {myModuleType}
*/
module.exports = {}
And if I create an interface, it complains that the methods are not implemented if done like so:
/**
* #interface myModuleType
*/
/**
* #function
* #name myModuleType#a
* #param paramA
* #param paramB
* #return {number}
*/
/**
* #module A
* #implements {myModuleType}
*/
module.exports = {}
Any ideas on how to get this to work?

So the original #type annotation actually works, it just doesn't autocomplete as it should in WebStorm after being documented.
This is being tracked at YouTrack for WebStorm as to when it will be fixed.
Edit: As of 12/18/2017, this has been fixed in a recent build of WebStorm and should hit the main branch soon.

Related

JSDoc: how to document a function that returns an instance of a passed constructor?

For instance, I have the following function:
function createInstanceOf(ObjectConstructor) {
return new ObjectConstructor;
}
I want to make WebStorm autocompletion working when I pass a class as an argument. For example: if I call createInstanceOf(ClassA) I want to see autocompletion for ClassA instance, if I call createInstanceOf(ClassB) – for ClassB instance. So JSDoc function has to be generic.
It's easy to define a generic function with JSDoc and make #return value type be the same as #param, but I've found no way to treat #param type as a constructor for returned object.
So this doesn't work:
/**
* #param {T} ObjectConstructor
* #returns {T}
* #template T
*/
function createInstanceOf(ObjectConstructor) {
return new ObjectConstructor;
}
I also tried to make it working this way:
/**
* #param {function(new:T)} ObjectConstructor
* #returns {T}
* #template T
*/
function createInstanceOf(ObjectConstructor) {
return new ObjectConstructor;
}
But maybe I use closure types wrong, or WebStorm can't resolve such generic types.
If there're several solutions for JSDoc, I'd like to find out which ones work specifically with WebStorm IDE autocompletion.
Thank you
You probably don't need this anymore, but I too have been stuck on this for months so for other people wondering, you can do it like this:
/**
* #template T
* #param {new() => T} ObjectConstructor
* #returns {T}
*/
function createInstanceOf(ObjectConstructor) {
return new ObjectConstructor;
}
Got the answer from this article

Closure Compiler externs - Referencing a complex function

How can I refer to a complex function type with the Google closure compiler and not an instance of the constructor function?
externs.js - Externs for CoolLibrary.matchSomething
/** #externs */
/** #const */
const CoolLibrary = {};
/**
* #param {!Object} object The object to inspect.
* #param {!Object} source The object of property values to match.
* #param {!function(!Object): !Object} customizer The function to customize
* comparisons.
* #return {boolean} Returns `true` if `object` is a match, else `false`.
* #constructor
*/
CoolLibrary.matchSomething = function(object, source, customizer) {};
/**
* #param {string} src
* #return {!Object}
*/
function require(src) {}
foo.js - application code
goog.module('foo');
const isMatchWith = /** #type {!CoolLibrary.matchSomething} */ (require('coollibrary.matchsomething'));
const foo = isMatchWith({}, {}, (val) => {});
I'm invoking it like so:
java -jar closure-compiler-v20161201.jar --js=foo.js --externs=externs.js --new_type_inf
Here's a runnable version at the Closure Compiler Debugger
It errors with:
foo.js:3: WARNING - Bad type annotation. Unknown type Lodash.isMatchWith
const isMatchWith = /** #type {!Lodash.isMatchWith} */ (require('lodash.ismatchwith'));
^
0 error(s), 1 warning(s), 72.7% typed
It works if I use #typedef but that loses most of the information. Is there a better way to add type information than using typedef like below?
/** #typedef {function(!Object, !Object, function(!Object):!Object):boolean} */
CoolLibrary.matchSomething;
Function definitions are not type names. You can use a typedef to prevent retyping this data if you import the function it in multiple places. However, if you only import the information in a single place, then a typedef would be overkill.
For a single import, you would simply duplicate the function annotation in the type cast at your require call.
const isMatchWith =
/** #type {function(!Object, !Object, function(!Object):!Object):boolean} */
(require('lodash.ismatchwith'));
The compiler handles these cases for you when module bundling is used, but that requires all of the source files to be compatible with the compiler and provided as part of the compilation. This is not currently possible for an external library.

How to use jsdoc for a meteor application

What is the correct way to use JSDoc within a meteor application?
Below there is my way to document the code, but there is no 'connection' between all parts.
Everything belongs to the example template, so the output of jsdoc should be structured correctly.
How can I improve this documentation?
Template.example.helpers({
/**
* Get all categories
* #name categories
* #return {Cursor}
* #summary Cursor categories
*/
categories() {
return Categories.find({});
},
});
Template.example.onCreated(
/**
* If template is created (still not rendered), ReactiveDict variable is initialized
* #function
* #name onCreated
* #summary Set ReactiveDict
*/
function() {
this.something = new ReactiveDict();
}
);
Template.example.events({
/**
* Clicking on category will show a console message
* #event
* #summary Console message
*/
'click #category': (event, template) => {
console.log('nice')
}
});
Where I work, we encountered the same situation a few months ago, what we concluded was that jsdoc was just not adapted to auto-doc with Meteor's implementation. We ended up using https://github.com/fabienb4/meteor-jsdoc which gives us full satisfaction.
It basically extends jsdoc syntax with meteor specific keywords so you can specify what is a Meteor.call, what is a collection helper and so on. Once configured and running, the output is basically what Meteor's documentation used to look like before 1.3 (as the author says, it's "Based on the templates from Meteor own docs.").
Edit: As we don't use Meteor's templating system, I don't have an existing example but I adapted a collection helper to your case, tell me if there is anything unclear. The trick is to play with #memberOf, #isMethod, etc depending on how you want your doc to be displayed.
/**
* #memberOf Foo Template
* #summary Returns Bar conversation attached to stuff
* #param {String} fooId
* #param {Boolean} forced # Use to create foo when it's not found
* #returns {Object} Bar
*/
getStuff: function(fooId, forced=false) {
'use strict';
/** your code **/
}

JSDoc #param together with #deprecated

I have a JavaScript function getting some parameters including object types. However, one property of a parameter, which is an object, will be used as deprecated. I would like to indicate this situation in the documentation, however I don't know how to use #param tag with #deprecated. Consider the example below:
/**
* This function does something.
*
* #name myFunction
* #function
* #since 3.0
* #param {function} [onSuccess] success callback
* #param {function} [onFailure] failure callback
* #param {object} [options] options for function
* #param {string} [options.lang] display language
* #param {string} [options.type] type of sth
*/
this.myFunction= function (onSuccess, onFailure, options) {
//do something
}
I want to deprecate "type" property of "options" object. How can I do that, or can I?
Official JSDoc documentation does not indicate that the #deprecated tag can be used for deprecating anything else than an entire symbol.
The #deprecated tag can be used to document that for example a function as a whole has been deprecated.
/**
* #deprecated since version 2.0.0
*/
function old () {
}
You can of course, as #Droogans said in the comments, add something like deprecated: in front of the #param description. If a developer somehow still ends up using the deprecated feature, you could implement a warning of some sorts.
/**
* #param {string=} bar - Deprecated: description
*/
function foo (bar) {
if (bar) {
console.warn('Parameter bar has been deprecated since 2.0.0')
}
}
A suggestion is using typescript, like so:
function test(
options: {
/**
* #deprecated use newName instead
*/
name?: string,
newName?: string
}) {
}

How to have an array of classes with static methods when using the closure compiler?

I'm having issues with an API I'm developing. I want to have an array of classes, each of which implement the same interface and have static methods. I will have another class that will cycle through the array of classes and pick one based on the return value of one of the static methods, but the compiler is removing the static methods and the javascript encounters a TypeError when it tries to call the static method. I've tried searching several forums, but I can't find anything that helps. This error only happens in advanced compilation mode, not simple, and even happens in advanced compilation in debug mode.
I am thinking this is because the compiler doesn't realize I am using the function because of the complicated way I am calling it.
I have put together a somewhat simplified example of what I am trying to do that causes an error.
See below for the code:
The Racer interface is an interface that is implemented by two classes: Runner and Jogger. The interface has one static method called getModesOfTransportation that returns an array of the ways that racer can move, and an instance method, called getTransportationModes, that does the same thing. This file also defines a custom type, racing.RacerClass, that (I think) makes a type that is a function that returns a racer object when called with the new keyword. I formed this based on https://developers.google.com/closure/compiler/docs/js-for-compiler#types. I would think that I should define that the type also has a static method, but I can't figure out how. Could this custom type be the culprit of the error?
racer.js:
goog.provide('racing.Racer');
/** #typedef function(new:racing.Racer)*/
racing.RacerClass={};// My custom typedef
/**
* Interface for racers.
* #interface
*/
racing.Racer=function(){};
/**
* Gets the ways this racer can move.
* #return {Array.<string>} The ways this racer can move.
*/
racing.Racer.getModesOfTransportation=function(){}
/**
* Gets the ways this racer can move.
* #return {Array.<string>} The ways this racer can move.
*/
racing.Racer.prototype.getTransportationModes=function(){}
The Helper class stores a list of Racer constructors. The registerRacer function accepts a constructor for a Racer and adds it to the list. The constructor function passed will also have a static getModesOfTransportation function on it. The getRacers function returns a list of the racers that have been registered.
helper.js:
goog.provide('racing.Helper');
/**
* A collection of functions to help with finding racers.
*/
racing.Helper={};
/**
* An array of racers.
* #type {Array.<racing.RacerClass>}
* #private
*/
racing.Helper.racers_=[]
/**
* Adds the given racer to the list.
* #param {racing.RacerClass} racer A racer to add to the list of racers.
*/
racing.Helper.registerRacer=
function(racer){
racing.Helper.racers_.push(racer);
}
/**
* Gets an array of registered racers.
* #return Array.<racing.RacerClass> A list of all registered racers.
*/
racing.Helper.getRacers=
function(){
return racing.Helper.racers_;
}
The Jogger class implements the Racer interface, the return value of the two functions is ['jog'] At the end of the file, it registers itself with the helper.
jogger.js:
goog.provide('racing.Jogger');
goog.require('racing.Racer');
goog.require('racing.Helper');
/**
* This racer can jog.
* #constructor
*/
racing.Jogger=function(){
console.log('Jogger is going');
};
/**
* Gets the ways this racer can move.
* #static
* #return {Array.<string>} The ways this racer can move.
*/
racing.Jogger.getModesOfTransportation=
function(){
return ['jog'];//I can jog
}
/**
* Gets the ways this racer can move.
* #return {Array.<string>} The ways this racer can move.
*/
racing.Jogger.prototype.getTransportationModes=
function(){
return ['jog'];//I can jog
}
//Register this racer
racing.Helper.registerRacer(racing.Jogger);
The Runner class also implements the Racer interface, but the return value of the two functions is ['run'] At the end of the file, it registers itself with the helper.
runner.js:
goog.provide('racing.Runner');
goog.require('racing.Racer');
goog.require('racing.Helper');
/**
* This racer can run.
* #constructor
*/
racing.Runner=
function(){
console.log('Runner is going');
};
/**
* Gets the ways this racer can move.
* #static
* #return {Array.<string>} The ways this racer can move.
*/
racing.Runner.getModesOfTransportation=
function(){
return ['run'];//I can run
}
/**
* Gets the ways this racer can move.
* #return {Array.<string>} The ways this racer can move.
*/
racing.Runner.prototype.getTransportationModes=
function(){
return ['run'];//I can run
}
//Register this racer
racing.Helper.registerRacer(racing.Runner);
The Organizer class has a public function called startRace that gets and stores an instance of a Racer that can run by calling the protected getRunner function. The getRunner function cycles through the list of racers and tries to find one that can run, but, once the code has been compiled, the racerClass.getModesOfTransportation() fails saying that getModesOfTransportation is undefined.
organizer.js:
goog.provide('racing.Organizer');
goog.require('racing.Helper');
goog.require('goog.array');
/**
* Class that helps with starting a race.
* #constructor
*/
racing.Organizer=function(){}
/**
* A racer that can run.
* #protected
* #returns {racing.Racer}
*/
racing.Organizer.prototype.runner=null;
/**
* Get a racer that can run.
* #protected
* #returns {racing.Racer}
*/
racing.Organizer.prototype.getRunner=
function(){
//Cycle through the racers until we find one that can run.
goog.array.findIndex(racing.Helper.getRacers(),
function(racerClass){
if(goog.array.contains(racerClass.getModesOfTransportation(),'run')){
this.runner=new racerClass();
return true;
}
else return false;
},this
);
}
/**
* Starts a race.
*/
racing.Organizer.prototype.startRace=
function(){
this.runner=this.getRunner();
}
The final file just includes all of the classes for the compiler.
api.js:
//Include the racers
goog.require('racing.Runner');
goog.require('racing.Jogger');
//Include the organizer and export its properties
goog.require('racing.Organizer')
goog.exportSymbol('racing.Organizer', racing.Organizer);
goog.exportProperty(racing.Organizer.prototype, 'startRace', racing.Organizer.prototype.startRace);
Running new racing.Organizer().startRace(); on the compiled code, in debug mode, yields the following error, and when I look at the compiled code, the getModesOfTransportation function no longer exists:
Uncaught TypeError: Object function () {
console.log("Runner is going")
} has no method '$getModesOfTransportation$'
$$racing$Organizer$$$$$startRace$$
(anonymous function)
I'd like to be able to get this to work without having to split the class into a class with just static functions, and a class with just the constructor, because that would make the code confusing. I've tried to figure this out, but I can't.
Thanks in advance for any ideas/suggestions.
I think you might be able to use #expose to stop the compiler from removing members. Or maybe I mis-understand what you're asking.
As stated above "static" methods are collapsed, and #expose prevents this by exporting them. The alternatives are is to indirectly add the property:
function helperAddMyProp(obj, value) {
obj.myProp = value;
}
/** #construcor */
function Foo() {};
helperAddMyProp(Foo, 1);
It will likely get inlined later but after properties have been collapsed. This demonstrates the problem (on closure-compiler.appspot.com):
// ==ClosureCompiler==
// #compilation_level ADVANCED_OPTIMIZATIONS
// #output_file_name default.js
// #formatting pretty_print
// #debug true
// ==/ClosureCompiler==
function Foo(name) {
alert('Hello, ' + name);
}
Foo.prop = 2;
Foo("me");
alert(Foo.prop);
And this the solution:
// ==ClosureCompiler==
// #compilation_level ADVANCED_OPTIMIZATIONS
// #output_file_name default.js
// #formatting pretty_print
// #debug true
// ==/ClosureCompiler==
function addProp(obj, value) {
obj.prop = value;
}
function Foo(name) {
alert('Hello, ' + name);
}
addProp(Foo,2);
Foo("me");
alert(Foo.prop);
If you change RacerClass to #constructor, it will work.
/** #typedef function(new:racing.Racer)*/
racing.RacerClass={};// My custom typedef
Use #typedef only for object attributes consistency. Its instanceof is Object. But you need racing.RacerClass instanceof to be RacerClass.
EDIT:
change to:
/**
* #constructor
* #implements {racing.Racer}
*/
racing.RacerClass={};// My custom typedef

Categories

Resources