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 | ;
|
40 |
|
41 | // lookup table of function doclets by longname
|
42 | var functionDoclets;
|
43 |
|
44 | function 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 |
|
58 | function 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 |
|
74 | function getParamVariation(doclet) {
|
75 | return getParamNames(doclet.params || []);
|
76 | }
|
77 |
|
78 | function 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 |
|
132 | function 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 |
|
171 | exports.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 | };
|