docletHelper.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. var template = require('./template');
  2. var helper = require('jsdoc/util/templateHelper');
  3. var markdown = require('jsdoc/util/markdown').getParser();
  4. var supportsParams = function (doclet) {
  5. return doclet.kind === 'function' || doclet.kind === 'class' || (doclet.kind === 'typedef' && !!doclet.type && !!doclet.type.names && doclet.type.names.some(function (name) {
  6. return name.toLowerCase() === 'function';
  7. }));
  8. };
  9. var getLinkText = exports.getLinkText = function(doclet){
  10. var text = doclet.longname;
  11. if (["class", "module", "namespace", "mixin", "interface", "event"].indexOf(doclet.kind) !== -1) {
  12. text = text.replace("module:", "");
  13. if ("event" === doclet.kind) {
  14. text = text.replace("event:", "");
  15. }
  16. if ("module" === doclet.kind){
  17. text = text.split('>').pop();
  18. }
  19. } else if ("external" === doclet.kind) {
  20. text = doclet.name.replace(/(^"|"$)/g, "");
  21. } else if ("tutorial" === doclet.kind || "readme" === doclet.kind || "list" === doclet.kind) {
  22. text = doclet.title || doclet.name;
  23. }
  24. return text;
  25. };
  26. exports.getAttribs = function (doclet) {
  27. if (supportsParams(doclet) || doclet.kind === 'member' || doclet.kind === 'constant') {
  28. var attribs = helper.getAttribs(doclet);
  29. return attribs.length ? '<span class="signature-attribs">' + helper.htmlsafe('<' + attribs.join(', ') + '> ') + '</span>' : '';
  30. }
  31. return '';
  32. };
  33. exports.getSignature = function (doclet) {
  34. var signature = '';
  35. if (supportsParams(doclet)) {
  36. signature += '<span class="signature-params">(';
  37. if (doclet.params && doclet.params.length) {
  38. signature += ' ';
  39. var optionalClose = [];
  40. doclet.params.forEach(function (p, i) {
  41. if (p.name && p.name.indexOf('.') === -1) {
  42. if (!p.optional && optionalClose.length){
  43. signature += optionalClose.pop();
  44. }
  45. var name = '<span class="signature-param">' + (p.variable ? '...' + p.name : p.name) + '</span>',
  46. separator = i > 0 ? (p.optional ? ' [,&nbsp;' : ', ') : (p.optional ? '[&nbsp;' : '');
  47. signature += separator + name;
  48. if (p.optional) optionalClose.push('&nbsp;]');
  49. }
  50. });
  51. signature += optionalClose.join('') + '&nbsp;';
  52. }
  53. signature += ')</span>';
  54. if (template.options.methodHeadingReturns) {
  55. var returnTypes = helper.getSignatureReturns(doclet);
  56. signature += '<span class="signature-type">' + (returnTypes.length ? ' &rarr;&nbsp;{' + returnTypes.join('|') + '}' : '') + '</span>';
  57. }
  58. } else if (doclet.kind === 'member' || doclet.kind === 'constant') {
  59. var types = helper.getSignatureTypes(doclet);
  60. signature += '<span class="signature-type">' + (types.length ? ' :' + types.join('|') : '') + '</span>';
  61. //todo: check if this is required
  62. //doclet.kind = 'member';
  63. }
  64. return signature;
  65. };
  66. exports.getExamples = function (doclet) {
  67. if (!doclet.examples || !doclet.examples.length) return [];
  68. return doclet.examples.map(function (example) {
  69. // perform parsing of the example content to extract custom inner tags
  70. // create a new example object to return as the result of the mapping
  71. var result = {
  72. caption: '',
  73. code: '',
  74. lang: 'javascript',
  75. run: false
  76. };
  77. // parse caption supplied using the default <caption></caption> syntax
  78. if (example.match(/^\s*?<caption>([\s\S]+?)<\/caption>(\s*)([\s\S]+?)$/i)) {
  79. example = RegExp.$3;
  80. result.caption = markdown(RegExp.$1);
  81. }
  82. // parse caption supplied using the {@caption <markdown>} inner tag
  83. var caption = /^\s*?\{@caption\s(.*?)}\s*?/.exec(example);
  84. if (caption && caption[1]) {
  85. example = example.replace(caption[0], "");
  86. result.caption = markdown(caption[1]); // parse markdown and set result value
  87. }
  88. // parse lang supplied using the {@lang <string>} inner tag, this should be a prism.js supported language to get syntax highlighting.
  89. var lang = /\s*?\{@lang\s(.*?)}\s*?/.exec(example);
  90. if (lang && lang[1]) {
  91. example = example.replace(lang[0], "");
  92. result.lang = lang[1];
  93. }
  94. // parse run supplied using the {@run <boolean>} inner tag, this allows the example to be executed with any console.log calls being piped into a textarea.
  95. // NOTE: if lang !== 'javascript' the {@run} inner tag is simply removed from the example code, we only support running javascript.
  96. var run = /\s*?\{@run\s(.*?)}\s*?/.exec(example);
  97. if (run && run[1]) {
  98. example = example.replace(run[0], "");
  99. // if the run tag is supplied it is always true regardless of the value so just test if the lang is javascript and use that value
  100. result.run = result.lang === 'javascript';
  101. }
  102. // the example should now contain just the code
  103. result.code = example;
  104. return result;
  105. });
  106. };
  107. var expandLongnames = function(longnames, parent){
  108. var results = [];
  109. var generated = template.kinds.pages.indexOf(parent.kind) !== -1;
  110. var memberof = generated ? parent.longname : parent.memberof;
  111. var leftovers = longnames.slice();
  112. template.find({longname: longnames}).forEach(function(doclet){
  113. var linkText = getLinkText(doclet);
  114. if (doclet.memberof === memberof){
  115. linkText = linkText.split("~").pop();
  116. }
  117. leftovers.splice(leftovers.indexOf(doclet.longname), 1);
  118. results.push({
  119. link: template.linkto(doclet.longname, linkText),
  120. summary: doclet.summary
  121. });
  122. });
  123. leftovers.forEach(function(longname){
  124. results.push({
  125. link: template.linkto(longname),
  126. summary: ''
  127. });
  128. });
  129. return results;
  130. };
  131. exports.getFires = function(doclet){
  132. if (!doclet.fires) return [];
  133. return expandLongnames(doclet.fires, doclet);
  134. };
  135. exports.getRequires = function(doclet){
  136. if (!doclet.requires) return [];
  137. return expandLongnames(doclet.requires, doclet);
  138. };
  139. exports.getSummary = function (doclet) {
  140. if (!doclet.summary) return '';
  141. return markdown(doclet.summary);
  142. };
  143. exports.getParamsOrProps = function (doclet, type) {
  144. if (!doclet[type] || !doclet[type].length) return [];
  145. var sorted = {};
  146. sorted[type] = [];
  147. doclet[type].forEach(function(paramOrProp){
  148. if (!paramOrProp) { return; }
  149. var parts = paramOrProp.name.split("."), last = parts.length - 1, base = sorted, parentName = [];
  150. parts.forEach(function(part, i){
  151. var index;
  152. if (i === last){
  153. paramOrProp.name = paramOrProp.name.replace(parentName.join('.'), '').replace(/^\./, '');
  154. base[type] = base[type] || [];
  155. base[type].push(paramOrProp);
  156. } else if ((index = base[type].findIndex(function(p){ return p.name === part; })) !== -1) {
  157. base = base[type][index];
  158. parentName.push(part);
  159. }
  160. });
  161. });
  162. return sorted[type].filter(function (paramOrProp) {
  163. return !!paramOrProp;
  164. });
  165. };
  166. var checkParamsOrProps = exports.checkParamsOrProps = function (parent, type) {
  167. if (!parent || !parent[type] || !parent[type].length) return;
  168. /* determine if we need extra columns, "attributes" and "default" */
  169. parent[type + 'HasAttributes'] = false;
  170. parent[type + 'HasDefaults'] = false;
  171. parent[type + 'HasNames'] = false;
  172. parent[type].forEach(function (paramOrProp) {
  173. if (!paramOrProp) {
  174. return;
  175. }
  176. if (paramOrProp.optional || paramOrProp.nullable || paramOrProp.variable) {
  177. parent[type + 'HasAttributes'] = true;
  178. }
  179. if (paramOrProp.name) {
  180. parent[type + 'HasNames'] = true;
  181. }
  182. if (typeof paramOrProp.defaultvalue !== 'undefined') {
  183. parent[type + 'HasDefaults'] = true;
  184. }
  185. if (paramOrProp[type]) {
  186. checkParamsOrProps(paramOrProp, type);
  187. }
  188. });
  189. };
  190. exports.getPageTitle = function(doclet, sanitized){
  191. var parts = [];
  192. if (doclet.attribs){
  193. parts.push(doclet.attribs);
  194. }
  195. if (template.kinds.pages.indexOf(doclet.kind) !== -1 && template.kinds.custom.indexOf(doclet.kind) === -1 && doclet.ancestors && doclet.ancestors.length){
  196. parts.push('<span class="ancestors">'+doclet.ancestors.join('')+'</span>');
  197. }
  198. if (doclet.title){
  199. parts.push('<span class="title">' + doclet.title + '</span>');
  200. } else if (doclet.name) {
  201. var name = doclet.name;
  202. if (doclet.exported){
  203. name = name.replace('module:', '<span class="name-signature">(<span class="name-require">require</span>(<span class="name-string">"') + '"</span>))</span>';
  204. }
  205. parts.push('<span class="name">' + name + '</span>');
  206. }
  207. if (template.kinds.pages.indexOf(doclet.kind) === -1 && doclet.signature){
  208. parts.push(doclet.signature);
  209. }
  210. if (doclet.variation){
  211. parts.push('<sup class="variation">' + doclet.variation + '</sup>');
  212. }
  213. var result = parts.join('');
  214. return sanitized ? template.sanitize(result) : result;
  215. };
  216. exports.getListTitle = function(doclet, sanitized){
  217. var parts = [], linkClose = false, url = doclet.kind === 'tutorial' ? helper.tutorialToUrl(doclet.longname) : helper.longnameToUrl[doclet.longname];
  218. // only generate links to kinds that have a page generated, others show content inline so there's no need
  219. if (url){
  220. parts.push('<a href="' + url + '">');
  221. linkClose = true;
  222. }
  223. if (doclet.kind === 'class'){
  224. parts.push('<span class="signature-new">new&nbsp;</span>');
  225. }
  226. if (doclet.ancestors && doclet.ancestors.length){
  227. parts.push('<span class="ancestors">'+template.sanitize(doclet.ancestors.join(''))+'</span>');
  228. }
  229. if (doclet.attribs){
  230. parts.push(doclet.attribs);
  231. }
  232. if (doclet.title){
  233. parts.push('<span class="title">' + doclet.title + '</span>');
  234. } else if (doclet.name) {
  235. var name = doclet.name;
  236. if (doclet.exported){
  237. name = name.replace('module:', '<span class="name-signature">(<span class="name-require">require</span>(<span class="name-string">"') + '"</span>))</span>';
  238. }
  239. parts.push('<span class="name">' + name + '</span>');
  240. }
  241. if (doclet.signature){
  242. parts.push(doclet.signature);
  243. }
  244. if (doclet.variation){
  245. parts.push('<sup class="variation">' + doclet.variation + '</sup>');
  246. }
  247. if (linkClose){
  248. parts.push('</a>');
  249. }
  250. var result = parts.join('');
  251. return sanitized ? template.sanitize(result) : result;
  252. };
  253. exports.getSymbolTitle = function(doclet, sanitized){
  254. var parts = [], linkClose = false, url = doclet.kind === 'tutorial' ? helper.tutorialToUrl(doclet.longname) : helper.longnameToUrl[doclet.longname];
  255. // only generate links to kinds that have a page generated, others show content inline so there's no need
  256. if (template.kinds.pages.indexOf(doclet.kind) !== -1 && url){
  257. parts.push('<a href="' + url + '">');
  258. linkClose = true;
  259. }
  260. if (doclet.kind === 'class'){
  261. parts.push('<span class="signature-new">new&nbsp;</span>');
  262. }
  263. if (doclet.attribs){
  264. parts.push(doclet.attribs);
  265. }
  266. if (doclet.title){
  267. parts.push('<span class="title">' + doclet.title + '</span>');
  268. } else if (doclet.name) {
  269. var name = doclet.name;
  270. if (doclet.exported){
  271. name = name.replace('module:', '<span class="name-signature">(<span class="name-require">require</span>(<span class="name-string">"') + '"</span>))</span>';
  272. }
  273. parts.push('<span class="name">' + name + '</span>');
  274. }
  275. if (doclet.signature){
  276. parts.push(doclet.signature);
  277. }
  278. if (doclet.variation){
  279. parts.push('<sup class="variation">' + doclet.variation + '</sup>');
  280. }
  281. if (linkClose){
  282. parts.push('</a>');
  283. }
  284. var result = parts.join('');
  285. return sanitized ? template.sanitize(result) : result;
  286. };
  287. exports.getPrimaryTitle = function(doclet, sanitized){
  288. var parts = [];
  289. if (doclet.kind === 'class'){
  290. parts.push('<span class="signature-new">new&nbsp;</span>');
  291. }
  292. if (doclet.attribs){
  293. parts.push(doclet.attribs);
  294. }
  295. if (doclet.title){
  296. parts.push('<span class="title">' + doclet.title + '</span>');
  297. } else if (doclet.name) {
  298. var name = doclet.name;
  299. if (doclet.exported){
  300. name = name.replace('module:', '<span class="name-signature">(<span class="name-require">require</span>(<span class="name-string">"') + '"</span>))</span>';
  301. }
  302. parts.push('<span class="name">' + doclet.name + '</span>');
  303. }
  304. if (doclet.signature){
  305. parts.push(doclet.signature);
  306. }
  307. if (doclet.variation){
  308. parts.push('<sup class="variation">' + doclet.variation + '</sup>');
  309. }
  310. var result = parts.join('');
  311. return sanitized ? template.sanitize(result) : result;
  312. };
  313. exports.getSymbols = function(doclet){
  314. var symbols = {};
  315. if (doclet.longname == helper.globalName){
  316. template.kinds.global.forEach(function(kind){
  317. symbols[kind] = template.find({kind: kind, memberof: { isUndefined: true }});
  318. });
  319. } else {
  320. template.kinds.symbols.forEach(function(kind){
  321. symbols[kind] = template.find({kind: kind, memberof: doclet.longname});
  322. });
  323. }
  324. return symbols;
  325. };
  326. exports.getShowAccessFilter = function(doclet){
  327. var result = typeof doclet.showAccessFilter != 'boolean' ? template.options.showAccessFilter : doclet.showAccessFilter;
  328. if (result){
  329. // if we can show the filter check if we should actually show it
  330. doclet.has = {
  331. inherited: template.find({kind: template.kinds.symbols, memberof: doclet.longname, inherited: true}).length > 0,
  332. public: template.find({kind: template.kinds.symbols, memberof: doclet.longname, access: "public"}).length > 0,
  333. protected: template.find({kind: template.kinds.symbols, memberof: doclet.longname, access: "protected"}).length > 0,
  334. private: template.find({kind: template.kinds.symbols, memberof: doclet.longname, access: "private"}).length > 0
  335. };
  336. var count = (doclet.has.inherited ? 1 : 0) + (doclet.has.public ? 1 : 0) + (doclet.has.protected ? 1 : 0) + (doclet.has.private ? 1 : 0);
  337. // only show the filter if there are two or more accessors available
  338. result = count > 1;
  339. }
  340. return result;
  341. };
  342. exports.isInherited = function(doclet){
  343. return !!doclet.inherited;
  344. };
  345. exports.hasDetails = function (doclet) {
  346. return !!(doclet.version
  347. || doclet.since
  348. || (doclet.inherited && doclet.inherits)
  349. || doclet.since
  350. || (doclet.implementations && doclet.implementations.length)
  351. || (doclet.implements && doclet.implements.length)
  352. || (doclet.mixes && doclet.mixes.length)
  353. || doclet.deprecated
  354. || (doclet.author && doclet.author.length)
  355. || doclet.copyright
  356. || doclet.license
  357. || doclet.defaultvalue
  358. || doclet.hasSource
  359. || (doclet.tutorials && doclet.tutorials.length)
  360. || (doclet.see && doclet.see.length)
  361. || (doclet.todo && doclet.todo.length))
  362. };