/** * 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 */ // lookup table of function doclets by longname let functionDoclets; function hasUniqueValues(obj) { let isUnique = true; const seen = []; Object.keys(obj).forEach(key => { if (seen.includes(obj[key])) { isUnique = false; } seen.push(obj[key]); }); return isUnique; } function getParamNames(params) { const names = []; params.forEach(param => { let name = param.name || ''; if (param.variable) { name = `...${name}`; } if (name !== '') { names.push(name); } }); return names.length ? names.join(', ') : ''; } function getParamVariation({params}) { return getParamNames(params || []); } function getUniqueVariations(doclets) { let counter = 0; const variations = {}; const docletKeys = Object.keys(doclets); function getUniqueNumbers() { docletKeys.forEach(doclet => { let newLongname; while (true) { counter++; variations[doclet] = String(counter); // is this longname + variation unique? newLongname = `${doclets[doclet].longname}(${variations[doclet]})`; if ( !functionDoclets[newLongname] ) { break; } } }); } function getUniqueNames() { // start by trying to preserve existing variations docletKeys.forEach(doclet => { variations[doclet] = doclets[doclet].variation || getParamVariation(doclets[doclet]); }); // if they're identical, try again, without preserving existing variations if ( !hasUniqueValues(variations) ) { docletKeys.forEach(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) { const doclets = { oldDoclet: functionDoclets[newDoclet.longname], newDoclet: newDoclet }; const docletKeys = Object.keys(doclets); let oldDocletLongname; let 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(doclet => { doclets[doclet].longname = doclets[doclet].longname.replace(/\([\s\S]*\)$/, ''); doclets[doclet].variation = null; }); } variations = getUniqueVariations(doclets); // update the longnames/variations docletKeys.forEach(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() { functionDoclets = {}; }, newDoclet(e) { if (e.doclet.kind === 'function') { e.doclet = ensureUniqueLongname(e.doclet); } }, parseComplete() { functionDoclets = null; } };