.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / doctrine / lib / doctrine.js
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 : */