doctrine.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  1. /*
  2. * @fileoverview Main Doctrine object
  3. * @author Yusuke Suzuki <utatane.tea@gmail.com>
  4. * @author Dan Tao <daniel.tao@gmail.com>
  5. * @author Andrew Eisenberg <andrew@eisenberg.as>
  6. */
  7. (function () {
  8. 'use strict';
  9. var typed,
  10. utility,
  11. jsdoc,
  12. esutils,
  13. hasOwnProperty;
  14. esutils = require('esutils');
  15. typed = require('./typed');
  16. utility = require('./utility');
  17. function sliceSource(source, index, last) {
  18. return source.slice(index, last);
  19. }
  20. hasOwnProperty = (function () {
  21. var func = Object.prototype.hasOwnProperty;
  22. return function hasOwnProperty(obj, name) {
  23. return func.call(obj, name);
  24. };
  25. }());
  26. function shallowCopy(obj) {
  27. var ret = {}, key;
  28. for (key in obj) {
  29. if (obj.hasOwnProperty(key)) {
  30. ret[key] = obj[key];
  31. }
  32. }
  33. return ret;
  34. }
  35. function isASCIIAlphanumeric(ch) {
  36. return (ch >= 0x61 /* 'a' */ && ch <= 0x7A /* 'z' */) ||
  37. (ch >= 0x41 /* 'A' */ && ch <= 0x5A /* 'Z' */) ||
  38. (ch >= 0x30 /* '0' */ && ch <= 0x39 /* '9' */);
  39. }
  40. function isParamTitle(title) {
  41. return title === 'param' || title === 'argument' || title === 'arg';
  42. }
  43. function isReturnTitle(title) {
  44. return title === 'return' || title === 'returns';
  45. }
  46. function isProperty(title) {
  47. return title === 'property' || title === 'prop';
  48. }
  49. function isNameParameterRequired(title) {
  50. return isParamTitle(title) || isProperty(title) ||
  51. title === 'alias' || title === 'this' || title === 'mixes' || title === 'requires';
  52. }
  53. function isAllowedName(title) {
  54. return isNameParameterRequired(title) || title === 'const' || title === 'constant';
  55. }
  56. function isAllowedNested(title) {
  57. return isProperty(title) || isParamTitle(title);
  58. }
  59. function isAllowedOptional(title) {
  60. return isProperty(title) || isParamTitle(title);
  61. }
  62. function isTypeParameterRequired(title) {
  63. return isParamTitle(title) || isReturnTitle(title) ||
  64. title === 'define' || title === 'enum' ||
  65. title === 'implements' || title === 'this' ||
  66. title === 'type' || title === 'typedef' || isProperty(title);
  67. }
  68. // Consider deprecation instead using 'isTypeParameterRequired' and 'Rules' declaration to pick when a type is optional/required
  69. // This would require changes to 'parseType'
  70. function isAllowedType(title) {
  71. return isTypeParameterRequired(title) || title === 'throws' || title === 'const' || title === 'constant' ||
  72. title === 'namespace' || title === 'member' || title === 'var' || title === 'module' ||
  73. title === 'constructor' || title === 'class' || title === 'extends' || title === 'augments' ||
  74. title === 'public' || title === 'private' || title === 'protected';
  75. }
  76. // A regex character class that contains all whitespace except linebreak characters (\r, \n, \u2028, \u2029)
  77. var WHITESPACE = '[ \\f\\t\\v\\u00a0\\u1680\\u180e\\u2000-\\u200a\\u202f\\u205f\\u3000\\ufeff]';
  78. var STAR_MATCHER = '(' + WHITESPACE + '*(?:\\*' + WHITESPACE + '?)?)(.+|[\r\n\u2028\u2029])';
  79. function unwrapComment(doc) {
  80. // JSDoc comment is following form
  81. // /**
  82. // * .......
  83. // */
  84. return doc.
  85. // remove /**
  86. replace(/^\/\*\*?/, '').
  87. // remove */
  88. replace(/\*\/$/, '').
  89. // remove ' * ' at the beginning of a line
  90. replace(new RegExp(STAR_MATCHER, 'g'), '$2').
  91. // remove trailing whitespace
  92. replace(/\s*$/, '');
  93. }
  94. /**
  95. * Converts an index in an "unwrapped" JSDoc comment to the corresponding index in the original "wrapped" version
  96. * @param {string} originalSource The original wrapped comment
  97. * @param {number} unwrappedIndex The index of a character in the unwrapped string
  98. * @returns {number} The index of the corresponding character in the original wrapped string
  99. */
  100. function convertUnwrappedCommentIndex(originalSource, unwrappedIndex) {
  101. var replacedSource = originalSource.replace(/^\/\*\*?/, '');
  102. var numSkippedChars = 0;
  103. var matcher = new RegExp(STAR_MATCHER, 'g');
  104. var match;
  105. while ((match = matcher.exec(replacedSource))) {
  106. numSkippedChars += match[1].length;
  107. if (match.index + match[0].length > unwrappedIndex + numSkippedChars) {
  108. return unwrappedIndex + numSkippedChars + originalSource.length - replacedSource.length;
  109. }
  110. }
  111. return originalSource.replace(/\*\/$/, '').replace(/\s*$/, '').length;
  112. }
  113. // JSDoc Tag Parser
  114. (function (exports) {
  115. var Rules,
  116. index,
  117. lineNumber,
  118. length,
  119. source,
  120. originalSource,
  121. recoverable,
  122. sloppy,
  123. strict;
  124. function advance() {
  125. var ch = source.charCodeAt(index);
  126. index += 1;
  127. if (esutils.code.isLineTerminator(ch) && !(ch === 0x0D /* '\r' */ && source.charCodeAt(index) === 0x0A /* '\n' */)) {
  128. lineNumber += 1;
  129. }
  130. return String.fromCharCode(ch);
  131. }
  132. function scanTitle() {
  133. var title = '';
  134. // waste '@'
  135. advance();
  136. while (index < length && isASCIIAlphanumeric(source.charCodeAt(index))) {
  137. title += advance();
  138. }
  139. return title;
  140. }
  141. function seekContent() {
  142. var ch, waiting, last = index;
  143. waiting = false;
  144. while (last < length) {
  145. ch = source.charCodeAt(last);
  146. if (esutils.code.isLineTerminator(ch) && !(ch === 0x0D /* '\r' */ && source.charCodeAt(last + 1) === 0x0A /* '\n' */)) {
  147. waiting = true;
  148. } else if (waiting) {
  149. if (ch === 0x40 /* '@' */) {
  150. break;
  151. }
  152. if (!esutils.code.isWhiteSpace(ch)) {
  153. waiting = false;
  154. }
  155. }
  156. last += 1;
  157. }
  158. return last;
  159. }
  160. // type expression may have nest brace, such as,
  161. // { { ok: string } }
  162. //
  163. // therefore, scanning type expression with balancing braces.
  164. function parseType(title, last, addRange) {
  165. var ch, brace, type, startIndex, direct = false;
  166. // search '{'
  167. while (index < last) {
  168. ch = source.charCodeAt(index);
  169. if (esutils.code.isWhiteSpace(ch)) {
  170. advance();
  171. } else if (ch === 0x7B /* '{' */) {
  172. advance();
  173. break;
  174. } else {
  175. // this is direct pattern
  176. direct = true;
  177. break;
  178. }
  179. }
  180. if (direct) {
  181. return null;
  182. }
  183. // type expression { is found
  184. brace = 1;
  185. type = '';
  186. while (index < last) {
  187. ch = source.charCodeAt(index);
  188. if (esutils.code.isLineTerminator(ch)) {
  189. advance();
  190. } else {
  191. if (ch === 0x7D /* '}' */) {
  192. brace -= 1;
  193. if (brace === 0) {
  194. advance();
  195. break;
  196. }
  197. } else if (ch === 0x7B /* '{' */) {
  198. brace += 1;
  199. }
  200. if (type === '') {
  201. startIndex = index;
  202. }
  203. type += advance();
  204. }
  205. }
  206. if (brace !== 0) {
  207. // braces is not balanced
  208. return utility.throwError('Braces are not balanced');
  209. }
  210. if (isAllowedOptional(title)) {
  211. return typed.parseParamType(type, {startIndex: convertIndex(startIndex), range: addRange});
  212. }
  213. return typed.parseType(type, {startIndex: convertIndex(startIndex), range: addRange});
  214. }
  215. function scanIdentifier(last) {
  216. var identifier;
  217. if (!esutils.code.isIdentifierStartES5(source.charCodeAt(index)) && !source[index].match(/[0-9]/)) {
  218. return null;
  219. }
  220. identifier = advance();
  221. while (index < last && esutils.code.isIdentifierPartES5(source.charCodeAt(index))) {
  222. identifier += advance();
  223. }
  224. return identifier;
  225. }
  226. function skipWhiteSpace(last) {
  227. while (index < last && (esutils.code.isWhiteSpace(source.charCodeAt(index)) || esutils.code.isLineTerminator(source.charCodeAt(index)))) {
  228. advance();
  229. }
  230. }
  231. function parseName(last, allowBrackets, allowNestedParams) {
  232. var name = '',
  233. useBrackets,
  234. insideString;
  235. skipWhiteSpace(last);
  236. if (index >= last) {
  237. return null;
  238. }
  239. if (source.charCodeAt(index) === 0x5B /* '[' */) {
  240. if (allowBrackets) {
  241. useBrackets = true;
  242. name = advance();
  243. } else {
  244. return null;
  245. }
  246. }
  247. name += scanIdentifier(last);
  248. if (allowNestedParams) {
  249. if (source.charCodeAt(index) === 0x3A /* ':' */ && (
  250. name === 'module' ||
  251. name === 'external' ||
  252. name === 'event')) {
  253. name += advance();
  254. name += scanIdentifier(last);
  255. }
  256. if(source.charCodeAt(index) === 0x5B /* '[' */ && source.charCodeAt(index + 1) === 0x5D /* ']' */){
  257. name += advance();
  258. name += advance();
  259. }
  260. while (source.charCodeAt(index) === 0x2E /* '.' */ ||
  261. source.charCodeAt(index) === 0x2F /* '/' */ ||
  262. source.charCodeAt(index) === 0x23 /* '#' */ ||
  263. source.charCodeAt(index) === 0x2D /* '-' */ ||
  264. source.charCodeAt(index) === 0x7E /* '~' */) {
  265. name += advance();
  266. name += scanIdentifier(last);
  267. }
  268. }
  269. if (useBrackets) {
  270. skipWhiteSpace(last);
  271. // do we have a default value for this?
  272. if (source.charCodeAt(index) === 0x3D /* '=' */) {
  273. // consume the '='' symbol
  274. name += advance();
  275. skipWhiteSpace(last);
  276. var ch;
  277. var bracketDepth = 1;
  278. // scan in the default value
  279. while (index < last) {
  280. ch = source.charCodeAt(index);
  281. if (esutils.code.isWhiteSpace(ch)) {
  282. if (!insideString) {
  283. skipWhiteSpace(last);
  284. ch = source.charCodeAt(index);
  285. }
  286. }
  287. if (ch === 0x27 /* ''' */) {
  288. if (!insideString) {
  289. insideString = '\'';
  290. } else {
  291. if (insideString === '\'') {
  292. insideString = '';
  293. }
  294. }
  295. }
  296. if (ch === 0x22 /* '"' */) {
  297. if (!insideString) {
  298. insideString = '"';
  299. } else {
  300. if (insideString === '"') {
  301. insideString = '';
  302. }
  303. }
  304. }
  305. if (ch === 0x5B /* '[' */) {
  306. bracketDepth++;
  307. } else if (ch === 0x5D /* ']' */ &&
  308. --bracketDepth === 0) {
  309. break;
  310. }
  311. name += advance();
  312. }
  313. }
  314. skipWhiteSpace(last);
  315. if (index >= last || source.charCodeAt(index) !== 0x5D /* ']' */) {
  316. // we never found a closing ']'
  317. return null;
  318. }
  319. // collect the last ']'
  320. name += advance();
  321. }
  322. return name;
  323. }
  324. function skipToTag() {
  325. while (index < length && source.charCodeAt(index) !== 0x40 /* '@' */) {
  326. advance();
  327. }
  328. if (index >= length) {
  329. return false;
  330. }
  331. utility.assert(source.charCodeAt(index) === 0x40 /* '@' */);
  332. return true;
  333. }
  334. function convertIndex(rangeIndex) {
  335. if (source === originalSource) {
  336. return rangeIndex;
  337. }
  338. return convertUnwrappedCommentIndex(originalSource, rangeIndex);
  339. }
  340. function TagParser(options, title) {
  341. this._options = options;
  342. this._title = title.toLowerCase();
  343. this._tag = {
  344. title: title,
  345. description: null
  346. };
  347. if (this._options.lineNumbers) {
  348. this._tag.lineNumber = lineNumber;
  349. }
  350. this._first = index - title.length - 1;
  351. this._last = 0;
  352. // space to save special information for title parsers.
  353. this._extra = { };
  354. }
  355. // addError(err, ...)
  356. TagParser.prototype.addError = function addError(errorText) {
  357. var args = Array.prototype.slice.call(arguments, 1),
  358. msg = errorText.replace(
  359. /%(\d)/g,
  360. function (whole, index) {
  361. utility.assert(index < args.length, 'Message reference must be in range');
  362. return args[index];
  363. }
  364. );
  365. if (!this._tag.errors) {
  366. this._tag.errors = [];
  367. }
  368. if (strict) {
  369. utility.throwError(msg);
  370. }
  371. this._tag.errors.push(msg);
  372. return recoverable;
  373. };
  374. TagParser.prototype.parseType = function () {
  375. // type required titles
  376. if (isTypeParameterRequired(this._title)) {
  377. try {
  378. this._tag.type = parseType(this._title, this._last, this._options.range);
  379. if (!this._tag.type) {
  380. if (!isParamTitle(this._title) && !isReturnTitle(this._title)) {
  381. if (!this.addError('Missing or invalid tag type')) {
  382. return false;
  383. }
  384. }
  385. }
  386. } catch (error) {
  387. this._tag.type = null;
  388. if (!this.addError(error.message)) {
  389. return false;
  390. }
  391. }
  392. } else if (isAllowedType(this._title)) {
  393. // optional types
  394. try {
  395. this._tag.type = parseType(this._title, this._last, this._options.range);
  396. } catch (e) {
  397. //For optional types, lets drop the thrown error when we hit the end of the file
  398. }
  399. }
  400. return true;
  401. };
  402. TagParser.prototype._parseNamePath = function (optional) {
  403. var name;
  404. name = parseName(this._last, sloppy && isAllowedOptional(this._title), true);
  405. if (!name) {
  406. if (!optional) {
  407. if (!this.addError('Missing or invalid tag name')) {
  408. return false;
  409. }
  410. }
  411. }
  412. this._tag.name = name;
  413. return true;
  414. };
  415. TagParser.prototype.parseNamePath = function () {
  416. return this._parseNamePath(false);
  417. };
  418. TagParser.prototype.parseNamePathOptional = function () {
  419. return this._parseNamePath(true);
  420. };
  421. TagParser.prototype.parseName = function () {
  422. var assign, name;
  423. // param, property requires name
  424. if (isAllowedName(this._title)) {
  425. this._tag.name = parseName(this._last, sloppy && isAllowedOptional(this._title), isAllowedNested(this._title));
  426. if (!this._tag.name) {
  427. if (!isNameParameterRequired(this._title)) {
  428. return true;
  429. }
  430. // it's possible the name has already been parsed but interpreted as a type
  431. // it's also possible this is a sloppy declaration, in which case it will be
  432. // fixed at the end
  433. if (isParamTitle(this._title) && this._tag.type && this._tag.type.name) {
  434. this._extra.name = this._tag.type;
  435. this._tag.name = this._tag.type.name;
  436. this._tag.type = null;
  437. } else {
  438. if (!this.addError('Missing or invalid tag name')) {
  439. return false;
  440. }
  441. }
  442. } else {
  443. name = this._tag.name;
  444. if (name.charAt(0) === '[' && name.charAt(name.length - 1) === ']') {
  445. // extract the default value if there is one
  446. // example: @param {string} [somebody=John Doe] description
  447. assign = name.substring(1, name.length - 1).split('=');
  448. if (assign.length > 1) {
  449. this._tag['default'] = assign.slice(1).join('=');
  450. }
  451. this._tag.name = assign[0];
  452. // convert to an optional type
  453. if (this._tag.type && this._tag.type.type !== 'OptionalType') {
  454. this._tag.type = {
  455. type: 'OptionalType',
  456. expression: this._tag.type
  457. };
  458. }
  459. }
  460. }
  461. }
  462. return true;
  463. };
  464. TagParser.prototype.parseDescription = function parseDescription() {
  465. var description = sliceSource(source, index, this._last).trim();
  466. if (description) {
  467. if ((/^-\s+/).test(description)) {
  468. description = description.substring(2);
  469. }
  470. this._tag.description = description;
  471. }
  472. return true;
  473. };
  474. TagParser.prototype.parseCaption = function parseDescription() {
  475. var description = sliceSource(source, index, this._last).trim();
  476. var captionStartTag = '<caption>';
  477. var captionEndTag = '</caption>';
  478. var captionStart = description.indexOf(captionStartTag);
  479. var captionEnd = description.indexOf(captionEndTag);
  480. if (captionStart >= 0 && captionEnd >= 0) {
  481. this._tag.caption = description.substring(
  482. captionStart + captionStartTag.length, captionEnd).trim();
  483. this._tag.description = description.substring(captionEnd + captionEndTag.length).trim();
  484. } else {
  485. this._tag.description = description;
  486. }
  487. return true;
  488. };
  489. TagParser.prototype.parseKind = function parseKind() {
  490. var kind, kinds;
  491. kinds = {
  492. 'class': true,
  493. 'constant': true,
  494. 'event': true,
  495. 'external': true,
  496. 'file': true,
  497. 'function': true,
  498. 'member': true,
  499. 'mixin': true,
  500. 'module': true,
  501. 'namespace': true,
  502. 'typedef': true
  503. };
  504. kind = sliceSource(source, index, this._last).trim();
  505. this._tag.kind = kind;
  506. if (!hasOwnProperty(kinds, kind)) {
  507. if (!this.addError('Invalid kind name \'%0\'', kind)) {
  508. return false;
  509. }
  510. }
  511. return true;
  512. };
  513. TagParser.prototype.parseAccess = function parseAccess() {
  514. var access;
  515. access = sliceSource(source, index, this._last).trim();
  516. this._tag.access = access;
  517. if (access !== 'private' && access !== 'protected' && access !== 'public') {
  518. if (!this.addError('Invalid access name \'%0\'', access)) {
  519. return false;
  520. }
  521. }
  522. return true;
  523. };
  524. TagParser.prototype.parseThis = function parseThis() {
  525. // this name may be a name expression (e.g. {foo.bar}),
  526. // an union (e.g. {foo.bar|foo.baz}) or a name path (e.g. foo.bar)
  527. var value = sliceSource(source, index, this._last).trim();
  528. if (value && value.charAt(0) === '{') {
  529. var gotType = this.parseType();
  530. if (gotType && this._tag.type.type === 'NameExpression' || this._tag.type.type === 'UnionType') {
  531. this._tag.name = this._tag.type.name;
  532. return true;
  533. } else {
  534. return this.addError('Invalid name for this');
  535. }
  536. } else {
  537. return this.parseNamePath();
  538. }
  539. };
  540. TagParser.prototype.parseVariation = function parseVariation() {
  541. var variation, text;
  542. text = sliceSource(source, index, this._last).trim();
  543. variation = parseFloat(text, 10);
  544. this._tag.variation = variation;
  545. if (isNaN(variation)) {
  546. if (!this.addError('Invalid variation \'%0\'', text)) {
  547. return false;
  548. }
  549. }
  550. return true;
  551. };
  552. TagParser.prototype.ensureEnd = function () {
  553. var shouldBeEmpty = sliceSource(source, index, this._last).trim();
  554. if (shouldBeEmpty) {
  555. if (!this.addError('Unknown content \'%0\'', shouldBeEmpty)) {
  556. return false;
  557. }
  558. }
  559. return true;
  560. };
  561. TagParser.prototype.epilogue = function epilogue() {
  562. var description;
  563. description = this._tag.description;
  564. // un-fix potentially sloppy declaration
  565. if (isAllowedOptional(this._title) && !this._tag.type && description && description.charAt(0) === '[') {
  566. this._tag.type = this._extra.name;
  567. if (!this._tag.name) {
  568. this._tag.name = undefined;
  569. }
  570. if (!sloppy) {
  571. if (!this.addError('Missing or invalid tag name')) {
  572. return false;
  573. }
  574. }
  575. }
  576. return true;
  577. };
  578. Rules = {
  579. // http://usejsdoc.org/tags-access.html
  580. 'access': ['parseAccess'],
  581. // http://usejsdoc.org/tags-alias.html
  582. 'alias': ['parseNamePath', 'ensureEnd'],
  583. // http://usejsdoc.org/tags-augments.html
  584. 'augments': ['parseType', 'parseNamePathOptional', 'ensureEnd'],
  585. // http://usejsdoc.org/tags-constructor.html
  586. 'constructor': ['parseType', 'parseNamePathOptional', 'ensureEnd'],
  587. // Synonym: http://usejsdoc.org/tags-constructor.html
  588. 'class': ['parseType', 'parseNamePathOptional', 'ensureEnd'],
  589. // Synonym: http://usejsdoc.org/tags-extends.html
  590. 'extends': ['parseType', 'parseNamePathOptional', 'ensureEnd'],
  591. // http://usejsdoc.org/tags-example.html
  592. 'example': ['parseCaption'],
  593. // http://usejsdoc.org/tags-deprecated.html
  594. 'deprecated': ['parseDescription'],
  595. // http://usejsdoc.org/tags-global.html
  596. 'global': ['ensureEnd'],
  597. // http://usejsdoc.org/tags-inner.html
  598. 'inner': ['ensureEnd'],
  599. // http://usejsdoc.org/tags-instance.html
  600. 'instance': ['ensureEnd'],
  601. // http://usejsdoc.org/tags-kind.html
  602. 'kind': ['parseKind'],
  603. // http://usejsdoc.org/tags-mixes.html
  604. 'mixes': ['parseNamePath', 'ensureEnd'],
  605. // http://usejsdoc.org/tags-mixin.html
  606. 'mixin': ['parseNamePathOptional', 'ensureEnd'],
  607. // http://usejsdoc.org/tags-member.html
  608. 'member': ['parseType', 'parseNamePathOptional', 'ensureEnd'],
  609. // http://usejsdoc.org/tags-method.html
  610. 'method': ['parseNamePathOptional', 'ensureEnd'],
  611. // http://usejsdoc.org/tags-module.html
  612. 'module': ['parseType', 'parseNamePathOptional', 'ensureEnd'],
  613. // Synonym: http://usejsdoc.org/tags-method.html
  614. 'func': ['parseNamePathOptional', 'ensureEnd'],
  615. // Synonym: http://usejsdoc.org/tags-method.html
  616. 'function': ['parseNamePathOptional', 'ensureEnd'],
  617. // Synonym: http://usejsdoc.org/tags-member.html
  618. 'var': ['parseType', 'parseNamePathOptional', 'ensureEnd'],
  619. // http://usejsdoc.org/tags-name.html
  620. 'name': ['parseNamePath', 'ensureEnd'],
  621. // http://usejsdoc.org/tags-namespace.html
  622. 'namespace': ['parseType', 'parseNamePathOptional', 'ensureEnd'],
  623. // http://usejsdoc.org/tags-private.html
  624. 'private': ['parseType', 'parseDescription'],
  625. // http://usejsdoc.org/tags-protected.html
  626. 'protected': ['parseType', 'parseDescription'],
  627. // http://usejsdoc.org/tags-public.html
  628. 'public': ['parseType', 'parseDescription'],
  629. // http://usejsdoc.org/tags-readonly.html
  630. 'readonly': ['ensureEnd'],
  631. // http://usejsdoc.org/tags-requires.html
  632. 'requires': ['parseNamePath', 'ensureEnd'],
  633. // http://usejsdoc.org/tags-since.html
  634. 'since': ['parseDescription'],
  635. // http://usejsdoc.org/tags-static.html
  636. 'static': ['ensureEnd'],
  637. // http://usejsdoc.org/tags-summary.html
  638. 'summary': ['parseDescription'],
  639. // http://usejsdoc.org/tags-this.html
  640. 'this': ['parseThis', 'ensureEnd'],
  641. // http://usejsdoc.org/tags-todo.html
  642. 'todo': ['parseDescription'],
  643. // http://usejsdoc.org/tags-typedef.html
  644. 'typedef': ['parseType', 'parseNamePathOptional'],
  645. // http://usejsdoc.org/tags-variation.html
  646. 'variation': ['parseVariation'],
  647. // http://usejsdoc.org/tags-version.html
  648. 'version': ['parseDescription']
  649. };
  650. TagParser.prototype.parse = function parse() {
  651. var i, iz, sequences, method;
  652. // empty title
  653. if (!this._title) {
  654. if (!this.addError('Missing or invalid title')) {
  655. return null;
  656. }
  657. }
  658. // Seek to content last index.
  659. this._last = seekContent(this._title);
  660. if (this._options.range) {
  661. this._tag.range = [this._first, source.slice(0, this._last).replace(/\s*$/, '').length].map(convertIndex);
  662. }
  663. if (hasOwnProperty(Rules, this._title)) {
  664. sequences = Rules[this._title];
  665. } else {
  666. // default sequences
  667. sequences = ['parseType', 'parseName', 'parseDescription', 'epilogue'];
  668. }
  669. for (i = 0, iz = sequences.length; i < iz; ++i) {
  670. method = sequences[i];
  671. if (!this[method]()) {
  672. return null;
  673. }
  674. }
  675. return this._tag;
  676. };
  677. function parseTag(options) {
  678. var title, parser, tag;
  679. // skip to tag
  680. if (!skipToTag()) {
  681. return null;
  682. }
  683. // scan title
  684. title = scanTitle();
  685. // construct tag parser
  686. parser = new TagParser(options, title);
  687. tag = parser.parse();
  688. // Seek global index to end of this tag.
  689. while (index < parser._last) {
  690. advance();
  691. }
  692. return tag;
  693. }
  694. //
  695. // Parse JSDoc
  696. //
  697. function scanJSDocDescription(preserveWhitespace) {
  698. var description = '', ch, atAllowed;
  699. atAllowed = true;
  700. while (index < length) {
  701. ch = source.charCodeAt(index);
  702. if (atAllowed && ch === 0x40 /* '@' */) {
  703. break;
  704. }
  705. if (esutils.code.isLineTerminator(ch)) {
  706. atAllowed = true;
  707. } else if (atAllowed && !esutils.code.isWhiteSpace(ch)) {
  708. atAllowed = false;
  709. }
  710. description += advance();
  711. }
  712. return preserveWhitespace ? description : description.trim();
  713. }
  714. function parse(comment, options) {
  715. var tags = [], tag, description, interestingTags, i, iz;
  716. if (options === undefined) {
  717. options = {};
  718. }
  719. if (typeof options.unwrap === 'boolean' && options.unwrap) {
  720. source = unwrapComment(comment);
  721. } else {
  722. source = comment;
  723. }
  724. originalSource = comment;
  725. // array of relevant tags
  726. if (options.tags) {
  727. if (Array.isArray(options.tags)) {
  728. interestingTags = { };
  729. for (i = 0, iz = options.tags.length; i < iz; i++) {
  730. if (typeof options.tags[i] === 'string') {
  731. interestingTags[options.tags[i]] = true;
  732. } else {
  733. utility.throwError('Invalid "tags" parameter: ' + options.tags);
  734. }
  735. }
  736. } else {
  737. utility.throwError('Invalid "tags" parameter: ' + options.tags);
  738. }
  739. }
  740. length = source.length;
  741. index = 0;
  742. lineNumber = 0;
  743. recoverable = options.recoverable;
  744. sloppy = options.sloppy;
  745. strict = options.strict;
  746. description = scanJSDocDescription(options.preserveWhitespace);
  747. while (true) {
  748. tag = parseTag(options);
  749. if (!tag) {
  750. break;
  751. }
  752. if (!interestingTags || interestingTags.hasOwnProperty(tag.title)) {
  753. tags.push(tag);
  754. }
  755. }
  756. return {
  757. description: description,
  758. tags: tags
  759. };
  760. }
  761. exports.parse = parse;
  762. }(jsdoc = {}));
  763. exports.version = utility.VERSION;
  764. exports.parse = jsdoc.parse;
  765. exports.parseType = typed.parseType;
  766. exports.parseParamType = typed.parseParamType;
  767. exports.unwrapComment = unwrapComment;
  768. exports.Syntax = shallowCopy(typed.Syntax);
  769. exports.Error = utility.DoctrineError;
  770. exports.type = {
  771. Syntax: exports.Syntax,
  772. parseType: typed.parseType,
  773. parseParamType: typed.parseParamType,
  774. stringify: typed.stringify
  775. };
  776. }());
  777. /* vim: set sw=4 ts=4 et tw=80 : */