UNPKG

5.9 kBJavaScriptView Raw
1/**
2 * The Overload Helper plugin automatically adds a signature-like string to the longnames of
3 * overloaded functions and methods. In JSDoc, this string is known as a _variation_. (The longnames
4 * of overloaded constructor functions are _not_ updated, so that JSDoc can identify the class'
5 * members correctly.)
6 *
7 * Using this plugin allows you to link to overloaded functions without manually adding `@variation`
8 * tags to your documentation.
9 *
10 * For example, suppose your code includes a function named `foo` that you can call in the
11 * following ways:
12 *
13 * + `foo()`
14 * + `foo(bar)`
15 * + `foo(bar, baz)` (where `baz` is repeatable)
16 *
17 * This plugin assigns the following variations and longnames to each version of `foo`:
18 *
19 * + `foo()` gets the variation `()` and the longname `foo()`.
20 * + `foo(bar)` gets the variation `(bar)` and the longname `foo(bar)`.
21 * + `foo(bar, baz)` (where `baz` is repeatable) gets the variation `(bar, ...baz)` and the longname
22 * `foo(bar, ...baz)`.
23 *
24 * You can then link to these functions with `{@link foo()}`, `{@link foo(bar)}`, and
25 * `{@link foo(bar, ...baz)`. Note that the variation is based on the names of the function
26 * parameters, _not_ their types.
27 *
28 * If you prefer to manually assign variations to certain functions, you can still do so with the
29 * `@variation` tag. This plugin will not change these variations or add more variations for that
30 * function, as long as the variations you've defined result in unique longnames.
31 *
32 * If an overloaded function includes multiple signatures with the same parameter names, the plugin
33 * will assign numeric variations instead, starting at `(1)` and counting upwards.
34 *
35 * @module plugins/overloadHelper
36 * @author Jeff Williams <jeffrey.l.williams@gmail.com>
37 * @license Apache License 2.0
38 */
39'use strict';
40
41// lookup table of function doclets by longname
42var functionDoclets;
43
44function hasUniqueValues(obj) {
45 var isUnique = true;
46 var seen = [];
47 Object.keys(obj).forEach(function(key) {
48 if (seen.indexOf(obj[key]) !== -1) {
49 isUnique = false;
50 }
51
52 seen.push(obj[key]);
53 });
54
55 return isUnique;
56}
57
58function getParamNames(params) {
59 var names = [];
60
61 params.forEach(function(param) {
62 var name = param.name || '';
63 if (param.variable) {
64 name = '...' + name;
65 }
66 if (name !== '') {
67 names.push(name);
68 }
69 });
70
71 return names.length ? names.join(', ') : '';
72}
73
74function getParamVariation(doclet) {
75 return getParamNames(doclet.params || []);
76}
77
78function getUniqueVariations(doclets) {
79 var counter = 0;
80 var variations = {};
81 var docletKeys = Object.keys(doclets);
82
83 function getUniqueNumbers() {
84 var format = require('util').format;
85
86 docletKeys.forEach(function(doclet) {
87 var newLongname;
88
89 while (true) {
90 counter++;
91 variations[doclet] = String(counter);
92
93 // is this longname + variation unique?
94 newLongname = format('%s(%s)', doclets[doclet].longname, variations[doclet]);
95 if ( !functionDoclets[newLongname] ) {
96 break;
97 }
98 }
99 });
100 }
101
102 function getUniqueNames() {
103 // start by trying to preserve existing variations
104 docletKeys.forEach(function(doclet) {
105 variations[doclet] = doclets[doclet].variation || getParamVariation(doclets[doclet]);
106 });
107
108 // if they're identical, try again, without preserving existing variations
109 if ( !hasUniqueValues(variations) ) {
110 docletKeys.forEach(function(doclet) {
111 variations[doclet] = getParamVariation(doclets[doclet]);
112 });
113
114 // if they're STILL identical, switch to numeric variations
115 if ( !hasUniqueValues(variations) ) {
116 getUniqueNumbers();
117 }
118 }
119 }
120
121 // are we already using numeric variations? if so, keep doing that
122 if (functionDoclets[doclets.newDoclet.longname + '(1)']) {
123 getUniqueNumbers();
124 }
125 else {
126 getUniqueNames();
127 }
128
129 return variations;
130}
131
132function ensureUniqueLongname(newDoclet) {
133 var doclets = {
134 oldDoclet: functionDoclets[newDoclet.longname],
135 newDoclet: newDoclet
136 };
137 var docletKeys = Object.keys(doclets);
138 var oldDocletLongname;
139 var variations = {};
140
141 if (doclets.oldDoclet) {
142 oldDocletLongname = doclets.oldDoclet.longname;
143 // if the shared longname has a variation, like MyClass#myLongname(variation),
144 // remove the variation
145 if (doclets.oldDoclet.variation || doclets.oldDoclet.variation === '') {
146 docletKeys.forEach(function(doclet) {
147 doclets[doclet].longname = doclets[doclet].longname.replace(/\([\s\S]*\)$/, '');
148 doclets[doclet].variation = null;
149 });
150 }
151
152 variations = getUniqueVariations(doclets);
153
154 // update the longnames/variations
155 docletKeys.forEach(function(doclet) {
156 doclets[doclet].longname += '(' + variations[doclet] + ')';
157 doclets[doclet].variation = variations[doclet];
158 });
159
160 // update the old doclet in the lookup table
161 functionDoclets[oldDocletLongname] = null;
162 functionDoclets[doclets.oldDoclet.longname] = doclets.oldDoclet;
163 }
164
165 // always store the new doclet in the lookup table
166 functionDoclets[doclets.newDoclet.longname] = doclets.newDoclet;
167
168 return doclets.newDoclet;
169}
170
171exports.handlers = {
172 parseBegin: function() {
173 functionDoclets = {};
174 },
175
176 newDoclet: function(e) {
177 if (e.doclet.kind === 'function') {
178 e.doclet = ensureUniqueLongname(e.doclet);
179 }
180 },
181
182 parseComplete: function() {
183 functionDoclets = null;
184 }
185};