jsdoc/plugins/overloadHelper.js
Jeff Williams 8b26b6d669 remove author tags, and clean up JSDoc comments without leading asterisks
Most of the existing author tags are grossly out of date at this point. The definitive reference for who has contributed what is available at https://github.com/jsdoc3/jsdoc/graphs/contributors.
2017-07-06 00:01:11 -07:00

186 lines
5.7 KiB
JavaScript

/**
* The Overload Helper plugin automatically adds a signature-like string to the longnames of
* overloaded functions and methods. In JSDoc, this string is known as a _variation_. (The longnames
* of overloaded constructor functions are _not_ updated, so that JSDoc can identify the class'
* members correctly.)
*
* Using this plugin allows you to link to overloaded functions without manually adding `@variation`
* tags to your documentation.
*
* For example, suppose your code includes a function named `foo` that you can call in the
* following ways:
*
* + `foo()`
* + `foo(bar)`
* + `foo(bar, baz)` (where `baz` is repeatable)
*
* This plugin assigns the following variations and longnames to each version of `foo`:
*
* + `foo()` gets the variation `()` and the longname `foo()`.
* + `foo(bar)` gets the variation `(bar)` and the longname `foo(bar)`.
* + `foo(bar, baz)` (where `baz` is repeatable) gets the variation `(bar, ...baz)` and the longname
* `foo(bar, ...baz)`.
*
* You can then link to these functions with `{@link foo()}`, `{@link foo(bar)}`, and
* `{@link foo(bar, ...baz)`. Note that the variation is based on the names of the function
* parameters, _not_ their types.
*
* If you prefer to manually assign variations to certain functions, you can still do so with the
* `@variation` tag. This plugin will not change these variations or add more variations for that
* function, as long as the variations you've defined result in unique longnames.
*
* If an overloaded function includes multiple signatures with the same parameter names, the plugin
* will assign numeric variations instead, starting at `(1)` and counting upwards.
*
* @module plugins/overloadHelper
*/
'use strict';
// lookup table of function doclets by longname
var functionDoclets;
function hasUniqueValues(obj) {
var isUnique = true;
var seen = [];
Object.keys(obj).forEach(function(key) {
if (seen.indexOf(obj[key]) !== -1) {
isUnique = false;
}
seen.push(obj[key]);
});
return isUnique;
}
function getParamNames(params) {
var names = [];
params.forEach(function(param) {
var name = param.name || '';
if (param.variable) {
name = '...' + name;
}
if (name !== '') {
names.push(name);
}
});
return names.length ? names.join(', ') : '';
}
function getParamVariation(doclet) {
return getParamNames(doclet.params || []);
}
function getUniqueVariations(doclets) {
var counter = 0;
var variations = {};
var docletKeys = Object.keys(doclets);
function getUniqueNumbers() {
var format = require('util').format;
docletKeys.forEach(function(doclet) {
var newLongname;
while (true) {
counter++;
variations[doclet] = String(counter);
// is this longname + variation unique?
newLongname = format('%s(%s)', doclets[doclet].longname, variations[doclet]);
if ( !functionDoclets[newLongname] ) {
break;
}
}
});
}
function getUniqueNames() {
// start by trying to preserve existing variations
docletKeys.forEach(function(doclet) {
variations[doclet] = doclets[doclet].variation || getParamVariation(doclets[doclet]);
});
// if they're identical, try again, without preserving existing variations
if ( !hasUniqueValues(variations) ) {
docletKeys.forEach(function(doclet) {
variations[doclet] = getParamVariation(doclets[doclet]);
});
// if they're STILL identical, switch to numeric variations
if ( !hasUniqueValues(variations) ) {
getUniqueNumbers();
}
}
}
// are we already using numeric variations? if so, keep doing that
if (functionDoclets[doclets.newDoclet.longname + '(1)']) {
getUniqueNumbers();
}
else {
getUniqueNames();
}
return variations;
}
function ensureUniqueLongname(newDoclet) {
var doclets = {
oldDoclet: functionDoclets[newDoclet.longname],
newDoclet: newDoclet
};
var docletKeys = Object.keys(doclets);
var oldDocletLongname;
var variations = {};
if (doclets.oldDoclet) {
oldDocletLongname = doclets.oldDoclet.longname;
// if the shared longname has a variation, like MyClass#myLongname(variation),
// remove the variation
if (doclets.oldDoclet.variation || doclets.oldDoclet.variation === '') {
docletKeys.forEach(function(doclet) {
doclets[doclet].longname = doclets[doclet].longname.replace(/\([\s\S]*\)$/, '');
doclets[doclet].variation = null;
});
}
variations = getUniqueVariations(doclets);
// update the longnames/variations
docletKeys.forEach(function(doclet) {
doclets[doclet].longname += '(' + variations[doclet] + ')';
doclets[doclet].variation = variations[doclet];
});
// update the old doclet in the lookup table
functionDoclets[oldDocletLongname] = null;
functionDoclets[doclets.oldDoclet.longname] = doclets.oldDoclet;
}
// always store the new doclet in the lookup table
functionDoclets[doclets.newDoclet.longname] = doclets.newDoclet;
return doclets.newDoclet;
}
exports.handlers = {
parseBegin: function() {
functionDoclets = {};
},
newDoclet: function(e) {
if (e.doclet.kind === 'function') {
e.doclet = ensureUniqueLongname(e.doclet);
}
},
parseComplete: function() {
functionDoclets = null;
}
};