UNPKG

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