overloadHelper.js 5.5 KB

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