overloadHelper.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  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. */
  37. 'use strict';
  38. // lookup table of function doclets by longname
  39. var functionDoclets;
  40. function hasUniqueValues(obj) {
  41. var isUnique = true;
  42. var seen = [];
  43. Object.keys(obj).forEach(function(key) {
  44. if (seen.indexOf(obj[key]) !== -1) {
  45. isUnique = false;
  46. }
  47. seen.push(obj[key]);
  48. });
  49. return isUnique;
  50. }
  51. function getParamNames(params) {
  52. var names = [];
  53. params.forEach(function(param) {
  54. var name = param.name || '';
  55. if (param.variable) {
  56. name = '...' + name;
  57. }
  58. if (name !== '') {
  59. names.push(name);
  60. }
  61. });
  62. return names.length ? names.join(', ') : '';
  63. }
  64. function getParamVariation(doclet) {
  65. return getParamNames(doclet.params || []);
  66. }
  67. function getUniqueVariations(doclets) {
  68. var counter = 0;
  69. var variations = {};
  70. var docletKeys = Object.keys(doclets);
  71. function getUniqueNumbers() {
  72. var format = require('util').format;
  73. docletKeys.forEach(function(doclet) {
  74. var newLongname;
  75. while (true) {
  76. counter++;
  77. variations[doclet] = String(counter);
  78. // is this longname + variation unique?
  79. newLongname = format('%s(%s)', doclets[doclet].longname, variations[doclet]);
  80. if ( !functionDoclets[newLongname] ) {
  81. break;
  82. }
  83. }
  84. });
  85. }
  86. function getUniqueNames() {
  87. // start by trying to preserve existing variations
  88. docletKeys.forEach(function(doclet) {
  89. variations[doclet] = doclets[doclet].variation || getParamVariation(doclets[doclet]);
  90. });
  91. // if they're identical, try again, without preserving existing variations
  92. if ( !hasUniqueValues(variations) ) {
  93. docletKeys.forEach(function(doclet) {
  94. variations[doclet] = getParamVariation(doclets[doclet]);
  95. });
  96. // if they're STILL identical, switch to numeric variations
  97. if ( !hasUniqueValues(variations) ) {
  98. getUniqueNumbers();
  99. }
  100. }
  101. }
  102. // are we already using numeric variations? if so, keep doing that
  103. if (functionDoclets[doclets.newDoclet.longname + '(1)']) {
  104. getUniqueNumbers();
  105. }
  106. else {
  107. getUniqueNames();
  108. }
  109. return variations;
  110. }
  111. function ensureUniqueLongname(newDoclet) {
  112. var doclets = {
  113. oldDoclet: functionDoclets[newDoclet.longname],
  114. newDoclet: newDoclet
  115. };
  116. var docletKeys = Object.keys(doclets);
  117. var oldDocletLongname;
  118. var variations = {};
  119. if (doclets.oldDoclet) {
  120. oldDocletLongname = doclets.oldDoclet.longname;
  121. // if the shared longname has a variation, like MyClass#myLongname(variation),
  122. // remove the variation
  123. if (doclets.oldDoclet.variation || doclets.oldDoclet.variation === '') {
  124. docletKeys.forEach(function(doclet) {
  125. doclets[doclet].longname = doclets[doclet].longname.replace(/\([\s\S]*\)$/, '');
  126. doclets[doclet].variation = null;
  127. });
  128. }
  129. variations = getUniqueVariations(doclets);
  130. // update the longnames/variations
  131. docletKeys.forEach(function(doclet) {
  132. doclets[doclet].longname += '(' + variations[doclet] + ')';
  133. doclets[doclet].variation = variations[doclet];
  134. });
  135. // update the old doclet in the lookup table
  136. functionDoclets[oldDocletLongname] = null;
  137. functionDoclets[doclets.oldDoclet.longname] = doclets.oldDoclet;
  138. }
  139. // always store the new doclet in the lookup table
  140. functionDoclets[doclets.newDoclet.longname] = doclets.newDoclet;
  141. return doclets.newDoclet;
  142. }
  143. exports.handlers = {
  144. parseBegin: function() {
  145. functionDoclets = {};
  146. },
  147. newDoclet: function(e) {
  148. if (e.doclet.kind === 'function') {
  149. e.doclet = ensureUniqueLongname(e.doclet);
  150. }
  151. },
  152. parseComplete: function() {
  153. functionDoclets = null;
  154. }
  155. };