mirror of
https://github.com/jsdoc/jsdoc.git
synced 2025-12-08 19:46:11 +00:00
add plugin making it easier to link to overloaded methods (#179)
This commit is contained in:
parent
263e3ca2e4
commit
359fa84d3c
184
plugins/overloadHelper.js
Normal file
184
plugins/overloadHelper.js
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @author Jeff Williams <jeffrey.l.williams@gmail.com>
|
||||||
|
* @license Apache License 2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
50
plugins/test/fixtures/overloadHelper.js
vendored
Normal file
50
plugins/test/fixtures/overloadHelper.js
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* A bowl of non-spicy soup.
|
||||||
|
* @class
|
||||||
|
*//**
|
||||||
|
* A bowl of spicy soup.
|
||||||
|
* @class
|
||||||
|
* @param {number} spiciness - The spiciness of the soup, in Scoville heat units (SHU).
|
||||||
|
*/
|
||||||
|
function Soup(spiciness) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slurp the soup.
|
||||||
|
*//**
|
||||||
|
* Slurp the soup loudly.
|
||||||
|
* @param {number} dBA - The slurping volume, in A-weighted decibels.
|
||||||
|
*/
|
||||||
|
Soup.prototype.slurp = function(dBA) {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Salt the soup as needed, using a highly optimized soup-salting heuristic.
|
||||||
|
*//**
|
||||||
|
* Salt the soup, specifying the amount of salt to add.
|
||||||
|
* @variation mg
|
||||||
|
* @param {number} amount - The amount of salt to add, in milligrams.
|
||||||
|
*/
|
||||||
|
Soup.prototype.salt = function(amount) {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heat the soup by the specified number of degrees.
|
||||||
|
* @param {number} degrees - The number of degrees, in Fahrenheit, by which to heat the soup.
|
||||||
|
*//**
|
||||||
|
* Heat the soup by the specified number of degrees.
|
||||||
|
* @variation 1
|
||||||
|
* @param {string} degrees - The number of degrees, in Fahrenheit, by which to heat the soup, but
|
||||||
|
* as a string for some reason.
|
||||||
|
*//**
|
||||||
|
* Heat the soup by the specified number of degrees.
|
||||||
|
* @param {boolean} degrees - The number of degrees, as a boolean. Wait, what?
|
||||||
|
*/
|
||||||
|
Soup.prototype.heat = function(degrees) {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discard the soup.
|
||||||
|
* @variation discardSoup
|
||||||
|
*//**
|
||||||
|
* Discard the soup by pouring it into the specified container.
|
||||||
|
* @variation discardSoup
|
||||||
|
* @param {Object} container - The container in which to discard the soup.
|
||||||
|
*/
|
||||||
|
Soup.prototype.discard = function(container) {};
|
||||||
96
plugins/test/specs/overloadHelper.js
Normal file
96
plugins/test/specs/overloadHelper.js
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
/*global describe: true, expect: true, it: true, jasmine: true, xit: true */
|
||||||
|
describe('plugins/overloadHelper', function() {
|
||||||
|
var parser = new (require('jsdoc/src/parser')).Parser();
|
||||||
|
var plugin = require('plugins/overloadHelper');
|
||||||
|
var docSet;
|
||||||
|
|
||||||
|
require('jsdoc/plugins').installPlugins(['plugins/overloadHelper'], parser);
|
||||||
|
docSet = jasmine.getDocSetFromFile('plugins/test/fixtures/overloadHelper.js', parser);
|
||||||
|
|
||||||
|
it('should exist', function() {
|
||||||
|
expect(plugin).toBeDefined();
|
||||||
|
expect(typeof plugin).toBe('object');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export handlers', function() {
|
||||||
|
expect(plugin.handlers).toBeDefined();
|
||||||
|
expect(typeof plugin.handlers).toBe('object');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export a "newDoclet" handler', function() {
|
||||||
|
expect(plugin.handlers.newDoclet).toBeDefined();
|
||||||
|
expect(typeof plugin.handlers.newDoclet).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should export a "parseComplete" handler', function() {
|
||||||
|
expect(plugin.handlers.parseComplete).toBeDefined();
|
||||||
|
expect(typeof plugin.handlers.parseComplete).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('newDoclet handler', function() {
|
||||||
|
it('should not add unique longnames to constructors', function() {
|
||||||
|
var soup = docSet.getByLongname('Soup');
|
||||||
|
var soup1 = docSet.getByLongname('Soup()');
|
||||||
|
var soup2 = docSet.getByLongname('Soup(spiciness)');
|
||||||
|
|
||||||
|
expect(soup.length).toBe(2);
|
||||||
|
expect(soup1.length).toBe(0);
|
||||||
|
expect(soup2.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add unique longnames to methods', function() {
|
||||||
|
var slurp = docSet.getByLongname('Soup#slurp');
|
||||||
|
var slurp1 = docSet.getByLongname('Soup#slurp()');
|
||||||
|
var slurp2 = docSet.getByLongname('Soup#slurp(dBA)');
|
||||||
|
|
||||||
|
expect(slurp.length).toBe(0);
|
||||||
|
expect(slurp1.length).toBe(1);
|
||||||
|
expect(slurp2.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the "variation" property of the method', function() {
|
||||||
|
var slurp1 = docSet.getByLongname('Soup#slurp()')[0];
|
||||||
|
var slurp2 = docSet.getByLongname('Soup#slurp(dBA)')[0];
|
||||||
|
|
||||||
|
expect(slurp1.variation).toBe('');
|
||||||
|
expect(slurp2.variation).toBe('dBA');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not add to or change existing variations that are unique', function() {
|
||||||
|
var salt1 = docSet.getByLongname('Soup#salt');
|
||||||
|
var salt2 = docSet.getByLongname('Soup#salt(mg)');
|
||||||
|
|
||||||
|
expect(salt1.length).toBe(1);
|
||||||
|
expect(salt2.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not duplicate the names of existing numeric variations', function() {
|
||||||
|
var heat1 = docSet.getByLongname('Soup#heat(1)');
|
||||||
|
var heat2 = docSet.getByLongname('Soup#heat(2)');
|
||||||
|
var heat3 = docSet.getByLongname('Soup#heat(3)');
|
||||||
|
|
||||||
|
expect(heat1.length).toBe(1);
|
||||||
|
expect(heat2.length).toBe(1);
|
||||||
|
expect(heat3.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should replace identical variations with new, unique variations', function() {
|
||||||
|
var discard1 = docSet.getByLongname('Soup#discard()');
|
||||||
|
var discard2 = docSet.getByLongname('Soup#discard(container)');
|
||||||
|
|
||||||
|
expect(discard1.length).toBe(1);
|
||||||
|
expect(discard2.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parseComplete handler', function() {
|
||||||
|
// disabled because on the second run, each comment is being parsed twice; who knows why...
|
||||||
|
xit('should not retain parse results between parser runs', function() {
|
||||||
|
parser.clear();
|
||||||
|
docSet = jasmine.getDocSetFromFile('plugins/test/fixtures/overloadHelper.js', parser);
|
||||||
|
var heat = docSet.getByLongname('Soup#heat(4)');
|
||||||
|
|
||||||
|
expect(heat.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user