Let's say I have 3 simple files:
app.js
const A = require('./a');
const B = require('./b');
let first = new A("hello");
let second = new B(first);
a.js
/**
* #constructor
* #param {*} val Value
*/
function a(val) {
this.val = val;
}
module.exports = a;
b.js
/**
* #constructor
* #param {a} a - a instance
*/
function b(a) {
this.a = a;
console.log(this.a.val); // no hint / autocomplete suggestions
}
module.exports = b;
Is it possible to get Intellisense to show fields/methods of class "a" in file "b.js" without importing/requiring it when it's passed as argument?
Related
I have a wrapper class like this (for a undo-redo system using command pattern):
class Command {
constructor(doFunction, undoFunction) {
this.doFunction = doFunction;
this.undoFunction = undoFunction;
}
do = (args) => { return this.doFunction(args) }
undo = (args) => { return this.undoFunction(args) }
}
How can I add JSDoc to the do and undo functions for then use the same #param types from provided this.doFunction and this.undoFunction, so when I use:
/** #param {number} n */
func1 = (n) => { return n+1 }
/** #param {number} n */
func2 = (n) => { return n-1 }
myCommand = new Command(func1, func2)
myCommand.do(...) // Here I want the intellisense to ask me for a `n` #param of type number
I appreciate the help.
You need to type the class, not the functions, because you pass the functions themselves as arguments in the constructor
I would do it like this
/**
* #typedef {function(arg: number): number} NumberFunc
*/
class Command {
/**
* #param {NumberFunc} doFunction
* #param {NumberFunc} undoFunction
*/
constructor(doFunction, undoFunction) {
this.doFunction = doFunction;
this.undoFunction = undoFunction;
}
/** #param {number} args */
do = (args) => { return this.doFunction(args) }
/** #param {number} args */
undo = (args) => { return this.undoFunction(args) }
}
/** #param {number} n */
func1 = (n) => { return n+1 }
/** #param {number} n */
func2 = (n) => { return n-1 }
/** #param {string} n */
func3 = (n) => { return n-1 }
const myCommand = new Command(func1, func2); // ok
const badCommand = new Command(func1, func3); // bad
const myCommand.do(1); // ok
const myCommand.do('1'); // bad
What other options are there for passing and using arguments in a function using an object besides these two?
Option 1:
let timerClosure = function ({
period, // number
funStart, // function
funEnd, // function
funStartArguments = [],
funEndArguments = [],
warming = 0
}) {// something }
Option 2:
let timerClosure = function (timerConfigObj) {
let period = timerConfigObj.period; // number
let funStart = timerConfigObj.funStart;
let funEnd = timerConfigObj.funEnd;
let funStartArguments = timerConfigObj.funStartArguments || [];
let funEndArguments = timerConfigObj.funStartArguments || [];
let warming = timerConfigObj.warming || 0;
}
Those, or other ways of spinning them, are basically it. Well, those and using an array, but if you use an array you may as well use discrete parameters, you'll have the same issue with order being significant and the problems with that as you get to more than three parameters.
Another way to spin what you have:
let timerClosure = function (timerConfigObj) {
const {
period, // number
funStart, // function
funEnd, // function
funStartArguments = [],
funEndArguments = [],
warming = 0
} = timerConfigObj;
// ...
};
You've said "...and describing" in the title but not the text. If that part is important to you, you can describe these more completely by using JDDoc annotations, which many IDEs can read and present to you (even if you never actually run JSDoc) when you're using the function:
/**
* Does something nifty with timers and closures.
*
* #param {Object} options - Options for the nifty thing.
* #param {number} options.period - `period` description...
* #param {function} options.funStart - `funStart` description...
* #param {function} options.funEnd - `funEnd` description...
* #param {array} options.funStartArguments - `funStartArguments` description...
* #param {array} options.funEndArguments - `funEndArguments` description...
* #param {number} options.warning - `warning` description...
*/
let timerClosure = function ({
period, // number
funStart, // function
funEnd, // function
funStartArguments = [],
funEndArguments = [],
warming = 0
}) {
// ...
};
Similarly, if you create a TypeScript type/interface and document its properties, IDEs will show that to you as well.
/**
* Options for `timerClosure`
*/
interface TimerClosureOptions {
/**
* Period description...
*/
period: number;
funStart: function;
funEnd: function;
funStartArguments?: any[];
funEndArguments?: any[];
warming?: number;
}
/**
* Does something nifty with timers and closures.
*
* #param {TimerClosureOptions} options - Options for the nifty thing.
*/
let timerClosure = function ({
period,
funStart,
funEnd,
funStartArguments = [],
funEndArguments = [],
warming = 0
}: TimerClosureOptions) {
// ...
};
I am using JSDoc for parameter documentation.
It is clear how to document the parameter types for many_prompts, but what is the right way to document the function it returns?
/**
* #param {Number} - number of times to prompt
* #return {Function(prompt{Number})} - the returned function
*/
function many_prompts(count) {
return function(prompt) {
for(var i=0; i < count; i++) alert(prompt);
}
}
//Example of use:
var y =many_prompts(3);
y('Hello World');
This seems to be working for me.
/**
* #param {Number} count - number of times to prompt
* #return {function(): void} - the returned function
*/
manyPrompts(count) {
/**
* My inner function
*
* #param {object} prompt Some parameter
*/
const inner = function(prompt) {
for (let i=0; i < count; i++) {
alert(prompt);
};
};
return inner;
}
You can document the inner function and then reference it like so
/**
* #param {Number} - number of times to prompt
* #return {many_prompts~inner} - the returned function
*/
function many_prompts(count){
/**
* My inner function
*
* #param {object} prompt Some parameter
*/
var inner = function(prompt){
for(var i=0;i<count;i++) alert(prompt)
};
return inner;
}
The way I prefer:
/**
* #param {number} count - number of times to prompt
* #returns { (prompt:string) => void } - the returned function
*/
manyPrompts(count) {
/**
* My inner function
*
* #param {object} prompt Some parameter
*/
const inner = function(prompt) {
for (let i=0; i < count; i++) {
alert(prompt);
};
};
return inner;
}
This is my solution for this.
I'm not describing a return in the first function and also document the inner function, which results in getting the documentation from the inner function.
/**
* Function to create a Function with multiple prompt messages
* #function
* #param {Number} count - number of times to prompt
*/
function many_prompts(count) {
/**
* To prompt a value multiple times
* #function
* #param {String} prompt - parameter to prompt
* #return {Function} prompt the input parameter
*/
return function(prompt) {
for(var i=0; i < count; i++) alert(prompt);
}
}
//Example of use:
var y = many_prompts(3);
y('Hello World');
Which is then shown e.g. in vscode like this...
For the outer function:
For the inner function:
You can also add an additional description when assigning the function, to describe the difference
/**
* Function to prompt a value 3 times
* #function
* #param {Number} prompt - parameter to prompt
* #return {Function} prompt the input parameter
*/
const y = many_prompts(3);
y('Hello World');
I am aware that similar questions have been asked before, but methodology changes quickly so I'm seeking to understand current best practices. (In fact, as recently as 2 days ago, Chad Killingsworth added a comment to an accepted answer from three years ago that #expose annotation is now deprecated.)
I'm using the module pattern. Working JSFIDDLE of the below code:
/** #const */
var MATHCALCS = (function () {
'use strict';
var MY = {};
/**
* #constructor
* #param {!Object} obj
* #expose
*/
MY.ModuleStruct = function (obj) {
/** #expose */
this.color = (obj.color !== undefined) ? obj.color : null;
/** #expose */
this.size = (obj.size !== undefined) ? obj.size : null;
};
/**
* #expose
*/
MY.ModuleStruct.prototype.clone = function () {
return new MY.ModuleStruct({
"color": this.color,
"size": this.size
});
};
MY.moduleProperty = 1;
/**
* #type {function(!Array<number>)}
* #expose
*/
MY.moduleMethod = function (a) {
var i, x = 0;
for (i = 0; i < a.length; i += 1) {
x = x + a[i];
}
return x;
};
return MY;
}());
window["MATHCALCS"] = MATHCALCS;*
Currently, using #expose annotation, above can be minified with Closure in advance mode and the following calls work (minified example):
// call a public method
alert(MATHCALCS.moduleMethod([1, 2, 3]));
// allocate a new structure
var ms = new MATHCALCS.ModuleStruct({
"color": "red",
"size": "small"
});
alert(ms.color + '\t' + ms.size);
// clone a second instance
var ms2 = ms.clone();
alert(ms2.color + '\t' + ms2.size);
alert(ms !== ms2); // cloned objs are not equal
// and directly update the properties of the object
ms2.color = "white";
ms2.size = "large";
alert(ms2.color + '\t' + ms2.size);
If possible, without changing away from the module pattern, I would like to update code (approx 10,000 lines) to use #export annotation. However, when I replace #expose with #export Closure raises this error:
ERROR - #export only applies to symbols/properties defined in the global scope.
Q: Is it possible, and if so, how should the above code be annotated to work with ADVANCED_OPTIMIZATIONS?
I am aware that I can possibly use this type of notation:
MY["ModuleStruct"] = MY.ModuleStruct;
MY["ModuleStruct"]["prototype"]["clone"] = MY.ModuleStruct.prototype.clone;
but exporting object properties this way will become tedious. Further JSLint complains about weird assignments so I would rather use JSDocs annotation.
Until the issue raised by #ChadKillingsworth is resolved, here's a solution which will enable you to use #export with only minor modifications to your code:
/** #const */
var MATHCALCS = {};
goog.scope(function () {
'use strict';
var MY = MATHCALCS;
/**
* #constructor
* #param {!Object} obj
* #export
*/
MY.ModuleStruct = function (obj) {
this.color = (obj.color !== undefined) ? obj.color : null;
this.size = (obj.size !== undefined) ? obj.size : null;
};
/**
* #export
*/
MY.ModuleStruct.prototype.clone = function () {
return new MY.ModuleStruct({
"color": this.color,
"size": this.size
});
};
MY.moduleProperty = 1;
/**
* #type {function(!Array<number>)}
* #export
*/
MY.moduleMethod = function (a) {
var i, x = 0;
for (i = 0; i < a.length; i += 1) {
x = x + a[i];
}
return x;
};
});
The changes are:
Change the #expose tags to #export.
Create an empty MATHCALCS object outside the module wrapper function, and make the MY alias point to it.
Instead of executing the module wrapper function immediately (IIFE), pass it to goog.scope(). This enables aliasing within scope functions, allowing the compiler to work out that the exported symbols are being defined on the global MATHCALCS object. This prevents the compiler from raising the error ("#export only applies to symbols/properties defined in the global scope").
Remove the following items, which are not needed:
The #export tags on this.color and this.size
return MY;
window["MATHCALCS"] = MATHCALCS;
When compiled with this command:
java -jar compiler.jar \
--js closure/goog/base.js \
--js mathcalcs.js \
--js_output_file mathcalcs.min.js \
--compilation_level ADVANCED_OPTIMIZATIONS \
--generate_exports \
--formatting PRETTY_PRINT \
--output_wrapper '(function() {%output%}).call(window);'
you'll get:
(function() {var f = this;
function g(a, d) {
var b = a.split("."), c = f;
b[0] in c || !c.execScript || c.execScript("var " + b[0]);
for (var e;b.length && (e = b.shift());) {
b.length || void 0 === d ? c[e] ? c = c[e] : c = c[e] = {} : c[e] = d;
}
}
;function h(a) {
this.color = void 0 !== a.color ? a.color : null;
this.size = void 0 !== a.size ? a.size : null;
}
g("MATHCALCS.ModuleStruct", h);
h.prototype.clone = function() {
return new h({color:this.color, size:this.size});
};
h.prototype.clone = h.prototype.clone;
g("MATHCALCS.moduleMethod", function(a) {
var d, b = 0;
for (d = 0;d < a.length;d += 1) {
b += a[d];
}
return b;
});
}).call(window);
The g() function is the compiled version of goog.exportSymbol() – see the #export docs for more details.
Note: if you want to run the code uncompiled, you'll need to load the Closure Library, or define goog.scope() yourself:
var goog = {};
goog.scope = function(fn) {
fn();
};
Here's a fork of your JSFiddle with all these changes.
Just starting out with mocha and cannot for the life of me figure out why it thinks Helper is undefined at the indicated line/columns below:
test.js
var assert = require('assert'),
helper = require('../src/js/helper.js');
describe('helper', function() {
describe('#parseValue', function() {
it('should return number of minutes for a properly formatted string', function() {
assert.equal(1501, (new Helper()).parseValue('1d 1h 1m', 'when'));
^^^^^^^^^^^^
});
});
});
helper.js
(function(exports) {
'use strict';
function Helper(opts) {
this.opts = opts || {};
/**
* Parse a value based on its type and return a sortable version of the original value
*
* #param {string} val input value
* #param {string} type type of input value
* #returns {mixed} sortable value corresponding to the input value
*/
this.parseValue = function(val, type) {
switch (type) {
case 'when':
var d = val.match(/\d+(?=d)/),
h = val.match(/\d+(?=h)/),
m = val.match(/\d+(?=m)/);
if (m)
m = parseInt(m, 10);
if (h)
m += parseInt(h, 10) * 60;
if (d)
m += parseInt(d, 10) * 1440;
val = m;
break;
default:
break;
}
return val;
};
}
exports.helper = Helper;
})(this);
I wrote a quick test in the browser without mocha to ensure my helper.js functions were accessible and it worked fine, so I really am at a loss. I am running this directly on my server by calling mocha from the command line in my directory.
You never define Helper in test.js—only helper on this line:
var helper = require('../src/js/helper.js');
Use the lower case helper that you defined.
By the way, you might want to change your exports line in helper.js from this:
exports.helper = Helper;
To this:
exports.Helper = Helper;
Then use helper in test.js like so:
assert.equal(1501, (new helper.Helper()).parseValue('1d 1h 1m', 'when'));
Or just do something like this:
var Helper = require('../src/js/helper.js').Helper;