Source: tag/dictionary/definitions.js

  1. /**
  2. Define tags that are known in JSDoc.
  3. @module jsdoc/tag/dictionary/definitions
  4. @author Michael Mathews <micmath@gmail.com>
  5. @license Apache License 2.0 - See file 'LICENSE.md' in this project.
  6. */
  7. 'use strict';
  8. var _ = require('underscore');
  9. var jsdoc = {
  10. env: require('jsdoc/env'),
  11. name: require('jsdoc/name'),
  12. src: {
  13. astnode: require('jsdoc/src/astnode')
  14. },
  15. tag: {
  16. type: require('jsdoc/tag/type')
  17. },
  18. util: {
  19. doop: require('jsdoc/util/doop'),
  20. logger: require('jsdoc/util/logger')
  21. }
  22. };
  23. var path = require('jsdoc/path');
  24. var Syntax = require('jsdoc/src/syntax').Syntax;
  25. var hasOwnProp = Object.prototype.hasOwnProperty;
  26. var DEFINITIONS = {
  27. closure: 'closureTags',
  28. jsdoc: 'jsdocTags'
  29. };
  30. var MODULE_NAMESPACE = 'module:';
  31. // Clone a tag definition, excluding synonyms.
  32. function cloneTagDef(tagDef, extras) {
  33. var newTagDef = jsdoc.util.doop(tagDef);
  34. delete newTagDef.synonyms;
  35. return (extras ? _.extend(newTagDef, extras) : newTagDef);
  36. }
  37. function getSourcePaths() {
  38. var sourcePaths = jsdoc.env.sourceFiles.slice(0) || [];
  39. if (jsdoc.env.opts._) {
  40. jsdoc.env.opts._.forEach(function(sourcePath) {
  41. var resolved = path.resolve(jsdoc.env.pwd, sourcePath);
  42. if (sourcePaths.indexOf(resolved) === -1) {
  43. sourcePaths.push(resolved);
  44. }
  45. });
  46. }
  47. return sourcePaths;
  48. }
  49. function filepathMinusPrefix(filepath) {
  50. var sourcePaths = getSourcePaths();
  51. var commonPrefix = path.commonPrefix(sourcePaths);
  52. var result = '';
  53. if (filepath) {
  54. filepath = path.normalize(filepath);
  55. // always use forward slashes in the result
  56. result = (filepath + path.sep).replace(commonPrefix, '')
  57. .replace(/\\/g, '/');
  58. }
  59. if (result.length > 0 && result[result.length - 1] !== '/') {
  60. result += '/';
  61. }
  62. return result;
  63. }
  64. /** @private */
  65. function setDocletKindToTitle(doclet, tag) {
  66. doclet.addTag( 'kind', tag.title );
  67. }
  68. function setDocletScopeToTitle(doclet, tag) {
  69. try {
  70. doclet.setScope(tag.title);
  71. }
  72. catch(e) {
  73. jsdoc.util.logger.error(e.message);
  74. }
  75. }
  76. function setDocletNameToValue(doclet, tag) {
  77. if (tag.value && tag.value.description) { // as in a long tag
  78. doclet.addTag('name', tag.value.description);
  79. }
  80. else if (tag.text) { // or a short tag
  81. doclet.addTag('name', tag.text);
  82. }
  83. }
  84. function setDocletNameToValueName(doclet, tag) {
  85. if (tag.value && tag.value.name) {
  86. doclet.addTag('name', tag.value.name);
  87. }
  88. }
  89. function setDocletDescriptionToValue(doclet, tag) {
  90. if (tag.value) {
  91. doclet.addTag('description', tag.value);
  92. }
  93. }
  94. function setDocletTypeToValueType(doclet, tag) {
  95. if (tag.value && tag.value.type) {
  96. // Add the type names and other type properties (such as `optional`).
  97. // Don't overwrite existing properties.
  98. Object.keys(tag.value).forEach(function(prop) {
  99. if ( !hasOwnProp.call(doclet, prop) ) {
  100. doclet[prop] = tag.value[prop];
  101. }
  102. });
  103. }
  104. }
  105. function setNameToFile(doclet, tag) {
  106. var name;
  107. if (doclet.meta.filename) {
  108. name = filepathMinusPrefix(doclet.meta.path) + doclet.meta.filename;
  109. doclet.addTag('name', name);
  110. }
  111. }
  112. function setDocletMemberof(doclet, tag) {
  113. if (tag.value && tag.value !== '<global>') {
  114. doclet.setMemberof(tag.value);
  115. }
  116. }
  117. function applyNamespace(docletOrNs, tag) {
  118. if (typeof docletOrNs === 'string') { // ns
  119. tag.value = jsdoc.name.applyNamespace(tag.value, docletOrNs);
  120. }
  121. else { // doclet
  122. if (!docletOrNs.name) {
  123. return; // error?
  124. }
  125. docletOrNs.longname = jsdoc.name.applyNamespace(docletOrNs.name, tag.title);
  126. }
  127. }
  128. function setDocletNameToFilename(doclet, tag) {
  129. var name = '';
  130. if (doclet.meta.path) {
  131. name = filepathMinusPrefix(doclet.meta.path);
  132. }
  133. name += doclet.meta.filename.replace(/\.js$/i, '');
  134. doclet.name = name;
  135. }
  136. function parseTypeText(text) {
  137. var tagType = jsdoc.tag.type.parse(text, false, true);
  138. return tagType.typeExpression || text;
  139. }
  140. function parseBorrows(doclet, tag) {
  141. var m = /^([\s\S]+?)(?:\s+as\s+([\s\S]+))?$/.exec(tag.text);
  142. if (m) {
  143. if (m[1] && m[2]) {
  144. return { target: m[1], source: m[2] };
  145. }
  146. else if (m[1]) {
  147. return { target: m[1] };
  148. }
  149. } else {
  150. return {};
  151. }
  152. }
  153. function stripModuleNamespace(name) {
  154. return name.replace(/^module\:/, '');
  155. }
  156. function firstWordOf(string) {
  157. var m = /^(\S+)/.exec(string);
  158. if (m) { return m[1]; }
  159. else { return ''; }
  160. }
  161. // Core JSDoc tags that are shared with other tag dictionaries.
  162. var baseTags = exports.baseTags = {
  163. abstract: {
  164. mustNotHaveValue: true,
  165. onTagged: function(doclet, tag) {
  166. // we call this `virtual` because `abstract` is a reserved word
  167. doclet.virtual = true;
  168. },
  169. synonyms: ['virtual']
  170. },
  171. access: {
  172. mustHaveValue: true,
  173. onTagged: function(doclet, tag) {
  174. // only valid values are private, protected and public
  175. if ( /^(private|protected|public)$/i.test(tag.value) ) {
  176. doclet.access = tag.value.toLowerCase();
  177. }
  178. else {
  179. delete doclet.access;
  180. }
  181. }
  182. },
  183. alias: {
  184. mustHaveValue: true,
  185. onTagged: function(doclet, tag) {
  186. doclet.alias = tag.value;
  187. }
  188. },
  189. // Special separator tag indicating that multiple doclets should be generated for the same
  190. // comment. Used internally (and by some JSDoc users, although it's not officially supported).
  191. // In the following example, the parser will replace `//**` with an `@also` tag:
  192. // /**
  193. // * Foo.
  194. // *//**
  195. // * Foo with a param.
  196. // * @param {string} bar
  197. // */
  198. // function foo(bar) {}
  199. also: {
  200. onTagged: function(doclet, tag) {
  201. // let the parser handle it; we define the tag here to avoid "not a known tag" errors
  202. }
  203. },
  204. augments: {
  205. mustHaveValue: true,
  206. // Allow augments value to be specified as a normal type, e.g. {Type}
  207. onTagText: parseTypeText,
  208. onTagged: function(doclet, tag) {
  209. doclet.augment( firstWordOf(tag.value) );
  210. },
  211. synonyms: ['extends']
  212. },
  213. author: {
  214. mustHaveValue: true,
  215. onTagged: function(doclet, tag) {
  216. doclet.author = doclet.author || [];
  217. doclet.author.push(tag.value);
  218. }
  219. },
  220. // this symbol has a member that should use the same docs as another symbol
  221. borrows: {
  222. mustHaveValue: true,
  223. onTagged: function(doclet, tag) {
  224. var borrows = parseBorrows(doclet, tag);
  225. doclet.borrow(borrows.target, borrows.source);
  226. }
  227. },
  228. class: {
  229. onTagged: function(doclet, tag) {
  230. doclet.addTag('kind', 'class');
  231. // handle special case where both @class and @constructor tags exist in same doclet
  232. if (tag.originalTitle === 'class') {
  233. // multiple words after @class?
  234. var looksLikeDesc = (tag.value || '').match(/\S+\s+\S+/);
  235. if ((looksLikeDesc || /@construct(s|or)\b/i.test(doclet.comment)) &&
  236. !/@classdesc\b/i.test(doclet.comment)) {
  237. // treat the @class tag as a @classdesc tag instead
  238. doclet.classdesc = tag.value;
  239. return;
  240. }
  241. }
  242. setDocletNameToValue(doclet, tag);
  243. },
  244. synonyms: ['constructor']
  245. },
  246. classdesc: {
  247. onTagged: function(doclet, tag) {
  248. doclet.classdesc = tag.value;
  249. }
  250. },
  251. constant: {
  252. canHaveType: true,
  253. canHaveName: true,
  254. onTagged: function(doclet, tag) {
  255. setDocletKindToTitle(doclet, tag);
  256. setDocletNameToValueName(doclet, tag);
  257. setDocletTypeToValueType(doclet, tag);
  258. },
  259. synonyms: ['const']
  260. },
  261. constructs: {
  262. onTagged: function(doclet, tag) {
  263. var ownerClassName;
  264. if (!tag.value) {
  265. // this can be resolved later in the handlers
  266. ownerClassName = '{@thisClass}';
  267. }
  268. else {
  269. ownerClassName = firstWordOf(tag.value);
  270. }
  271. doclet.addTag('alias', ownerClassName);
  272. doclet.addTag('kind', 'class');
  273. }
  274. },
  275. copyright: {
  276. mustHaveValue: true,
  277. onTagged: function(doclet, tag) {
  278. doclet.copyright = tag.value;
  279. }
  280. },
  281. default: {
  282. onTagged: function(doclet, tag) {
  283. var nodeToValue = jsdoc.src.astnode.nodeToValue;
  284. if (tag.value) {
  285. doclet.defaultvalue = tag.value;
  286. }
  287. else if (doclet.meta && doclet.meta.code &&
  288. typeof doclet.meta.code.value !== 'undefined') {
  289. switch (doclet.meta.code.type) {
  290. case Syntax.ArrayExpression:
  291. doclet.defaultvalue = nodeToValue(doclet.meta.code.node);
  292. doclet.defaultvaluetype = 'array';
  293. break;
  294. case Syntax.Literal:
  295. doclet.defaultvalue = doclet.meta.code.value;
  296. break;
  297. case Syntax.ObjectExpression:
  298. doclet.defaultvalue = nodeToValue(doclet.meta.code.node);
  299. doclet.defaultvaluetype = 'object';
  300. break;
  301. default:
  302. // do nothing
  303. break;
  304. }
  305. }
  306. },
  307. synonyms: ['defaultvalue']
  308. },
  309. deprecated: {
  310. // value is optional
  311. onTagged: function(doclet, tag) {
  312. doclet.deprecated = tag.value || true;
  313. }
  314. },
  315. description: {
  316. mustHaveValue: true,
  317. synonyms: ['desc']
  318. },
  319. enum: {
  320. canHaveType: true,
  321. onTagged: function(doclet, tag) {
  322. doclet.kind = 'member';
  323. doclet.isEnum = true;
  324. setDocletTypeToValueType(doclet, tag);
  325. }
  326. },
  327. event: {
  328. isNamespace: true,
  329. onTagged: function(doclet, tag) {
  330. setDocletKindToTitle(doclet, tag);
  331. setDocletNameToValue(doclet, tag);
  332. }
  333. },
  334. example: {
  335. keepsWhitespace: true,
  336. removesIndent: true,
  337. mustHaveValue: true,
  338. onTagged: function(doclet, tag) {
  339. doclet.examples = doclet.examples || [];
  340. doclet.examples.push(tag.value);
  341. }
  342. },
  343. exports: {
  344. mustHaveValue: true,
  345. onTagged: function(doclet, tag) {
  346. var modName = firstWordOf(tag.value);
  347. // in case the user wrote something like `/** @exports module:foo */`:
  348. doclet.addTag( 'alias', stripModuleNamespace(modName) );
  349. doclet.addTag('kind', 'module');
  350. }
  351. },
  352. external: {
  353. canHaveType: true,
  354. isNamespace: true,
  355. onTagged: function(doclet, tag) {
  356. setDocletKindToTitle(doclet, tag);
  357. if (tag.value && tag.value.type) {
  358. setDocletTypeToValueType(doclet, tag);
  359. doclet.addTag('name', doclet.type.names[0]);
  360. }
  361. else {
  362. setDocletNameToValue(doclet, tag);
  363. }
  364. },
  365. synonyms: ['host']
  366. },
  367. file: {
  368. onTagged: function(doclet, tag) {
  369. setNameToFile(doclet, tag);
  370. setDocletKindToTitle(doclet, tag);
  371. setDocletDescriptionToValue(doclet, tag);
  372. doclet.preserveName = true;
  373. },
  374. synonyms: ['fileoverview', 'overview']
  375. },
  376. fires: {
  377. mustHaveValue: true,
  378. onTagged: function(doclet, tag) {
  379. doclet.fires = doclet.fires || [];
  380. applyNamespace('event', tag);
  381. doclet.fires.push(tag.value);
  382. },
  383. synonyms: ['emits']
  384. },
  385. function: {
  386. onTagged: function(doclet, tag) {
  387. setDocletKindToTitle(doclet, tag);
  388. setDocletNameToValue(doclet, tag);
  389. },
  390. synonyms: ['func', 'method']
  391. },
  392. global: {
  393. mustNotHaveValue: true,
  394. onTagged: function(doclet, tag) {
  395. doclet.scope = jsdoc.name.SCOPE.NAMES.GLOBAL;
  396. delete doclet.memberof;
  397. }
  398. },
  399. ignore: {
  400. mustNotHaveValue: true,
  401. onTagged: function(doclet, tag) {
  402. doclet.ignore = true;
  403. }
  404. },
  405. implements: {
  406. mustHaveValue: true,
  407. onTagText: parseTypeText,
  408. onTagged: function(doclet, tag) {
  409. doclet.implements = doclet.implements || [];
  410. doclet.implements.push(tag.value);
  411. }
  412. },
  413. inheritdoc: {
  414. mustNotHaveValue: true,
  415. onTagged: function(doclet, tag) {
  416. // use an empty string so JSDoc can support `@inheritdoc Foo#bar` in the future
  417. doclet.inheritdoc = '';
  418. }
  419. },
  420. inner: {
  421. onTagged: function(doclet, tag) {
  422. setDocletScopeToTitle(doclet, tag);
  423. }
  424. },
  425. instance: {
  426. onTagged: function(doclet, tag) {
  427. setDocletScopeToTitle(doclet, tag);
  428. }
  429. },
  430. interface: {
  431. canHaveName: true,
  432. onTagged: function(doclet, tag) {
  433. doclet.addTag('kind', 'interface');
  434. if (tag.value) {
  435. setDocletNameToValueName(doclet, tag);
  436. }
  437. }
  438. },
  439. kind: {
  440. mustHaveValue: true
  441. },
  442. lends: {
  443. onTagged: function(doclet, tag) {
  444. doclet.alias = tag.value || jsdoc.name.LONGNAMES.GLOBAL;
  445. doclet.addTag('undocumented');
  446. }
  447. },
  448. license: {
  449. mustHaveValue: true,
  450. onTagged: function(doclet, tag) {
  451. doclet.license = tag.value;
  452. }
  453. },
  454. listens: {
  455. mustHaveValue: true,
  456. onTagged: function (doclet, tag) {
  457. doclet.listens = doclet.listens || [];
  458. applyNamespace('event', tag);
  459. doclet.listens.push(tag.value);
  460. }
  461. },
  462. member: {
  463. canHaveType: true,
  464. canHaveName: true,
  465. onTagged: function(doclet, tag) {
  466. setDocletKindToTitle(doclet, tag);
  467. setDocletNameToValueName(doclet, tag);
  468. setDocletTypeToValueType(doclet, tag);
  469. },
  470. synonyms: ['var']
  471. },
  472. memberof: {
  473. mustHaveValue: true,
  474. onTagged: function(doclet, tag) {
  475. if (tag.originalTitle === 'memberof!') {
  476. doclet.forceMemberof = true;
  477. if (tag.value === jsdoc.name.LONGNAMES.GLOBAL) {
  478. doclet.addTag('global');
  479. delete doclet.memberof;
  480. }
  481. }
  482. setDocletMemberof(doclet, tag);
  483. },
  484. synonyms: ['memberof!']
  485. },
  486. // this symbol mixes in all of the specified object's members
  487. mixes: {
  488. mustHaveValue: true,
  489. onTagged: function(doclet, tag) {
  490. var source = firstWordOf(tag.value);
  491. doclet.mix(source);
  492. }
  493. },
  494. mixin: {
  495. onTagged: function(doclet, tag) {
  496. setDocletKindToTitle(doclet, tag);
  497. setDocletNameToValue(doclet, tag);
  498. }
  499. },
  500. module: {
  501. canHaveType: true,
  502. isNamespace: true,
  503. onTagged: function(doclet, tag) {
  504. setDocletKindToTitle(doclet, tag);
  505. setDocletNameToValue(doclet, tag);
  506. if (!doclet.name) {
  507. setDocletNameToFilename(doclet, tag);
  508. }
  509. // in case the user wrote something like `/** @module module:foo */`:
  510. doclet.name = stripModuleNamespace(doclet.name);
  511. setDocletTypeToValueType(doclet, tag);
  512. }
  513. },
  514. name: {
  515. mustHaveValue: true
  516. },
  517. namespace: {
  518. canHaveType: true,
  519. onTagged: function(doclet, tag) {
  520. setDocletKindToTitle(doclet, tag);
  521. setDocletNameToValue(doclet, tag);
  522. setDocletTypeToValueType(doclet, tag);
  523. }
  524. },
  525. param: {
  526. canHaveType: true,
  527. canHaveName: true,
  528. onTagged: function(doclet, tag) {
  529. doclet.params = doclet.params || [];
  530. doclet.params.push(tag.value || {});
  531. },
  532. synonyms: ['arg', 'argument']
  533. },
  534. private: {
  535. mustNotHaveValue: true,
  536. onTagged: function(doclet, tag) {
  537. doclet.access = 'private';
  538. }
  539. },
  540. property: {
  541. mustHaveValue: true,
  542. canHaveType: true,
  543. canHaveName: true,
  544. onTagged: function(doclet, tag) {
  545. doclet.properties = doclet.properties || [];
  546. doclet.properties.push(tag.value);
  547. },
  548. synonyms: ['prop']
  549. },
  550. protected: {
  551. mustNotHaveValue: true,
  552. onTagged: function(doclet, tag) {
  553. doclet.access = 'protected';
  554. }
  555. },
  556. public: {
  557. mustNotHaveValue: true,
  558. onTagged: function(doclet, tag) {
  559. doclet.access = 'public';
  560. }
  561. },
  562. readonly: {
  563. mustNotHaveValue: true,
  564. onTagged: function(doclet, tag) {
  565. doclet.readonly = true;
  566. }
  567. },
  568. requires: {
  569. mustHaveValue: true,
  570. onTagged: function(doclet, tag) {
  571. var requiresName;
  572. // inline link tags are passed through as-is so that `@requires {@link foo}` works
  573. if ( require('jsdoc/tag/inline').isInlineTag(tag.value, 'link\\S*') ) {
  574. requiresName = tag.value;
  575. }
  576. // otherwise, assume it's a module
  577. else {
  578. requiresName = firstWordOf(tag.value);
  579. if (requiresName.indexOf(MODULE_NAMESPACE) !== 0) {
  580. requiresName = MODULE_NAMESPACE + requiresName;
  581. }
  582. }
  583. doclet.requires = doclet.requires || [];
  584. doclet.requires.push(requiresName);
  585. }
  586. },
  587. returns: {
  588. mustHaveValue: true,
  589. canHaveType: true,
  590. onTagged: function(doclet, tag) {
  591. doclet.returns = doclet.returns || [];
  592. doclet.returns.push(tag.value);
  593. },
  594. synonyms: ['return']
  595. },
  596. see: {
  597. mustHaveValue: true,
  598. onTagged: function(doclet, tag) {
  599. doclet.see = doclet.see || [];
  600. doclet.see.push(tag.value);
  601. }
  602. },
  603. since: {
  604. mustHaveValue: true,
  605. onTagged: function(doclet, tag) {
  606. doclet.since = tag.value;
  607. }
  608. },
  609. static: {
  610. onTagged: function(doclet, tag) {
  611. setDocletScopeToTitle(doclet, tag);
  612. }
  613. },
  614. summary: {
  615. mustHaveValue: true,
  616. onTagged: function(doclet, tag) {
  617. doclet.summary = tag.value;
  618. }
  619. },
  620. 'this': {
  621. mustHaveValue: true,
  622. onTagged: function(doclet, tag) {
  623. doclet.this = firstWordOf(tag.value);
  624. }
  625. },
  626. todo: {
  627. mustHaveValue: true,
  628. onTagged: function(doclet, tag) {
  629. doclet.todo = doclet.todo || [];
  630. doclet.todo.push(tag.value);
  631. }
  632. },
  633. throws: {
  634. mustHaveValue: true,
  635. canHaveType: true,
  636. onTagged: function(doclet, tag) {
  637. doclet.exceptions = doclet.exceptions || [];
  638. doclet.exceptions.push(tag.value);
  639. },
  640. synonyms: ['exception']
  641. },
  642. tutorial: {
  643. mustHaveValue: true,
  644. onTagged: function(doclet, tag) {
  645. doclet.tutorials = doclet.tutorials || [];
  646. doclet.tutorials.push(tag.value);
  647. }
  648. },
  649. type: {
  650. mustHaveValue: true,
  651. mustNotHaveDescription: true,
  652. canHaveType: true,
  653. onTagText: function(text) {
  654. var closeIdx;
  655. var openIdx;
  656. var OPEN_BRACE = '{';
  657. var CLOSE_BRACE = '}';
  658. // remove line breaks
  659. text = text.replace(/[\f\n\r]/g, '');
  660. // Text must be a type expression; for backwards compatibility, we add braces if they're
  661. // missing. But do NOT add braces to things like `@type {string} some pointless text`.
  662. openIdx = text.indexOf(OPEN_BRACE);
  663. closeIdx = text.indexOf(CLOSE_BRACE);
  664. // a type expression is at least one character long
  665. if ( openIdx !== 0 || closeIdx <= openIdx + 1) {
  666. text = OPEN_BRACE + text + CLOSE_BRACE;
  667. }
  668. return text;
  669. },
  670. onTagged: function(doclet, tag) {
  671. if (tag.value && tag.value.type) {
  672. setDocletTypeToValueType(doclet, tag);
  673. // for backwards compatibility, we allow @type for functions to imply return type
  674. if (doclet.kind === 'function') {
  675. doclet.addTag('returns', tag.text);
  676. }
  677. }
  678. }
  679. },
  680. typedef: {
  681. canHaveType: true,
  682. canHaveName: true,
  683. onTagged: function(doclet, tag) {
  684. setDocletKindToTitle(doclet, tag);
  685. if (tag.value) {
  686. setDocletNameToValueName(doclet, tag);
  687. // callbacks are always type {function}
  688. if (tag.originalTitle === 'callback') {
  689. doclet.type = {
  690. names: [
  691. 'function'
  692. ]
  693. };
  694. }
  695. else {
  696. setDocletTypeToValueType(doclet, tag);
  697. }
  698. }
  699. },
  700. synonyms: ['callback']
  701. },
  702. undocumented: {
  703. mustNotHaveValue: true,
  704. onTagged: function(doclet, tag) {
  705. doclet.undocumented = true;
  706. doclet.comment = '';
  707. }
  708. },
  709. variation: {
  710. mustHaveValue: true,
  711. onTagged: function(doclet, tag) {
  712. var value = tag.value;
  713. if ( /^\((.+)\)$/.test(value) ) {
  714. value = RegExp.$1;
  715. }
  716. doclet.variation = value;
  717. }
  718. },
  719. version: {
  720. mustHaveValue: true,
  721. onTagged: function(doclet, tag) {
  722. doclet.version = tag.value;
  723. }
  724. }
  725. };
  726. // Tag dictionary for JSDoc.
  727. var jsdocTags = exports.jsdocTags = baseTags;
  728. // Tag dictionary for Google Closure Compiler.
  729. var closureTags = exports.closureTags = {
  730. const: cloneTagDef(baseTags.constant),
  731. constructor: cloneTagDef(baseTags.class),
  732. deprecated: cloneTagDef(baseTags.deprecated),
  733. enum: cloneTagDef(baseTags.enum),
  734. extends: cloneTagDef(baseTags.augments),
  735. final: cloneTagDef(baseTags.readonly),
  736. implements: cloneTagDef(baseTags.implements),
  737. inheritdoc: cloneTagDef(baseTags.inheritdoc),
  738. interface: cloneTagDef(baseTags.interface, {
  739. canHaveName: false,
  740. mustNotHaveValue: true
  741. }),
  742. lends: cloneTagDef(baseTags.lends),
  743. license: cloneTagDef(baseTags.license),
  744. // Closure Compiler only
  745. override: {
  746. mustNotHaveValue: true,
  747. onTagged: function(doclet, tag) {
  748. doclet.override = true;
  749. }
  750. },
  751. param: cloneTagDef(baseTags.param),
  752. private: {
  753. canHaveType: true,
  754. onTagged: function(doclet, tag) {
  755. doclet.access = 'private';
  756. if (tag.value && tag.value.type) {
  757. setDocletTypeToValueType(doclet, tag);
  758. }
  759. }
  760. },
  761. protected: {
  762. canHaveType: true,
  763. onTagged: function(doclet, tag) {
  764. doclet.access = 'protected';
  765. if (tag.value && tag.value.type) {
  766. setDocletTypeToValueType(doclet, tag);
  767. }
  768. }
  769. },
  770. return: cloneTagDef(baseTags.returns),
  771. 'this': cloneTagDef(baseTags.this),
  772. throws: cloneTagDef(baseTags.throws),
  773. type: cloneTagDef(baseTags.type, {
  774. mustNotHaveDescription: false
  775. }),
  776. typedef: cloneTagDef(baseTags.typedef)
  777. };
  778. function addTagDefinitions(dictionary, tagDefs) {
  779. Object.keys(tagDefs).forEach(function(tagName) {
  780. var tagDef;
  781. tagDef = tagDefs[tagName];
  782. dictionary.defineTag(tagName, tagDef);
  783. if (tagDef.synonyms) {
  784. tagDef.synonyms.forEach(function(synonym) {
  785. dictionary.defineSynonym(tagName, synonym);
  786. });
  787. }
  788. });
  789. }
  790. /**
  791. * Populate the given dictionary with the appropriate JSDoc tag definitions.
  792. *
  793. * If the `tagDefinitions` parameter is omitted, JSDoc uses its configuration settings to decide
  794. * which tags to add to the dictionary.
  795. *
  796. * If the `tagDefinitions` parameter is included, JSDoc adds only the tag definitions from the
  797. * `tagDefinitions` object. The configuration settings are ignored.
  798. *
  799. * @param {module:jsdoc/tag/dictionary} dictionary
  800. * @param {Object} [tagDefinitions] - A dictionary whose values define the rules for a JSDoc tag.
  801. */
  802. exports.defineTags = function(dictionary, tagDefinitions) {
  803. var dictionaries;
  804. if (!tagDefinitions) {
  805. dictionaries = jsdoc.env.conf.tags.dictionaries;
  806. if (!dictionaries) {
  807. jsdoc.util.logger.error('The configuration setting "tags.dictionaries" is undefined. ' +
  808. 'Unable to load tag definitions.');
  809. return;
  810. }
  811. else {
  812. dictionaries = dictionaries.slice(0).reverse();
  813. }
  814. dictionaries.forEach(function(dictName) {
  815. var tagDefs = exports[DEFINITIONS[dictName]];
  816. if (!tagDefs) {
  817. jsdoc.util.logger.error('The configuration setting "tags.dictionaries" contains ' +
  818. 'the unknown dictionary name %s. Ignoring the dictionary.', dictName);
  819. return;
  820. }
  821. addTagDefinitions(dictionary, tagDefs);
  822. });
  823. }
  824. else {
  825. addTagDefinitions(dictionary, tagDefinitions);
  826. }
  827. };