UNPKG

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