Code coverage report for master/plugins/overloadHelper.js

Statements: 91.43% (64 / 70)      Branches: 76.67% (23 / 30)      Functions: 94.12% (16 / 17)      Lines: 91.43% (64 / 70)      Ignored: none     

All files » master/plugins/ » overloadHelper.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186                                                                                  1   1 2 2 2 4       4     2     1 4   4 2 2     2 2       4     1 4     1 3 3 3   1 1   1 2   2 3 3     3 3 2           1   2 4       2                         3 1     2     3     1 9       9 9 9   9 3     3 1 2 2       3     3 6 6       3 3       9   9     1   1       11 9         1      
/**
 * 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
 */
'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) {
        Iif (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 || '';
        Iif (param.variable) {
            name = '...' + name;
        }
        Eif (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
        Iif ( !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;
    }
};