I have been looking for a way to document this in js:
function foo(param1: 'opt1' | 'opt2') { ... }
But found nothing because I don't know how can I search for it and didn't find any example alike to what I want to do in JSDoc
I have tried with:
/**
* #param {string} param1 - Receives either 'opt1' | 'opt2'
*/
function foo(param1) { ... };
But I think there would be a better way to specify if param1 receives a string that's either 'these' or 'that'. Another solution than only put in the param description what you can pass as value like shown in my example.
You can document your function this way in case of strings:
/**
* #param {('opt1' | 'opt2')} param1
*/
function foo(param1) { };
Using the same method you can also handle integer values
/**
* #param {(1 | 2)} param1
*/
function foo(param1) { };
This will allow editors to give suggestions using the documented values like following
I'm centralizing user-facing message string templates in a Node API I'm writing so all my strings live together. I'd like to be able to build these strings dynamically from elsewhere in the code by passing a template function the arguments it needs to build the message, but the problem is that I can't figure out how to structure JSDoc to perform autocompletion for the object that's passed to my "string builders", AKA the functions returned from template below. See below for an example of what this would ideally look like. You can mostly ignore the template function implementation as it's taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates.
// strings.js
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates
function template(strings, ...keys) {
return function(...values) {
var dict = values[values.length - 1] || {};
var result = [strings[0]];
keys.forEach(function(key, i) {
var value = Number.isInteger(key) ? values[key] : dict[key];
result.push(value, strings[i + 1]);
});
return result.join('');
};
}
/**
* Returns a string for welcoming mentees to the platform.
* #param {object} params
* #param {string} params.name The name of the mentee.
*/
export const menteeOnboardingWelcome = template`Hi ${'name'}, welcome!`;
/**
* Returns a string for when a mentee and mentor are matched.
* #param {object} params
* #param {string} params.menteeName The name of the mentee.
* #param {string} params.mentorName The name of the mentor.
*/
export const menteeMentorMatched = template`${'menteeName'} and ${'mentorName'} have been matched!`;
console.log(menteeMentorMatched({ menteeName: 'Jack', mentorName: 'Jill' }));
When typing that last line, I'd like autocomplete (in VSCode, if that's helpful) to list menteeName and mentorName plus their associated documentation, but since template returns a function that takes in ...values that's what it shows instead. How can I structure my JSDoc to show what I want instead of ...values?
Screenshot
Another option, of course, is to simply wrap the string builder functions in a function wrapper for documentation:
export const menteeMentorMatched = ({ menteeName, mentorName }) => `...`
I suspect this is the only way to make JSDoc work in this case. While this wouldn't be too bad, it's just a little more verbose than I'd like it to be since I'd like to avoid as much bloat as humanly possible in this file.
Form-Elements added by JavaScript to a Fluid-Form are not passed as Argument.
How to do it the right way?
How to dynamically add Form-Fields with JavaScript?
Expected behavior:
Form is submmitted to "subscribeSubmitAction"
Validation of "subscribeSubmitAction" fails
Fallback to "subscribeAction" is called
"$subscription" is set and assigned (NOT HAPPENING) - instead "$subscription" is allways NULL (but subscription.test is set in the Fluid-Form)
My Guess:
The PropertyMapper is removing "subscription.childs" because they are not in "TrustedProperties" - How can I add them?
Why "$subscription" is allways NULL - I have no Idea!
Controller:
namespace VENDOR\EXTENSION\Controller;
class EventController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
{
public function initializeAction()
{
if ($this->arguments->hasArgument('subscription'))
{
//Try to add "subscription.childs" to the PropertyMapperConfiguration
$subscriptionConfiguration = $this->arguments->getArgument('subscription')->getPropertyMappingConfiguration();
$subscriptionConfiguration->allowProperties();
$subscriptionConfiguration->forProperty('*')->allowAllProperties();
$subscriptionConfiguration->forProperty('*')->allowCreationForSubProperty('*');
$subscriptionConfiguration->forProperty('*')->forProperty('*')->allowAllProperties();
$subscriptionConfiguration->forProperty('*')->forProperty('*')->allowCreationForSubProperty('*');
$subscriptionConfiguration->forProperty('childs')->allowProperties();
$subscriptionConfiguration->forProperty('childs')->allowCreationForSubProperty('*');
$subscriptionConfiguration->forProperty('childs.*')->allowProperties();
$subscriptionConfiguration->forProperty('childs.*')->allowCreationForSubProperty('*');
print_r($this->arguments->getArgument('subscription')->getValue()); // => NULL
}
}
/**
* subscribeAction
* #param array $event
* #param array $subscription
* #ignorevalidation $subscription
*/
public function subscribeAction($event,$subscription = null)
{
print_r($subscription); // => NULL
$this->view->assign('event',$event);
$this->view->assign('subscription',$subscription);
}
/**
* subscribeSubmitAction
* #param array $event
* #param array $subscription
* #param string $g_recaptcha_reponse
* #validate $g_recaptcha_reponse NotEmpty
*/
public function subscribeSubmitAction($event,$subscription = null, $g_recaptcha_reponse = null)
{
/**
* This Method will never be called because the Validation of "$g_recaptcha_reponse" must fail (it's empty)
*/
}
}
Template:
<f:debug>{subscription}</f:debug>
<f:form action="subscribeSubmit" name="subscription" object="{subscription}">
<f:form.hidden name='event[]' value='{event.uid}' />
<f:form.textfield property="test" />
<!--suppose this was added by javascript-->
<input type="text" name="tx_extension_plugin[subscription][childs][0][test]" value="{subscription.childs.0.test}">
<f:form.hidden name='g_recaptcha_reponse' value='' />
<f:form.submit name="submit" value="Submit" />
</f:form>
I would recommend to check the request via Network panel of your browser. Do you see the arguments there? Do they follow the naming schema of tx_extension_plugin[subscription][...?
They should be contained in the request within extbase.
Perhaps you shouldn't add them to the existing argument subscription but instead create another argument. You can validate it the same way and errors should be available the same way within Fluid.
You can be right with "TrustedProperties" I'm not sure about that. But if so, you definitely should be able to workaround that within your initializeAction method.
In JSDoc there exists the possibility to document the exact types of array contents like this:
/** #param {Array.<MyClass>} myClasses An array of MyClass objects. */
TestClass.protoype.someMethod = function( myClasses ){
myClasses[0].aMethodOnMyClass();
}
This makes code completion in IDEs like WebStorm actually provide the right type information after the [0].. This works well for the Array type, however I have my own collection types where I would like to make use of this feature, too. The problem is I cannot find the right syntax (maybe because there is none, yet). I would love to be able to declare my class somehow like this:
/**
* #typeparam {T} the type parameter
* #constructor {Test2.<T>}
* */
Test2 = function(){};
/**
* #returns {T} a value of type T, where T is the generic type parameter of Test2
*/
Test2.prototype.getGenericValue = function(){}
This syntax or feature does not work with my IDE and is not listed here, so I am wondering whether there is a syntax for this use-case, either for WebStorm or any other JS authoring tool.
You can try using #template tag (undocumented tag used in Google Closure library - extremely limited form of generics). Something like:
/**
* Search an array for the first element that satisfies a given condition and
* return that element.
* #param {Array.<T>|goog.array.ArrayLike} arr Array or array
* like object over which to iterate.
* #param {?function(this:S, T, number, ?) : boolean} f The function to call
* for every element. This function takes 3 arguments (the element, the
* index and the array) and should return a boolean.
* #param {S=} opt_obj An optional "this" context for the function.
* #return {T} The first array element that passes the test, or null if no
* element is found.
* #template T,S
*/
goog.array.find = function(arr, f, opt_obj) {
var i = goog.array.findIndex(arr, f, opt_obj);
return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i];
};
WebStorm uses this tag for type hinting - i.e. if we pass array of strings to goog.array.find in the sample above , IDE will know that return type is string, so string completion options will be suggested etc.
Not sure if this is what you are looking for... The post that looks related is here.
In the meantime, support for this feature has been finalized and is now documented on the Closure Compiler JSDOC page for generics.
Basically it works like this for ES6 classes:
/** #template T */
class Foo {
/** #return {T} */
get() { ... };
/** #param {T} t */
set(t) { ... };
}
... and like this for pre-ES6 code:
/**
* #constructor
* #template T
*/
Foo = function() { ... };
and
/** #return {T} */
Foo.prototype.get = function() { ... };
/** #param {T} t */
Foo.prototype.set = function(t) { ... };
WebStorm 7.0 did not support this feature at the time the original answer was written, but as of today (2019) all JetBrains IDEs understand this syntax, properly.
The following code works fine for me in WebStorm 8.
/** #type {Array.<MyPair.<Event, Array.<Thought>>>} */
scope.pairs = [];
/**
* #template TFirst, TSecond
*/
function MyPair(first, second){
this.first = first;
this.second = second;
}
/** #type {TFirst} */
MyPair.prototype.first = null;
/** #type {TSecond} */
MyPair.prototype.second = null;
...
function Event(){}
...
...
function Thought(){}
...
I'm developing a JavaScript library that will be used by 3rd party developers.
The API includes methods with this signature:
function doSomething(arg1, arg2, options)
arg1, arg2 are 'required' simple type arguments.
options is a hash object containing optional arguments.
Would you recommend to validate that:
- argument types are valid?
- options attributes are correct? For example: that the developer didn't pass by mistake onSucces instead of onSuccess?
why do popular libraries like prototype.js do not validate?
You have the right to decide whether to make a "defensive" vs. a "contractual" API. In many cases, reading the manual of a library can make it clear to it's user that he should provide arguments of this or that type that obey these and those constraints.
If you intend to make a very intuitive, user friendly, API, it would be nice to validate your arguments, at least in debug mode. However, validation costs time (and source code => space), so it may also be nice to leave it out.
It's up to you.
Validate as much as you can and print useful error messages which help people to track down problems quickly and easily.
Quote this validation code with some special comments (like //+++VALIDATE and //--VALIDATE) so you can easily remove it with a tool for a high-speed, compressed production version.
Thanks for the detailed answers.
Below is my solution - a utility object for validations that can easily be extended to validate basically anything...
The code is still short enough so that I dont need to parse it out in production.
WL.Validators = {
/*
* Validates each argument in the array with the matching validator.
* #Param array - a JavaScript array.
* #Param validators - an array of validators - a validator can be a function or
* a simple JavaScript type (string).
*/
validateArray : function (array, validators){
if (! WL.Utils.isDevelopmentMode()){
return;
}
for (var i = 0; i < array.length; ++i ){
WL.Validators.validateArgument(array[i], validators[i]);
}
},
/*
* Validates a single argument.
* #Param arg - an argument of any type.
* #Param validator - a function or a simple JavaScript type (string).
*/
validateArgument : function (arg, validator){
switch (typeof validator){
// Case validation function.
case 'function':
validator.call(this, arg);
break;
// Case direct type.
case 'string':
if (typeof arg !== validator){
throw new Error("Invalid argument '" + Object.toJSON(arg) + "' expected type " + validator);
}
break;
}
},
/*
* Validates that each option attribute in the given options has a valid name and type.
* #Param options - the options to validate.
* #Param validOptions - the valid options hash with their validators:
* validOptions = {
* onSuccess : 'function',
* timeout : function(value){...}
* }
*/
validateOptions : function (validOptions, options){
if (! WL.Utils.isDevelopmentMode() || typeof options === 'undefined'){
return;
}
for (var att in options){
if (! validOptions[att]){
throw new Error("Invalid options attribute '" + att + "', valid attributes: " + Object.toJSON(validOptions));
}
try {
WL.Validators.validateArgument(options[att], validOptions[att]);
}
catch (e){
throw new Error("Invalid options attribute '" + att + "'");
}
}
},
};
Heres a few examples of how I use it:
isUserAuthenticated : function(realm) {
WL.Validators.validateArgument(realm, 'string');
getLocation: function(options) {
WL.Validators.validateOptions{
onSuccess: 'function',
onFailure: 'function'}, options);
makeRequest : function(url, options) {
WL.Validators.validateArray(arguments, ['string',
WL.Validators.validateOptions.carry({
onSuccess : 'function',
onFailure : 'function',
timeout : 'number'})]);
We have to discover and eliminate problems as soon as possible. If don't use TypeScript or Flow, you rather do it with a validation lib. It will help you avoiding spending hours hunting obscure errors caused by invalid types given as arguments. It looks like many take it serious - https://www.npmjs.com/package/aproba gets currently 9M(!) downloads per week.
To me it doesn't suite, explained here http://dsheiko.com/weblog/validating-arguments-in-javascript-like-a-boss
I go with https://www.npmjs.com/package/bycontract that based on JSDoc expressions:
import { validate } from "bycontract";
const PdfOptionsType = {
scale: "?number"
}
function pdf( path, w, h, options, callback ) {
validate( arguments, [
"string",
"!number",
"!number",
PdfOptionsType,
"function=" ] );
//...
return validate( returnValue, "Promise" );
}
pdf( "/tmp/test.pdf", 1, 1, { scale: 1 } ); // ok
pdf( "/tmp/test.pdf", "1", 1, { scale: 1 } ); // ByContractError: Argument #1: expected non-nullable but got string
On methods you can just reuse existing JSDoc comment block:
import { validateJsdoc, typedef } from "bycontract";
typedef("#PdfOptionsType", {
scale: "number"
});
class Page {
#validateJsdoc(`
#param {string} path
#param {!number} w
#param {!number} h
#param {#PdfOptionsType} options
#param {function=} callback
#returns {Promise}
`)
pdf( path, w, h, options, callback ) {
return Promise.resolve();
}
}
However I keep this validation on dev/test environments, but skip it on live:
import { config } from "bycontract";
if ( process.env.NODE_ENV === "production" ) {
config({ enable: false });
}
It depends. How big this library would be? It is said that typed languages are better for big projects with complex API. Since JS is to some extent hybrid, you can choose.
About validation - I don't like defensive programming, the user of the function shall be obliged to pass valid arguments. And in JS size of code matters.
An intermediate way would be to return a reasonable default value (e.g. null) when required arguments are missing. In this way, the user's code will fail, not yours. And it will probably be easier for them to figure out what is the issue in their code rather than in yours.
When I've developed APIs like these in the past, I've validated anything that I feel is a "major" requirement - in your example, I'd verify the first two arguments.
As long as you specify sensible defaults, it should be pretty simple for your user to determine that "optional" arguments aren't specified correctly, since it won't make any change to the application, but everything will still work properly.
If the API is complex, I'd suggest following Aaron's advice - add comments that can be parsed by a compressor around your validation so the developers get the benefit of validation, but can extract the extra dead weight when pushing the code into production.
EDIT:
Here're some examples of what I like to do in the cases where validation is necessary. This particular case is pretty simple; I probably wouldn't bother with validation for it, since it really is trivial. Depending on your needs, sometimes attempting to force types would be better than validation, as demonstrated with the integer value.
Assume extend() is a function that merges objects, and the helper functions exist:
var f = function(args){
args = extend({
foo: 1,
bar: function(){},
biz: 'hello'
}, args || {});
// ensure foo is an int.
args.foo = parseInt(args.foo);
//<validation>
if(!isNumeric(args.foo) || args.foo > 10 || args.foo < 0){
throw new Error('foo must be a number between 0 and 10');
}
if(!isFunction(args.bar)){
throw new Error('bar must be a valid function');
}
if(!isString(args.biz) || args.biz.length == 0){
throw new Error('biz must be a string, and cannot be empty');
}
//</validation>
};
EDIT 2:
If you want to avoid common misspellings, you can either 1) accept and re-assign them or 2) validate the argument count. Option 1 is easy, option 2 could be done like this, although I'd definitely refactor it into its own method, something like Object.extendStrict() (example code works w/ prototype):
var args = {
ar: ''
};
var base = {
foo: 1,
bar: function(){},
biz: 'hello'
};
// save the original length
var length = Object.keys(base).length;
// extend
args = Object.extend(base, args || {});
// detect if there're any extras
if(Object.keys(args).length != length){
throw new Error('Invalid argument specified. Please check the options.')
}
Don't validate. More code is more code the user has to download, so it's a very real cost on the user and production systems. Argument errors are easy enough to catch by the developer; don't burden the user with such.