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