doctrine.js 32 KB

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