851e67c0d9a87ee088e0ef4e83bcfaaf5e8fe6ef
[platform/framework/web/crosswalk-tizen.git] /
1 /*
2   Copyright (C) 2013 Yusuke Suzuki <utatane.tea@gmail.com>
3
4   Redistribution and use in source and binary forms, with or without
5   modification, are permitted provided that the following conditions are met:
6
7     * Redistributions of source code must retain the above copyright
8       notice, this list of conditions and the following disclaimer.
9     * Redistributions in binary form must reproduce the above copyright
10       notice, this list of conditions and the following disclaimer in the
11       documentation and/or other materials provided with the distribution.
12
13   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16   ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
17   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24 /*global require describe it*/
25 /*jslint node:true */
26 'use strict';
27
28 var fs = require('fs'),
29     path = require('path'),
30     root = path.join(path.dirname(fs.realpathSync(__filename)), '..'),
31     doctrine = require(root);
32 require('should');
33
34 describe('parse', function () {
35     it('alias', function () {
36         var res = doctrine.parse('/** @alias */', { unwrap: true });
37         res.tags.should.have.length(0);
38     });
39
40     it('alias with name', function () {
41         var res = doctrine.parse('/** @alias aliasName */', { unwrap: true });
42         res.tags.should.have.length(1);
43         res.tags[0].should.have.property('title', 'alias');
44         res.tags[0].should.have.property('name', 'aliasName');
45     });
46
47     it('alias with namepath', function () {
48         var res = doctrine.parse('/** @alias aliasName.OK */', { unwrap: true });
49         res.tags.should.have.length(1);
50         res.tags[0].should.have.property('title', 'alias');
51         res.tags[0].should.have.property('name', 'aliasName.OK');
52     });
53
54     it('const', function () {
55         var res = doctrine.parse('/** @const */', { unwrap: true });
56         res.tags.should.have.length(1);
57         res.tags[0].should.have.property('title', 'const');
58     });
59
60     it('const with name', function () {
61         var res = doctrine.parse('/** @const constname */', { unwrap: true });
62         res.tags.should.have.length(1);
63         res.tags[0].should.have.property('title', 'const');
64         res.tags[0].should.have.property('name', 'constname');
65     });
66
67     it('constant with name', function () {
68         var res = doctrine.parse('/** @constant constname */', { unwrap: true });
69         res.tags.should.have.length(1);
70         res.tags[0].should.have.property('title', 'constant');
71         res.tags[0].should.have.property('name', 'constname');
72     });
73
74     it('const with type and name', function () {
75         var res = doctrine.parse('/** @const {String} constname */', { unwrap: true });
76         res.tags.should.have.length(1);
77         res.tags[0].should.have.property('title', 'const');
78         res.tags[0].should.have.property('name', 'constname');
79         res.tags[0].should.have.property('type');
80         res.tags[0].type.should.eql({
81             type: 'NameExpression',
82             name: 'String'
83         });
84     });
85
86     it('constant with type and name', function () {
87         var res = doctrine.parse('/** @constant {String} constname */', { unwrap: true });
88         res.tags.should.have.length(1);
89         res.tags[0].should.have.property('title', 'constant');
90         res.tags[0].should.have.property('name', 'constname');
91         res.tags[0].should.have.property('type');
92         res.tags[0].type.should.eql({
93             type: 'NameExpression',
94             name: 'String'
95         });
96     });
97
98     it('const multiple', function () {
99         var res = doctrine.parse("/**@const\n @const*/", { unwrap: true });
100         res.tags.should.have.length(2);
101         res.tags[0].should.have.property('title', 'const');
102         res.tags[1].should.have.property('title', 'const');
103     });
104
105     it('const double', function () {
106         var res = doctrine.parse("/**@const\n @const*/", { unwrap: true });
107         res.tags.should.have.length(2);
108         res.tags[0].should.have.property('title', 'const');
109         res.tags[1].should.have.property('title', 'const');
110     });
111
112     it('const triple', function () {
113         var res = doctrine.parse(
114             [
115                 "/**",
116                 " * @const @const",
117                 " * @const @const",
118                 " * @const @const",
119                 " */"
120             ].join('\n'), { unwrap: true });
121         res.tags.should.have.length(3);
122         res.tags[0].should.have.property('title', 'const');
123         res.tags[1].should.have.property('title', 'const');
124         res.tags[2].should.have.property('title', 'const');
125     });
126
127     it('constructor', function () {
128         var res = doctrine.parse('/** @constructor */', { unwrap: true });
129         res.tags.should.have.length(1);
130         res.tags[0].should.have.property('title', 'constructor');
131     });
132
133     it('constructor with type', function () {
134         var res = doctrine.parse('/** @constructor {Object} */', { unwrap: true });
135         res.tags.should.have.length(1);
136         res.tags[0].should.have.property('title', 'constructor');
137         res.tags[0].type.should.eql({
138             type: 'NameExpression',
139             name: 'Object'
140         });
141     });
142
143     it('constructor with type and name', function () {
144         var res = doctrine.parse('/** @constructor {Object} objName */', { unwrap: true });
145         res.tags.should.have.length(1);
146         res.tags[0].should.have.property('title', 'constructor');
147         res.tags[0].should.have.property('name', 'objName');
148         res.tags[0].type.should.eql({
149             type: 'NameExpression',
150             name: 'Object'
151         });
152     });
153
154     it('class', function () {
155         var res = doctrine.parse('/** @class */', { unwrap: true });
156         res.tags.should.have.length(1);
157         res.tags[0].should.have.property('title', 'class');
158     });
159
160     it('class with type', function () {
161         var res = doctrine.parse('/** @class {Object} */', { unwrap: true });
162         res.tags.should.have.length(1);
163         res.tags[0].should.have.property('title', 'class');
164         res.tags[0].type.should.eql({
165             type: 'NameExpression',
166             name: 'Object'
167         });
168     });
169
170     it('class with type and name', function () {
171         var res = doctrine.parse('/** @class {Object} objName */', { unwrap: true });
172         res.tags.should.have.length(1);
173         res.tags[0].should.have.property('title', 'class');
174         res.tags[0].should.have.property('name', 'objName');
175         res.tags[0].type.should.eql({
176             type: 'NameExpression',
177             name: 'Object'
178         });
179     });
180
181     it('deprecated', function () {
182         var res = doctrine.parse('/** @deprecated */', { unwrap: true });
183         res.tags.should.have.length(1);
184         res.tags[0].should.have.property('title', 'deprecated');
185     });
186
187     it('deprecated', function () {
188         var res = doctrine.parse('/** @deprecated some text here describing why it is deprecated */', { unwrap: true });
189         res.tags.should.have.length(1);
190         res.tags[0].should.have.property('title', 'deprecated');
191         res.tags[0].should.have.property('description', 'some text here describing why it is deprecated');
192     });
193
194     it('func', function () {
195         var res = doctrine.parse('/** @func */', { unwrap: true });
196         res.tags.should.have.length(1);
197         res.tags[0].should.have.property('title', 'func');
198     });
199
200     it('func with name', function () {
201         var res = doctrine.parse('/** @func thingName.func */', { unwrap: true });
202         res.tags.should.have.length(1);
203         res.tags[0].should.have.property('title', 'func');
204         res.tags[0].should.have.property('name', 'thingName.func');
205     });
206
207     it('func with type', function () {
208         var res = doctrine.parse('/** @func {Object} thingName.func */', { unwrap: true });
209         res.tags.should.have.length(0);
210         // func does not accept type
211     });
212
213     it('function', function () {
214         var res = doctrine.parse('/** @function */', { unwrap: true });
215         res.tags.should.have.length(1);
216         res.tags[0].should.have.property('title', 'function');
217     });
218
219     it('function with name', function () {
220         var res = doctrine.parse('/** @function thingName.function */', { unwrap: true });
221         res.tags.should.have.length(1);
222         res.tags[0].should.have.property('title', 'function');
223         res.tags[0].should.have.property('name', 'thingName.function');
224     });
225
226     it('function with type', function () {
227         var res = doctrine.parse('/** @function {Object} thingName.function */', { unwrap: true });
228         res.tags.should.have.length(0);
229         // function does not accept type
230     });
231
232     it('member', function () {
233         var res = doctrine.parse('/** @member */', { unwrap: true });
234         res.tags.should.have.length(1);
235         res.tags[0].should.have.property('title', 'member');
236     });
237
238     it('member with name', function () {
239         var res = doctrine.parse('/** @member thingName.name */', { unwrap: true });
240         res.tags.should.have.length(1);
241         res.tags[0].should.have.property('title', 'member');
242         res.tags[0].should.have.property('name', 'thingName.name');
243     });
244
245     it('member with type', function () {
246         var res = doctrine.parse('/** @member {Object} thingName.name */', { unwrap: true });
247         res.tags.should.have.length(1);
248         res.tags[0].should.have.property('title', 'member');
249         res.tags[0].should.have.property('name', 'thingName.name');
250         res.tags[0].should.have.property('type');
251         res.tags[0].type.should.eql({
252             type: 'NameExpression',
253             name: 'Object'
254         });
255     });
256
257     it('method', function () {
258         var res = doctrine.parse('/** @method */', { unwrap: true });
259         res.tags.should.have.length(1);
260         res.tags[0].should.have.property('title', 'method');
261     });
262
263     it('method with name', function () {
264         var res = doctrine.parse('/** @method thingName.function */', { unwrap: true });
265         res.tags.should.have.length(1);
266         res.tags[0].should.have.property('title', 'method');
267         res.tags[0].should.have.property('name', 'thingName.function');
268     });
269
270     it('method with type', function () {
271         var res = doctrine.parse('/** @method {Object} thingName.function */', { unwrap: true });
272         res.tags.should.have.length(0);
273         // method does not accept type
274     });
275
276     it('mixes', function () {
277         var res = doctrine.parse('/** @mixes */', { unwrap: true });
278         res.tags.should.have.length(0);
279     });
280
281     it('mixes with name', function () {
282         var res = doctrine.parse('/** @mixes thingName */', { unwrap: true });
283         res.tags.should.have.length(1);
284         res.tags[0].should.have.property('title', 'mixes');
285         res.tags[0].should.have.property('name', 'thingName');
286     });
287
288     it('mixes with namepath', function () {
289         var res = doctrine.parse('/** @mixes thingName.name */', { unwrap: true });
290         res.tags.should.have.length(1);
291         res.tags[0].should.have.property('title', 'mixes');
292         res.tags[0].should.have.property('name', 'thingName.name');
293     });
294
295     it('mixin', function () {
296         var res = doctrine.parse('/** @mixin */', { unwrap: true });
297         res.tags.should.have.length(1);
298         res.tags[0].should.have.property('title', 'mixin');
299     });
300
301     it('mixin with name', function () {
302         var res = doctrine.parse('/** @mixin thingName */', { unwrap: true });
303         res.tags.should.have.length(1);
304         res.tags[0].should.have.property('title', 'mixin');
305         res.tags[0].should.have.property('name', 'thingName');
306     });
307
308     it('mixin with namepath', function () {
309         var res = doctrine.parse('/** @mixin thingName.name */', { unwrap: true });
310         res.tags.should.have.length(1);
311         res.tags[0].should.have.property('title', 'mixin');
312         res.tags[0].should.have.property('name', 'thingName.name');
313     });
314
315     it('module', function () {
316         var res = doctrine.parse('/** @module */', { unwrap: true });
317         res.tags.should.have.length(1);
318         res.tags[0].should.have.property('title', 'module');
319     });
320
321     it('module with name', function () {
322         var res = doctrine.parse('/** @module thingName.name */', { unwrap: true });
323         res.tags.should.have.length(1);
324         res.tags[0].should.have.property('title', 'module');
325         res.tags[0].should.have.property('name', 'thingName.name');
326     });
327
328     it('module with type', function () {
329         var res = doctrine.parse('/** @module {Object} thingName.name */', { unwrap: true });
330         res.tags.should.have.length(1);
331         res.tags[0].should.have.property('title', 'module');
332         res.tags[0].should.have.property('name', 'thingName.name');
333         res.tags[0].should.have.property('type');
334         res.tags[0].type.should.eql({
335             type: 'NameExpression',
336             name: 'Object'
337         });
338     });
339
340     it('name', function () {
341         var res = doctrine.parse('/** @name thingName.name */', { unwrap: true });
342         res.tags.should.have.length(1);
343         res.tags[0].should.have.property('title', 'name');
344         res.tags[0].should.have.property('name', 'thingName.name');
345     });
346
347     it('name', function () {
348         var res = doctrine.parse('/** @name thingName#name */', { unwrap: true });
349         res.tags.should.have.length(1);
350         res.tags[0].should.have.property('title', 'name');
351         res.tags[0].should.have.property('name', 'thingName#name');
352     });
353
354     it('name', function () {
355         var res = doctrine.parse('/** @name thingName~name */', { unwrap: true });
356         res.tags.should.have.length(1);
357         res.tags[0].should.have.property('title', 'name');
358         res.tags[0].should.have.property('name', 'thingName~name');
359     });
360
361     it('name', function () {
362         var res = doctrine.parse('/** @name {thing} thingName.name */', { unwrap: true });
363         // name does not accept type
364         res.tags.should.have.length(0);
365     });
366
367     it('namespace', function () {
368         var res = doctrine.parse('/** @namespace */', { unwrap: true });
369         res.tags.should.have.length(1);
370         res.tags[0].should.have.property('title', 'namespace');
371     });
372
373     it('namespace with name', function () {
374         var res = doctrine.parse('/** @namespace thingName.name */', { unwrap: true });
375         res.tags.should.have.length(1);
376         res.tags[0].should.have.property('title', 'namespace');
377         res.tags[0].should.have.property('name', 'thingName.name');
378     });
379
380     it('namespace with type', function () {
381         var res = doctrine.parse('/** @namespace {Object} thingName.name */', { unwrap: true });
382         res.tags.should.have.length(1);
383         res.tags[0].should.have.property('title', 'namespace');
384         res.tags[0].should.have.property('name', 'thingName.name');
385         res.tags[0].should.have.property('type');
386         res.tags[0].type.should.eql({
387             type: 'NameExpression',
388             name: 'Object'
389         });
390     });
391
392     it('param', function () {
393         var res = doctrine.parse(
394             [
395                 "/**",
396                 " * @param {String} userName",
397                 "*/"
398             ].join('\n'), { unwrap: true });
399         res.tags.should.have.length(1);
400         res.tags[0].should.have.property('title', 'param');
401         res.tags[0].should.have.property('name', 'userName');
402         res.tags[0].should.have.property('type');
403         res.tags[0].type.should.eql({
404             type: 'NameExpression',
405             name: 'String'
406         });
407     });
408
409     it('param with properties', function () {
410         var res = doctrine.parse(
411             [
412                 "/**",
413                 " * @param {String} user.name",
414                 "*/"
415             ].join('\n'), { unwrap: true });
416         res.tags.should.have.length(1);
417         res.tags[0].should.have.property('title', 'param');
418         res.tags[0].should.have.property('name', 'user.name');
419         res.tags[0].should.have.property('type');
420         res.tags[0].type.should.eql({
421             type: 'NameExpression',
422             name: 'String'
423         });
424     });
425
426     it('arg with properties', function () {
427         var res = doctrine.parse(
428             [
429                 "/**",
430                 " * @arg {String} user.name",
431                 "*/"
432             ].join('\n'), { unwrap: true });
433         res.tags.should.have.length(1);
434         res.tags[0].should.have.property('title', 'arg');
435         res.tags[0].should.have.property('name', 'user.name');
436         res.tags[0].should.have.property('type');
437         res.tags[0].type.should.eql({
438             type: 'NameExpression',
439             name: 'String'
440         });
441     });
442
443     it('argument with properties', function () {
444         var res = doctrine.parse(
445             [
446                 "/**",
447                 " * @argument {String} user.name",
448                 "*/"
449             ].join('\n'), { unwrap: true });
450         res.tags.should.have.length(1);
451         res.tags[0].should.have.property('title', 'argument');
452         res.tags[0].should.have.property('name', 'user.name');
453         res.tags[0].should.have.property('type');
454         res.tags[0].type.should.eql({
455             type: 'NameExpression',
456             name: 'String'
457         });
458     });
459
460     it('param typeless', function () {
461         var res = doctrine.parse(
462         [
463             "/**",
464             " * @param userName",
465             "*/"
466         ].join('\n'), { unwrap: true });
467         res.tags.should.have.length(1);
468         res.tags[0].should.eql({
469             title: 'param',
470             type: null,
471             name: 'userName',
472             description: null
473         });
474
475         var res = doctrine.parse(
476         [
477             "/**",
478             " * @param userName Something descriptive",
479             "*/"
480         ].join('\n'), { unwrap: true });
481         res.tags.should.have.length(1);
482         res.tags[0].should.eql({
483             title: 'param',
484             type: null,
485             name: 'userName',
486             description: 'Something descriptive'
487         });
488
489         var res = doctrine.parse(
490         [
491             "/**",
492             " * @param user.name Something descriptive",
493             "*/"
494         ].join('\n'), { unwrap: true });
495         res.tags.should.have.length(1);
496         res.tags[0].should.eql({
497             title: 'param',
498             type: null,
499             name: 'user.name',
500             description: 'Something descriptive'
501         });
502     });
503
504     it('param broken', function () {
505         var res = doctrine.parse(
506             [
507                 "/**",
508                 " * @param {String} userName",
509                 " * @param {String userName",
510                 "*/"
511             ].join('\n'), { unwrap: true });
512         res.tags.should.have.length(1);
513         res.tags[0].should.have.property('title', 'param');
514         res.tags[0].should.have.property('name', 'userName');
515         res.tags[0].should.have.property('type');
516         res.tags[0].type.should.eql({
517             type: 'NameExpression',
518             name: 'String'
519         });
520     });
521
522     it('param record', function () {
523         var res = doctrine.parse(
524             [
525                 "/**",
526                 " * @param {{ok:String}} userName",
527                 "*/"
528             ].join('\n'), { unwrap: true });
529         res.tags.should.have.length(1);
530         res.tags[0].should.have.property('title', 'param');
531         res.tags[0].should.have.property('name', 'userName');
532         res.tags[0].should.have.property('type');
533         res.tags[0].type.should.eql({
534             type: 'RecordType',
535             fields: [{
536                 type: 'FieldType',
537                 key: 'ok',
538                 value: {
539                     type: 'NameExpression',
540                     name: 'String'
541                 }
542             }]
543         });
544     });
545
546     it('param record broken', function () {
547         var res = doctrine.parse(
548             [
549                 "/**",
550                 " * @param {{ok:String} userName",
551                 "*/"
552             ].join('\n'), { unwrap: true });
553         res.tags.should.be.empty;
554     });
555
556     it('param multiple lines', function () {
557         var res = doctrine.parse(
558             [
559                 "/**",
560                 " * @param {string|",
561                 " *     number} userName",
562                 " * }}",
563                 "*/"
564             ].join('\n'), { unwrap: true });
565         res.tags.should.have.length(1);
566         res.tags[0].should.have.property('title', 'param');
567         res.tags[0].should.have.property('name', 'userName');
568         res.tags[0].should.have.property('type');
569         res.tags[0].type.should.eql({
570             type: 'UnionType',
571             elements: [{
572                 type: 'NameExpression',
573                 name: 'string'
574             }, {
575                 type: 'NameExpression',
576                 name: 'number'
577             }]
578         });
579     });
580
581     it('param without braces', function () {
582         var res = doctrine.parse(
583             [
584                 "/**",
585                 " * @param string name description",
586                 "*/"
587             ].join('\n'), { unwrap: true });
588         res.tags.should.have.length(1);
589         res.tags[0].should.have.property('title', 'param');
590         res.tags[0].should.have.property('name', 'string');
591         res.tags[0].should.have.property('type', null);
592         res.tags[0].should.have.property('description', 'name description');
593     });
594
595     it('param w/ hyphen before description', function () {
596         var res = doctrine.parse(
597             [
598                 "/**",
599                 " * @param {string} name - description",
600                 "*/"
601             ].join('\n'), { unwrap: true });
602         res.tags.should.have.length(1);
603         res.tags[0].should.eql({
604             title: 'param',
605             type: {
606                 type: 'NameExpression',
607                 name: 'string'
608             },
609             name: 'name',
610             description: 'description'
611         });
612     });
613
614     it('param w/ hyphen + leading space before description', function () {
615         var res = doctrine.parse(
616             [
617                 "/**",
618                 " * @param {string} name -   description",
619                 "*/"
620             ].join('\n'), { unwrap: true });
621         res.tags.should.have.length(1);
622         res.tags[0].should.eql({
623             title: 'param',
624             type: {
625                 type: 'NameExpression',
626                 name: 'string'
627             },
628             name: 'name',
629             description: '  description'
630         });
631     });
632
633     it('description and param separated by blank line', function () {
634         var res = doctrine.parse(
635             [
636                 "/**",
637                 " * Description",
638                 " * blah blah blah",
639                 " *",
640                 " * @param {string} name description",
641                 "*/"
642             ].join('\n'), { unwrap: true });
643         res.description.should.eql('Description\nblah blah blah');
644         res.tags.should.have.length(1);
645         res.tags[0].should.have.property('title', 'param');
646         res.tags[0].should.have.property('name', 'name');
647         res.tags[0].should.have.property('type');
648         res.tags[0].type.should.eql({
649             type: 'NameExpression',
650             name: 'string'
651         });
652         res.tags[0].should.have.property('description', 'description');
653     });
654
655     it('regular block comment instead of jsdoc-style block comment', function () {
656         var res = doctrine.parse(
657             [
658                 "/*",
659                 " * Description",
660                 " * blah blah blah",
661                 "*/"
662             ].join('\n'), { unwrap: true });
663         res.description.should.eql("Description\nblah blah blah");
664     });
665
666     it('augments', function () {
667         var res = doctrine.parse('/** @augments */', { unwrap: true });
668         res.tags.should.have.length(1);
669     });
670
671     it('augments with name', function () {
672         var res = doctrine.parse('/** @augments ClassName */', { unwrap: true });
673         res.tags.should.have.length(1);
674         res.tags[0].should.have.property('title', 'augments');
675         res.tags[0].should.have.property('name', 'ClassName');
676     });
677
678     it('augments with type', function () {
679         var res = doctrine.parse('/** @augments {ClassName} */', { unwrap: true });
680         res.tags.should.have.length(1);
681         res.tags[0].should.have.property('title', 'augments');
682         res.tags[0].should.have.property('type', {
683           type: 'NameExpression',
684           name: 'ClassName'
685         });
686     });
687
688     it('augments with name', function () {
689         var res = doctrine.parse('/** @augments ClassName.OK */', { unwrap: true });
690         res.tags.should.have.length(1);
691         res.tags[0].should.have.property('title', 'augments');
692         res.tags[0].should.have.property('name', 'ClassName.OK');
693     });
694
695     it('extends', function () {
696         var res = doctrine.parse('/** @extends */', { unwrap: true });
697         res.tags.should.have.length(1);
698     });
699
700     it('extends with name', function () {
701         var res = doctrine.parse('/** @extends ClassName */', { unwrap: true });
702         res.tags.should.have.length(1);
703         res.tags[0].should.have.property('title', 'extends');
704         res.tags[0].should.have.property('name', 'ClassName');
705     });
706
707     it('extends with type', function () {
708         var res = doctrine.parse('/** @extends {ClassName} */', { unwrap: true });
709         res.tags.should.have.length(1);
710         res.tags[0].should.have.property('title', 'extends');
711         res.tags[0].should.have.property('type', {
712           type: 'NameExpression',
713           name: 'ClassName'
714         });
715     });
716
717     it('extends with namepath', function () {
718         var res = doctrine.parse('/** @extends ClassName.OK */', { unwrap: true });
719         res.tags.should.have.length(1);
720         res.tags[0].should.have.property('title', 'extends');
721         res.tags[0].should.have.property('name', 'ClassName.OK');
722     });
723
724     it('prop', function () {
725         var res = doctrine.parse(
726             [
727               "/**",
728               " * @prop {string} thingName - does some stuff",
729               "*/"
730             ].join('\n'), { unwrap: true });
731         res.tags.should.have.length(1);
732         res.tags[0].should.have.property('title', 'prop');
733         res.tags[0].should.have.property('description', 'does some stuff');
734         res.tags[0].type.should.have.property('name', 'string');
735         res.tags[0].should.have.property('name', 'thingName');
736     });
737
738     it('prop without type', function () {
739         var res = doctrine.parse(
740             [
741               "/**",
742               " * @prop thingName - does some stuff",
743               "*/"
744             ].join('\n'), { unwrap: true });
745         res.tags.should.have.length(0);
746     });
747
748
749     it('property', function () {
750         var res = doctrine.parse(
751             [
752               "/**",
753               " * @property {string} thingName - does some stuff",
754               "*/"
755             ].join('\n'), { unwrap: true });
756         res.tags.should.have.length(1);
757         res.tags[0].should.have.property('title', 'property');
758         res.tags[0].should.have.property('description', 'does some stuff');
759         res.tags[0].type.should.have.property('name', 'string');
760         res.tags[0].should.have.property('name', 'thingName');
761     });
762
763     it('property without type', function () {
764         var res = doctrine.parse(
765             [
766               "/**",
767               " * @property thingName - does some stuff",
768               "*/"
769             ].join('\n'), { unwrap: true });
770         res.tags.should.have.length(0);
771     });
772
773     it('property with nested name', function () {
774         var res = doctrine.parse(
775             [
776               "/**",
777               " * @property {string} thingName.name - does some stuff",
778               "*/"
779             ].join('\n'), { unwrap: true });
780         res.tags.should.have.length(1);
781         res.tags[0].should.have.property('title', 'property');
782         res.tags[0].should.have.property('description', 'does some stuff');
783         res.tags[0].type.should.have.property('name', 'string');
784         res.tags[0].should.have.property('name', 'thingName.name');
785     });
786
787     it('throws', function () {
788         var res = doctrine.parse(
789             [
790               "/**",
791               " * @throws {Error} if something goes wrong",
792               " */"
793             ].join('\n'), { unwrap: true });
794         res.tags.should.have.length(1);
795         res.tags[0].should.have.property('title', 'throws');
796         res.tags[0].should.have.property('description', 'if something goes wrong');
797         res.tags[0].type.should.have.property('name', 'Error');
798     });
799
800     it('throws without type', function () {
801         var res = doctrine.parse(
802             [
803               "/**",
804               " * @throws if something goes wrong",
805               " */"
806             ].join('\n'), { unwrap: true });
807         res.tags.should.have.length(1);
808         res.tags[0].should.have.property('title', 'throws');
809         res.tags[0].should.have.property('description', 'if something goes wrong');
810     });
811
812     it('kind', function () {
813         var res = doctrine.parse('/** @kind class */', { unwrap: true });
814         res.tags.should.have.length(1);
815         res.tags[0].should.have.property('title', 'kind');
816         res.tags[0].should.have.property('kind', 'class');
817     });
818
819     it('kind error', function () {
820         var res = doctrine.parse('/** @kind ng */', { unwrap: true, recoverable: true });
821         res.tags.should.have.length(1);
822         res.tags[0].should.have.property('errors');
823         res.tags[0].errors.should.have.length(1);
824         res.tags[0].errors[0].should.equal('Invalid kind name \'ng\'');
825     });
826
827     it('todo', function () {
828         var res = doctrine.parse('/** @todo Write the documentation */', { unwrap: true });
829         res.tags.should.have.length(1);
830         res.tags[0].should.have.property('title', 'todo');
831         res.tags[0].should.have.property('description', 'Write the documentation');
832     });
833
834     it('summary', function () {
835         // japanese lang
836         var res = doctrine.parse('/** @summary ゆるゆり3期おめでとー */', { unwrap: true });
837         res.tags.should.have.length(1);
838         res.tags[0].should.have.property('title', 'summary');
839         res.tags[0].should.have.property('description', 'ゆるゆり3期おめでとー');
840     });
841
842     it('variation', function () {
843         // japanese lang
844         var res = doctrine.parse('/** @variation 42 */', { unwrap: true });
845         res.tags.should.have.length(1);
846         res.tags[0].should.have.property('title', 'variation');
847         res.tags[0].should.have.property('variation', 42);
848     });
849
850     it('variation error', function () {
851         // japanese lang
852         var res = doctrine.parse('/** @variation Animation */', { unwrap: true, recoverable: true });
853         res.tags.should.have.length(1);
854         res.tags[0].should.have.property('errors');
855         res.tags[0].errors.should.have.length(1);
856         res.tags[0].errors[0].should.equal('Invalid variation \'Animation\'');
857     });
858
859     it('access', function () {
860         var res = doctrine.parse('/** @access public */', { unwrap: true });
861         res.tags.should.have.length(1);
862         res.tags[0].should.have.property('title', 'access');
863         res.tags[0].should.have.property('access', 'public');
864     });
865
866     it('access error', function () {
867         var res = doctrine.parse('/** @access ng */', { unwrap: true, recoverable: true });
868         res.tags.should.have.length(1);
869         res.tags[0].should.have.property('errors');
870         res.tags[0].errors.should.have.length(1);
871         res.tags[0].errors[0].should.equal('Invalid access name \'ng\'');
872     });
873
874     it('public', function () {
875         var res = doctrine.parse('/** @public */', { unwrap: true });
876         res.tags.should.have.length(1);
877         res.tags[0].should.have.property('title', 'public');
878     });
879
880     it('public type and description', function () {
881         var res = doctrine.parse('/** @public {number} ok */', { unwrap: true, recoverable: true });
882         res.tags.should.have.length(1);
883         res.tags[0].should.have.property('title', 'public');
884         res.tags[0].should.have.property('description', 'ok');
885         res.tags[0].should.have.property('type');
886         res.tags[0].type.should.eql({
887             type: 'NameExpression',
888             name: 'number'
889         });
890     });
891
892     it('protected', function () {
893         var res = doctrine.parse('/** @protected */', { unwrap: true });
894         res.tags.should.have.length(1);
895         res.tags[0].should.have.property('title', 'protected');
896     });
897
898     it('protected type and description', function () {
899         var res = doctrine.parse('/** @protected {number} ok */', { unwrap: true, recoverable: true });
900         res.tags.should.have.length(1);
901         res.tags[0].should.have.property('title', 'protected');
902         res.tags[0].should.have.property('description', 'ok');
903         res.tags[0].should.have.property('type');
904         res.tags[0].type.should.eql({
905             type: 'NameExpression',
906             name: 'number'
907         });
908     });
909
910     it('private', function () {
911         var res = doctrine.parse('/** @private */', { unwrap: true });
912         res.tags.should.have.length(1);
913         res.tags[0].should.have.property('title', 'private');
914     });
915
916     it('private type and description', function () {
917         var res = doctrine.parse('/** @private {number} ok */', { unwrap: true, recoverable: true });
918         res.tags.should.have.length(1);
919         res.tags[0].should.have.property('title', 'private');
920         res.tags[0].should.have.property('description', 'ok');
921         res.tags[0].should.have.property('type');
922         res.tags[0].type.should.eql({
923             type: 'NameExpression',
924             name: 'number'
925         });
926     });
927
928     it('readonly', function () {
929         var res = doctrine.parse('/** @readonly */', { unwrap: true });
930         res.tags.should.have.length(1);
931         res.tags[0].should.have.property('title', 'readonly');
932     });
933
934     it('readonly error', function () {
935         var res = doctrine.parse('/** @readonly ng */', { unwrap: true, recoverable: true });
936         res.tags.should.have.length(1);
937         res.tags[0].should.have.property('errors');
938         res.tags[0].errors.should.have.length(1);
939         res.tags[0].errors[0].should.equal('Unknown content \'ng\'');
940     });
941
942     it('requires', function () {
943         var res = doctrine.parse('/** @requires */', { unwrap: true });
944         res.tags.should.have.length(0);
945     });
946
947     it('requires with module name', function () {
948         var res = doctrine.parse('/** @requires name.path */', { unwrap: true });
949         res.tags.should.have.length(1);
950         res.tags[0].should.have.property('title', 'requires');
951         res.tags[0].should.have.property('name', 'name.path');
952     });
953
954     it('global', function () {
955         var res = doctrine.parse('/** @global */', { unwrap: true });
956         res.tags.should.have.length(1);
957         res.tags[0].should.have.property('title', 'global');
958     });
959
960     it('global error', function () {
961         var res = doctrine.parse('/** @global ng */', { unwrap: true, recoverable: true });
962         res.tags.should.have.length(1);
963         res.tags[0].should.have.property('errors');
964         res.tags[0].errors.should.have.length(1);
965         res.tags[0].errors[0].should.equal('Unknown content \'ng\'');
966     });
967
968     it('inner', function () {
969         var res = doctrine.parse('/** @inner */', { unwrap: true });
970         res.tags.should.have.length(1);
971         res.tags[0].should.have.property('title', 'inner');
972     });
973
974     it('inner error', function () {
975         var res = doctrine.parse('/** @inner ng */', { unwrap: true, recoverable: true });
976         res.tags.should.have.length(1);
977         res.tags[0].should.have.property('errors');
978         res.tags[0].errors.should.have.length(1);
979         res.tags[0].errors[0].should.equal('Unknown content \'ng\'');
980     });
981
982     it('instance', function () {
983         var res = doctrine.parse('/** @instance */', { unwrap: true });
984         res.tags.should.have.length(1);
985         res.tags[0].should.have.property('title', 'instance');
986     });
987
988     it('instance error', function () {
989         var res = doctrine.parse('/** @instance ng */', { unwrap: true, recoverable: true });
990         res.tags.should.have.length(1);
991         res.tags[0].should.have.property('errors');
992         res.tags[0].errors.should.have.length(1);
993         res.tags[0].errors[0].should.equal('Unknown content \'ng\'');
994     });
995
996     it('since', function () {
997         var res = doctrine.parse('/** @since 1.2.1 */', { unwrap: true });
998         res.tags.should.have.length(1);
999         res.tags[0].should.have.property('title', 'since');
1000         res.tags[0].should.have.property('description', '1.2.1');
1001     });
1002
1003     it('static', function () {
1004         var res = doctrine.parse('/** @static */', { unwrap: true });
1005         res.tags.should.have.length(1);
1006         res.tags[0].should.have.property('title', 'static');
1007     });
1008
1009     it('static error', function () {
1010         var res = doctrine.parse('/** @static ng */', { unwrap: true, recoverable: true });
1011         res.tags.should.have.length(1);
1012         res.tags[0].should.have.property('errors');
1013         res.tags[0].errors.should.have.length(1);
1014         res.tags[0].errors[0].should.equal('Unknown content \'ng\'');
1015     });
1016
1017     it('this', function () {
1018         var res = doctrine.parse(
1019             [
1020               "/**",
1021               " * @this thingName",
1022               "*/"
1023             ].join('\n'), { unwrap: true });
1024         res.tags.should.have.length(1);
1025         res.tags[0].should.have.property('title', 'this');
1026         res.tags[0].should.have.property('name', 'thingName');
1027     });
1028
1029     it('this with namepath', function () {
1030         var res = doctrine.parse(
1031             [
1032               "/**",
1033               " * @this thingName.name",
1034               "*/"
1035             ].join('\n'), { unwrap: true });
1036         res.tags.should.have.length(1);
1037         res.tags[0].should.have.property('title', 'this');
1038         res.tags[0].should.have.property('name', 'thingName.name');
1039     });
1040
1041     it('this error', function () {
1042         var res = doctrine.parse(
1043             [
1044               "/**",
1045               " * @this",
1046               "*/"
1047             ].join('\n'), { unwrap: true, recoverable: true });
1048         res.tags.should.have.length(1);
1049         res.tags[0].should.have.property('title', 'this');
1050         res.tags[0].should.have.property('errors');
1051         res.tags[0].errors.should.have.length(1);
1052         res.tags[0].errors[0].should.equal('Missing or invalid tag name');
1053     });
1054
1055     it('var', function () {
1056         var res = doctrine.parse('/** @var */', { unwrap: true });
1057         res.tags.should.have.length(1);
1058         res.tags[0].should.have.property('title', 'var');
1059     });
1060
1061     it('var with name', function () {
1062         var res = doctrine.parse('/** @var thingName.name */', { unwrap: true });
1063         res.tags.should.have.length(1);
1064         res.tags[0].should.have.property('title', 'var');
1065         res.tags[0].should.have.property('name', 'thingName.name');
1066     });
1067
1068     it('var with type', function () {
1069         var res = doctrine.parse('/** @var {Object} thingName.name */', { unwrap: true });
1070         res.tags.should.have.length(1);
1071         res.tags[0].should.have.property('title', 'var');
1072         res.tags[0].should.have.property('name', 'thingName.name');
1073         res.tags[0].should.have.property('type');
1074         res.tags[0].type.should.eql({
1075             type: 'NameExpression',
1076             name: 'Object'
1077         });
1078     });
1079
1080     it('version', function () {
1081         var res = doctrine.parse('/** @version 1.2.1 */', { unwrap: true });
1082         res.tags.should.have.length(1);
1083         res.tags[0].should.have.property('title', 'version');
1084         res.tags[0].should.have.property('description', '1.2.1');
1085     });
1086
1087     it('incorrect name', function () {
1088         var res = doctrine.parse('/** @name thingName#%name */', { unwrap: true });
1089         // name does not accept type
1090         res.tags.should.have.length(0);
1091         res.should.eql({
1092             "description": "",
1093             "tags": [
1094             ]
1095         });
1096     });
1097 });
1098
1099 describe('parseType', function () {
1100     it('union type closure-compiler extended', function () {
1101         var type = doctrine.parseType("string|number");
1102         type.should.eql({
1103             type: 'UnionType',
1104             elements: [{
1105                 type: 'NameExpression',
1106                 name: 'string'
1107             }, {
1108                 type: 'NameExpression',
1109                 name: 'number'
1110             }]
1111         });
1112     });
1113
1114     it('empty union type', function () {
1115         var type = doctrine.parseType("()");
1116         type.should.eql({
1117             type: 'UnionType',
1118             elements: []
1119         });
1120     });
1121
1122     it('comma last array type', function () {
1123         var type = doctrine.parseType("[string,]");
1124         type.should.eql({
1125             type: 'ArrayType',
1126             elements: [{
1127                 type: 'NameExpression',
1128                 name: 'string'
1129             }]
1130         });
1131     });
1132
1133     it('array type of all literal', function () {
1134         var type = doctrine.parseType("[*]");
1135         type.should.eql({
1136             type: 'ArrayType',
1137             elements: [{
1138                 type: 'AllLiteral'
1139             }]
1140         });
1141     });
1142
1143     it('array type of nullable literal', function () {
1144         var type = doctrine.parseType("[?]");
1145         type.should.eql({
1146             type: 'ArrayType',
1147             elements: [{
1148                 type: 'NullableLiteral'
1149             }]
1150         });
1151     });
1152
1153     it('comma last record type', function () {
1154         var type = doctrine.parseType("{,}");
1155         type.should.eql({
1156             type: 'RecordType',
1157             fields: []
1158         });
1159     });
1160
1161     it('type application', function () {
1162         var type = doctrine.parseType("Array.<String>");
1163         type.should.eql({
1164             type: 'TypeApplication',
1165             expression: {
1166                 type: 'NameExpression',
1167                 name: 'Array'
1168             },
1169             applications: [{
1170                 type: 'NameExpression',
1171                 name: 'String'
1172             }]
1173         });
1174     });
1175
1176     it('type application with NullableLiteral', function () {
1177         var type = doctrine.parseType("Array<?>");
1178         type.should.eql({
1179             type: 'TypeApplication',
1180             expression: {
1181                 type: 'NameExpression',
1182                 name: 'Array'
1183             },
1184             applications: [{
1185                 type: 'NullableLiteral'
1186             }]
1187         });
1188     });
1189
1190     it('type application with multiple patterns', function () {
1191         var type = doctrine.parseType("Array.<String, Number>");
1192         type.should.eql({
1193             type: 'TypeApplication',
1194             expression: {
1195                 type: 'NameExpression',
1196                 name: 'Array'
1197             },
1198             applications: [{
1199                 type: 'NameExpression',
1200                 name: 'String'
1201             }, {
1202                 type: 'NameExpression',
1203                 name: 'Number'
1204             }]
1205         });
1206     });
1207
1208     it('type application without dot', function () {
1209         var type = doctrine.parseType("Array<String>");
1210         type.should.eql({
1211             type: 'TypeApplication',
1212             expression: {
1213                 type: 'NameExpression',
1214                 name: 'Array'
1215             },
1216             applications: [{
1217                 type: 'NameExpression',
1218                 name: 'String'
1219             }]
1220         });
1221     });
1222
1223     it('array-style type application', function () {
1224         var type = doctrine.parseType("String[]");
1225         type.should.eql({
1226             type: 'TypeApplication',
1227             expression: {
1228                 type: 'NameExpression',
1229                 name: 'Array'
1230             },
1231             applications: [{
1232                 type: 'NameExpression',
1233                 name: 'String'
1234             }]
1235         });
1236     });
1237
1238     it('function type simple', function () {
1239         var type = doctrine.parseType("function()");
1240         type.should.eql({
1241                  "type": "FunctionType",
1242                  "params": [],
1243                  "result": null
1244                 });
1245     });
1246
1247     it('function type with name', function () {
1248         var type = doctrine.parseType("function(a)");
1249         type.should.eql({
1250                  "type": "FunctionType",
1251                  "params": [
1252                 {
1253                    "type": "NameExpression",
1254                    "name": "a"
1255                   }
1256                          ],
1257                  "result": null
1258                 });
1259     });
1260     it('function type with name and type', function () {
1261         var type = doctrine.parseType("function(a:b)");
1262         type.should.eql({
1263                  "type": "FunctionType",
1264                  "params": [
1265                   {
1266                    "type": "ParameterType",
1267                    "name": "a",
1268                    "expression": {
1269                     "type": "NameExpression",
1270                     "name": "b"
1271                    }
1272                   }
1273                  ],
1274                  "result": null
1275                 });
1276     });
1277     it('function type with optional param', function () {
1278         var type = doctrine.parseType("function(a=)");
1279         type.should.eql({
1280                  "type": "FunctionType",
1281                  "params": [
1282                   {
1283                    "type": "OptionalType",
1284                    "expression": {
1285                     "type": "NameExpression",
1286                     "name": "a"
1287                    }
1288                   }
1289                  ],
1290                  "result": null
1291                 });
1292     });
1293     it('function type with optional param name and type', function () {
1294         var type = doctrine.parseType("function(a:b=)");
1295         type.should.eql({
1296                  "type": "FunctionType",
1297                  "params": [
1298                   {
1299                    "type": "OptionalType",
1300                    "expression": {
1301                     "type": "ParameterType",
1302                     "name": "a",
1303                     "expression": {
1304                      "type": "NameExpression",
1305                      "name": "b"
1306                     }
1307                    }
1308                   }
1309                  ],
1310                  "result": null
1311                 });
1312     });
1313     it('function type with rest param', function () {
1314         var type = doctrine.parseType("function(...a)");
1315         type.should.eql({
1316                  "type": "FunctionType",
1317                  "params": [
1318                   {
1319                    "type": "RestType",
1320                    "expression": {
1321                     "type": "NameExpression",
1322                     "name": "a"
1323                    }
1324                   }
1325                  ],
1326                  "result": null
1327                 });
1328     });
1329     it('function type with rest param name and type', function () {
1330         var type = doctrine.parseType("function(...a:b)");
1331         type.should.eql({
1332                  "type": "FunctionType",
1333                  "params": [
1334                   {
1335                    "type": "RestType",
1336                    "expression": {
1337                     "type": "ParameterType",
1338                     "name": "a",
1339                     "expression": {
1340                      "type": "NameExpression",
1341                      "name": "b"
1342                     }
1343                    }
1344                   }
1345                  ],
1346                  "result": null
1347                 });
1348     });
1349
1350     it('function type with optional rest param', function () {
1351         var type = doctrine.parseType("function(...a=)");
1352         type.should.eql({
1353                  "type": "FunctionType",
1354                  "params": [
1355                   {
1356                         "type": "RestType",
1357                         "expression": {
1358                            "type": "OptionalType",
1359                            "expression": {
1360                             "type": "NameExpression",
1361                             "name": "a"
1362                            }
1363                           }
1364                         }
1365                  ],
1366                  "result": null
1367                 });
1368     });
1369     it('function type with optional rest param name and type', function () {
1370         var type = doctrine.parseType("function(...a:b=)");
1371         type.should.eql({
1372                  "type": "FunctionType",
1373                  "params": [
1374                   {
1375                         "type": "RestType",
1376                         "expression": {
1377                            "type": "OptionalType",
1378                            "expression": {
1379                             "type": "ParameterType",
1380                             "name": "a",
1381                             "expression": {
1382                              "type": "NameExpression",
1383                              "name": "b"
1384                             }
1385                           }
1386                         }
1387                  }],
1388                  "result": null
1389                 });
1390     });
1391
1392     it('string value in type', function () {
1393         var type;
1394
1395         type = doctrine.parseType("{'ok':String}");
1396         type.should.eql({
1397             "fields": [
1398                 {
1399                     "key": "ok",
1400                     "type": "FieldType",
1401                     "value": {
1402                         "name": "String",
1403                         "type": "NameExpression"
1404                     }
1405                 }
1406             ],
1407             "type": "RecordType"
1408         });
1409
1410         type = doctrine.parseType('{"\\r\\n\\t\\u2028\\x20\\u20\\b\\f\\v\\\r\n\\\n\\0\\07\\012\\o":String}');
1411         type.should.eql({
1412             "fields": [
1413                 {
1414                     "key": "\r\n\t\u2028\x20u20\b\f\v\0\u0007\u000ao",
1415                     "type": "FieldType",
1416                     "value": {
1417                         "name": "String",
1418                         "type": "NameExpression"
1419                     }
1420                 }
1421             ],
1422             "type": "RecordType"
1423         });
1424
1425         doctrine.parseType.bind(doctrine, "{'ok\":String}").should.throw('unexpected quote');
1426         doctrine.parseType.bind(doctrine, "{'o\n':String}").should.throw('unexpected quote');
1427     });
1428
1429     it('number value in type', function () {
1430         var type;
1431
1432         type = doctrine.parseType("{20:String}");
1433         type.should.eql({
1434             "fields": [
1435                 {
1436                     "key": "20",
1437                     "type": "FieldType",
1438                     "value": {
1439                         "name": "String",
1440                         "type": "NameExpression"
1441                     }
1442                 }
1443             ],
1444             "type": "RecordType"
1445         });
1446
1447         type = doctrine.parseType("{.2:String, 30:Number, 0x20:String}");
1448         type.should.eql({
1449             "fields": [
1450                 {
1451                     "key": "0.2",
1452                     "type": "FieldType",
1453                     "value": {
1454                         "name": "String",
1455                         "type": "NameExpression"
1456                     }
1457                 },
1458                 {
1459                     "key": "30",
1460                     "type": "FieldType",
1461                     "value": {
1462                         "name": "Number",
1463                         "type": "NameExpression"
1464                     }
1465                 },
1466                 {
1467                     "key": "32",
1468                     "type": "FieldType",
1469                     "value": {
1470                         "name": "String",
1471                         "type": "NameExpression"
1472                     }
1473                 }
1474             ],
1475             "type": "RecordType"
1476         });
1477
1478
1479         type = doctrine.parseType("{0X2:String, 0:Number, 100e200:String, 10e-20:Number}");
1480         type.should.eql({
1481             "fields": [
1482                 {
1483                     "key": "2",
1484                     "type": "FieldType",
1485                     "value": {
1486                         "name": "String",
1487                         "type": "NameExpression"
1488                     }
1489                 },
1490                 {
1491                     "key": "0",
1492                     "type": "FieldType",
1493                     "value": {
1494                         "name": "Number",
1495                         "type": "NameExpression"
1496                     }
1497                 },
1498                 {
1499                     "key": "1e+202",
1500                     "type": "FieldType",
1501                     "value": {
1502                         "name": "String",
1503                         "type": "NameExpression"
1504                     }
1505                 },
1506                 {
1507                     "key": "1e-19",
1508                     "type": "FieldType",
1509                     "value": {
1510                         "name": "Number",
1511                         "type": "NameExpression"
1512                     }
1513                 }
1514             ],
1515             "type": "RecordType"
1516         });
1517
1518
1519         doctrine.parseType.bind(doctrine, "{0x:String}").should.throw('unexpected token');
1520         doctrine.parseType.bind(doctrine, "{0x").should.throw('unexpected token');
1521         doctrine.parseType.bind(doctrine, "{0xd").should.throw('unexpected token');
1522         doctrine.parseType.bind(doctrine, "{0x2_:").should.throw('unexpected token');
1523         doctrine.parseType.bind(doctrine, "{021:").should.throw('unexpected token');
1524         doctrine.parseType.bind(doctrine, "{021_:").should.throw('unexpected token');
1525         doctrine.parseType.bind(doctrine, "{021").should.throw('unexpected token');
1526         doctrine.parseType.bind(doctrine, "{08").should.throw('unexpected token');
1527         doctrine.parseType.bind(doctrine, "{0y").should.throw('unexpected token');
1528         doctrine.parseType.bind(doctrine, "{0").should.throw('unexpected token');
1529         doctrine.parseType.bind(doctrine, "{100e2").should.throw('unexpected token');
1530         doctrine.parseType.bind(doctrine, "{100e-2").should.throw('unexpected token');
1531         doctrine.parseType.bind(doctrine, "{100e-200:").should.throw('unexpected token');
1532         doctrine.parseType.bind(doctrine, "{100e:").should.throw('unexpected token');
1533         doctrine.parseType.bind(doctrine, "function(number=, string)").should.throw('not reach to EOF');
1534     });
1535
1536     it('dotted type', function () {
1537         var type;
1538         type = doctrine.parseType("Cocoa.Cappuccino");
1539         type.should.eql({
1540             "name": "Cocoa.Cappuccino",
1541             "type": "NameExpression"
1542         });
1543     });
1544
1545     it('rest array type', function () {
1546         var type;
1547         type = doctrine.parseType("[string,...string]");
1548         type.should.eql({
1549             "elements": [
1550                 {
1551                     "name": "string",
1552                     "type": "NameExpression"
1553                 },
1554                 {
1555                     "expression": {
1556                         "name": "string",
1557                         "type": "NameExpression"
1558                     },
1559                 "type": "RestType"
1560                 }
1561             ],
1562             "type": "ArrayType"
1563         });
1564     });
1565
1566     it ('nullable type', function () {
1567         var type;
1568         type = doctrine.parseType("string?");
1569         type.should.eql({
1570             "expression": {
1571                 "name": "string",
1572                 "type": "NameExpression"
1573             },
1574             "prefix": false,
1575             "type": "NullableType"
1576         });
1577     });
1578
1579     it ('non-nullable type', function () {
1580         var type;
1581         type = doctrine.parseType("string!");
1582         type.should.eql({
1583             "expression": {
1584                 "name": "string",
1585                 "type": "NameExpression"
1586             },
1587             "prefix": false,
1588             "type": "NonNullableType"
1589         });
1590     });
1591
1592     it ('toplevel multiple pipe type', function () {
1593         var type;
1594         type = doctrine.parseType("string|number|Test");
1595         type.should.eql({
1596             "elements": [
1597                 {
1598                     "name": "string",
1599                     "type": "NameExpression"
1600                 },
1601                 {
1602                     "name": "number",
1603                     "type": "NameExpression"
1604                 },
1605                 {
1606                     "name": "Test",
1607                     "type": "NameExpression"
1608                 }
1609             ],
1610             "type": "UnionType"
1611         });
1612     });
1613
1614     it('illegal tokens', function () {
1615         doctrine.parseType.bind(doctrine, ".").should.throw('unexpected token');
1616         doctrine.parseType.bind(doctrine, ".d").should.throw('unexpected token');
1617         doctrine.parseType.bind(doctrine, "(").should.throw('unexpected token');
1618         doctrine.parseType.bind(doctrine, "Test.").should.throw('unexpected token');
1619     });
1620 });
1621
1622 describe('parseParamType', function () {
1623     it('question', function () {
1624         var type = doctrine.parseParamType("?");
1625         type.should.eql({
1626             type: 'NullableLiteral'
1627         });
1628     });
1629
1630     it('question option', function () {
1631         var type = doctrine.parseParamType("?=");
1632         type.should.eql({
1633             type: 'OptionalType',
1634             expression: {
1635                 type: 'NullableLiteral'
1636             }
1637         });
1638     });
1639
1640     it('function option parameters former', function () {
1641         var type = doctrine.parseParamType("function(?, number)");
1642         type.should.eql({
1643             type: 'FunctionType',
1644             params: [{
1645                 type: 'NullableLiteral'
1646             }, {
1647                 type: 'NameExpression',
1648                 name: 'number'
1649             }],
1650             result: null
1651         });
1652     });
1653
1654     it('function option parameters latter', function () {
1655         var type = doctrine.parseParamType("function(number, ?)");
1656         type.should.eql({
1657             type: 'FunctionType',
1658             params: [{
1659                 type: 'NameExpression',
1660                 name: 'number'
1661             }, {
1662                 type: 'NullableLiteral'
1663             }],
1664             result: null
1665         });
1666     });
1667
1668     it('function type union', function () {
1669         var type = doctrine.parseParamType("function(): ?|number");
1670         type.should.eql({
1671             type: 'UnionType',
1672             elements: [{
1673                 type: 'FunctionType',
1674                 params: [],
1675                 result: {
1676                     type: 'NullableLiteral'
1677                 }
1678             }, {
1679                 type: 'NameExpression',
1680                 name: 'number'
1681             }]
1682         });
1683     });
1684 });
1685
1686 describe('invalid', function () {
1687     it('empty union pipe', function () {
1688         doctrine.parseType.bind(doctrine, "(|)").should.throw();
1689         doctrine.parseType.bind(doctrine, "(string|)").should.throw();
1690         doctrine.parseType.bind(doctrine, "(string||)").should.throw();
1691     });
1692
1693     it('comma only array type', function () {
1694         doctrine.parseType.bind(doctrine, "[,]").should.throw();
1695     });
1696
1697     it('comma only record type', function () {
1698         doctrine.parseType.bind(doctrine, "{,,}").should.throw();
1699     });
1700
1701     it('incorrect bracket', function () {
1702         doctrine.parseParamType.bind(doctrine, "int[").should.throw();
1703     });
1704 });
1705
1706 describe('tags option', function() {
1707         it ('only param', function() {
1708         var res = doctrine.parse(
1709             [
1710                 "/**",
1711                 " * @const @const",
1712                 " * @param {String} y",
1713                 " */"
1714             ].join('\n'), { tags: ['param'], unwrap:true });
1715         res.tags.should.have.length(1);
1716         res.tags[0].should.have.property('title', 'param');
1717         res.tags[0].should.have.property('name', 'y');
1718      });
1719
1720         it ('param and type', function() {
1721         var res = doctrine.parse(
1722             [
1723                 "/**",
1724                 " * @const x",
1725                 " * @param {String} y",
1726                 " * @type {String} ",
1727                 " */"
1728             ].join('\n'), { tags: ['param', 'type'], unwrap:true });
1729         res.tags.should.have.length(2);
1730         res.tags[0].should.have.property('title', 'param');
1731         res.tags[0].should.have.property('name', 'y');
1732         res.tags[1].should.have.property('title', 'type');
1733         res.tags[1].should.have.property('type');
1734         res.tags[1].type.should.have.property('name', 'String');
1735      });
1736
1737 });
1738
1739 describe('invalid tags', function() {
1740         it ('bad tag 1', function() {
1741         doctrine.parse.bind(doctrine,
1742             [
1743                 "/**",
1744                 " * @param {String} hucairz",
1745                 " */"
1746             ].join('\n'), { tags: 1, unwrap:true }).should.throw();
1747      });
1748
1749         it ('bad tag 2', function() {
1750         doctrine.parse.bind(doctrine,
1751             [
1752                 "/**",
1753                 " * @param {String} hucairz",
1754                 " */"
1755             ].join('\n'), { tags: ['a', 1], unwrap:true }).should.throw();
1756      });
1757 });
1758
1759 describe('optional params', function() {
1760
1761     // should fail since sloppy option not set
1762     it('failure 0', function() {
1763         doctrine.parse(
1764         ["/**", " * @param {String} [val]", " */"].join('\n'), {
1765             unwrap: true
1766         }).should.eql({
1767             "description": "",
1768             "tags": []
1769         });
1770     });
1771
1772     it('failure 1', function() {
1773         doctrine.parse(
1774         ["/**", " * @param [val", " */"].join('\n'), {
1775             unwrap: true, sloppy: true
1776         }).should.eql({
1777             "description": "",
1778             "tags": []
1779         });
1780     });
1781
1782     it('success 1', function() {
1783         doctrine.parse(
1784         ["/**", " * @param {String} [val]", " */"].join('\n'), {
1785             unwrap: true, sloppy: true
1786         }).should.eql({
1787             "description": "",
1788             "tags": [{
1789                 "title": "param",
1790                 "description": null,
1791                 "type": {
1792                     "type": "OptionalType",
1793                     "expression": {
1794                         "type": "NameExpression",
1795                         "name": "String"
1796                     }
1797                 },
1798                 "name": "val"
1799             }]
1800         });
1801     });
1802     it('success 2', function() {
1803         doctrine.parse(
1804         ["/**", " * @param {String=} val", " */"].join('\n'), {
1805             unwrap: true, sloppy: true
1806         }).should.eql({
1807             "description": "",
1808             "tags": [{
1809                 "title": "param",
1810                 "description": null,
1811                 "type": {
1812                     "type": "OptionalType",
1813                     "expression": {
1814                         "type": "NameExpression",
1815                         "name": "String"
1816                     }
1817                 },
1818                 "name": "val"
1819             }]
1820         });
1821     });
1822
1823     it('success 3', function() {
1824         doctrine.parse(
1825             ["/**", " * @param {String=} [val=abc] some description", " */"].join('\n'),
1826             { unwrap: true, sloppy: true}
1827         ).should.eql({
1828             "description": "",
1829             "tags": [{
1830                 "title": "param",
1831                 "description": "some description",
1832                 "type": {
1833                     "type": "OptionalType",
1834                     "expression": {
1835                         "type": "NameExpression",
1836                         "name": "String"
1837                     }
1838                 },
1839                 "name": "val",
1840                 "default": "abc"
1841             }]
1842         });
1843     });
1844
1845     it('line numbers', function() {
1846         var res = doctrine.parse(
1847             [
1848                 "/**",
1849                 " * @param {string} foo",
1850                 " * @returns {string}",
1851                 " *",
1852                 " * @example",
1853                 " * f('blah'); // => undefined",
1854                 " */"
1855             ].join('\n'),
1856             { unwrap: true, lineNumbers: true }
1857         );
1858
1859         res.tags[0].should.have.property('lineNumber', 1);
1860         res.tags[1].should.have.property('lineNumber', 2);
1861         res.tags[2].should.have.property('lineNumber', 4);
1862     });
1863
1864     it('should handle \\r\\n line endings correctly', function() {
1865         var res = doctrine.parse(
1866             [
1867                 "/**",
1868                 " * @param {string} foo",
1869                 " * @returns {string}",
1870                 " *",
1871                 " * @example",
1872                 " * f('blah'); // => undefined",
1873                 " */"
1874             ].join('\r\n'),
1875             { unwrap: true, lineNumbers: true }
1876         );
1877
1878         res.tags[0].should.have.property('lineNumber', 1);
1879         res.tags[1].should.have.property('lineNumber', 2);
1880         res.tags[2].should.have.property('lineNumber', 4);
1881     });
1882 });
1883
1884 describe('recovery tests', function() {
1885         it ('params 2', function () {
1886                 var res = doctrine.parse(
1887             [
1888                 "@param f",
1889                 "@param {string} f2"
1890             ].join('\n'), { recoverable: true });
1891
1892          // ensure both parameters are OK
1893          res.tags.should.have.length(2);
1894          res.tags[0].should.have.property('title', 'param');
1895          res.tags[0].should.have.property('type', null);
1896          res.tags[0].should.have.property('name', 'f');
1897
1898          res.tags[1].should.have.property('title', 'param');
1899          res.tags[1].should.have.property('type');
1900          res.tags[1].type.should.have.property('name', 'string');
1901          res.tags[1].type.should.have.property('type', 'NameExpression');
1902          res.tags[1].should.have.property('name', 'f2');
1903         });
1904
1905         it ('params 2', function () {
1906                 var res = doctrine.parse(
1907             [
1908                 "@param string f",
1909                 "@param {string} f2"
1910             ].join('\n'), { recoverable: true });
1911
1912          // ensure first parameter is OK even with invalid type name
1913          res.tags.should.have.length(2);
1914          res.tags[0].should.have.property('title', 'param');
1915          res.tags[0].should.have.property('type', null);
1916          res.tags[0].should.have.property('name', 'string');
1917          res.tags[0].should.have.property('description', 'f');
1918
1919          res.tags[1].should.have.property('title', 'param');
1920          res.tags[1].should.have.property('type');
1921          res.tags[1].type.should.have.property('name', 'string');
1922          res.tags[1].type.should.have.property('type', 'NameExpression');
1923          res.tags[1].should.have.property('name', 'f2');
1924         });
1925
1926         it ('return 1', function() {
1927                 var res = doctrine.parse(
1928             [
1929                 "@returns"
1930             ].join('\n'), { recoverable: true });
1931
1932          // return tag should exist
1933          res.tags.should.have.length(1);
1934          res.tags[0].should.have.property('title', 'returns');
1935          res.tags[0].should.have.property('type', null);
1936         });
1937         it ('return 2', function() {
1938                 var res = doctrine.parse(
1939             [
1940                 "@returns",
1941                                 "@param {string} f2"
1942             ].join('\n'), { recoverable: true });
1943
1944          // return tag should exist as well as next tag
1945          res.tags.should.have.length(2);
1946          res.tags[0].should.have.property('title', 'returns');
1947          res.tags[0].should.have.property('type', null);
1948
1949          res.tags[1].should.have.property('title', 'param');
1950          res.tags[1].should.have.property('type');
1951          res.tags[1].type.should.have.property('name', 'string');
1952          res.tags[1].type.should.have.property('type', 'NameExpression');
1953          res.tags[1].should.have.property('name', 'f2');
1954         });
1955
1956         it ('extra @ 1', function() {
1957                 var res = doctrine.parse(
1958             [
1959                 "@",
1960                 "@returns",
1961                                 "@param {string} f2"
1962             ].join('\n'), { recoverable: true });
1963
1964          // empty tag name shouldn't affect subsequent tags
1965          res.tags.should.have.length(3);
1966          res.tags[0].should.have.property('title', '');
1967          res.tags[0].should.not.have.property('type');
1968
1969          res.tags[1].should.have.property('title', 'returns');
1970          res.tags[1].should.have.property('type', null);
1971
1972          res.tags[2].should.have.property('title', 'param');
1973          res.tags[2].should.have.property('type');
1974          res.tags[2].type.should.have.property('name', 'string');
1975          res.tags[2].type.should.have.property('type', 'NameExpression');
1976          res.tags[2].should.have.property('name', 'f2');
1977         });
1978
1979         it ('extra @ 2', function() {
1980                 var res = doctrine.parse(
1981             [
1982                 "@ invalid name",
1983                                 "@param {string} f2"
1984             ].join('\n'), { recoverable: true });
1985
1986          // empty tag name shouldn't affect subsequent tags
1987          res.tags.should.have.length(2);
1988          res.tags[0].should.have.property('title', '');
1989          res.tags[0].should.not.have.property('type');
1990          res.tags[0].should.not.have.property('name');
1991          res.tags[0].should.have.property('description', 'invalid name');
1992
1993          res.tags[1].should.have.property('title', 'param');
1994          res.tags[1].should.have.property('type');
1995          res.tags[1].type.should.have.property('name', 'string');
1996          res.tags[1].type.should.have.property('type', 'NameExpression');
1997          res.tags[1].should.have.property('name', 'f2');
1998         });
1999
2000         it ('invalid tag 1', function() {
2001                 var res = doctrine.parse(
2002             [
2003                 "@111 invalid name",
2004                                 "@param {string} f2"
2005             ].join('\n'), { recoverable: true });
2006
2007          // invalid tag name shouldn't affect subsequent tags
2008          res.tags.should.have.length(2);
2009          res.tags[0].should.have.property('title', '111');
2010          res.tags[0].should.not.have.property('type');
2011          res.tags[0].should.not.have.property('name');
2012          res.tags[0].should.have.property('description', 'invalid name');
2013
2014          res.tags[1].should.have.property('title', 'param');
2015          res.tags[1].should.have.property('type');
2016          res.tags[1].type.should.have.property('name', 'string');
2017          res.tags[1].type.should.have.property('type', 'NameExpression');
2018          res.tags[1].should.have.property('name', 'f2');
2019         });
2020
2021         it ('invalid tag 1', function() {
2022                 var res = doctrine.parse(
2023             [
2024                 "@111",
2025                                 "@param {string} f2"
2026             ].join('\n'), { recoverable: true });
2027
2028          // invalid tag name shouldn't affect subsequent tags
2029          res.tags.should.have.length(2);
2030          res.tags[0].should.have.property('title', '111');
2031          res.tags[0].should.not.have.property('type');
2032          res.tags[0].should.not.have.property('name');
2033          res.tags[0].should.have.property('description', null);
2034
2035          res.tags[1].should.have.property('title', 'param');
2036          res.tags[1].should.have.property('type');
2037          res.tags[1].type.should.have.property('name', 'string');
2038          res.tags[1].type.should.have.property('type', 'NameExpression');
2039          res.tags[1].should.have.property('name', 'f2');
2040         });
2041
2042         it ('should not crash on bad type in @param without name', function() {
2043                 var res = doctrine.parse("@param {Function(DOMNode)}", { recoverable: true });
2044         res.tags.should.have.length(1);
2045         res.tags[0].should.eql({
2046             "description": null,
2047             "errors": [
2048                 "not reach to EOF",
2049                 "Missing or invalid tag name"
2050             ],
2051             "name": null,
2052             "title": "param",
2053             "type": null
2054         });
2055     });
2056
2057         it ('should not crash on bad type in @param in sloppy mode', function() {
2058                 var res = doctrine.parse("@param {int[} [x]", { sloppy: true, recoverable: true });
2059         res.tags.should.have.length(1);
2060         res.tags[0].should.eql({
2061             "description": null,
2062             "errors": [
2063                 "expected an array-style type declaration (int[])"
2064             ],
2065             "name": "x",
2066             "title": "param",
2067             "type": null
2068         });
2069     });
2070 });
2071
2072 describe('exported Syntax', function() {
2073         it ('members', function () {
2074         doctrine.Syntax.should.eql({
2075             NullableLiteral: 'NullableLiteral',
2076             AllLiteral: 'AllLiteral',
2077             NullLiteral: 'NullLiteral',
2078             UndefinedLiteral: 'UndefinedLiteral',
2079             VoidLiteral: 'VoidLiteral',
2080             UnionType: 'UnionType',
2081             ArrayType: 'ArrayType',
2082             RecordType: 'RecordType',
2083             FieldType: 'FieldType',
2084             FunctionType: 'FunctionType',
2085             ParameterType: 'ParameterType',
2086             RestType: 'RestType',
2087             NonNullableType: 'NonNullableType',
2088             OptionalType: 'OptionalType',
2089             NullableType: 'NullableType',
2090             NameExpression: 'NameExpression',
2091             TypeApplication: 'TypeApplication'
2092         });
2093     });
2094 });
2095
2096 describe('@ mark contained descriptions', function () {
2097     it ('comment description #10', function () {
2098         doctrine.parse(
2099             [
2100                 '/**',
2101                 ' * Prevents the default action. It is equivalent to',
2102                 ' * {@code e.preventDefault()}, but can be used as the callback argument of',
2103                 ' * {@link goog.events.listen} without declaring another function.',
2104                 ' * @param {!goog.events.Event} e An event.',
2105                 ' */'
2106             ].join('\n'),
2107             { unwrap: true, sloppy: true }).should.eql({
2108             'description': 'Prevents the default action. It is equivalent to\n{@code e.preventDefault()}, but can be used as the callback argument of\n{@link goog.events.listen} without declaring another function.',
2109             'tags': [{
2110                 'title': 'param',
2111                 'description': 'An event.',
2112                 'type': {
2113                     'type': 'NonNullableType',
2114                     'expression': {
2115                         'type': 'NameExpression',
2116                         'name': 'goog.events.Event'
2117                     },
2118                     'prefix': true
2119                 },
2120                 'name': 'e'
2121             }]
2122         });
2123     });
2124
2125     it ('tag description', function () {
2126         doctrine.parse(
2127             [
2128                 '/**',
2129                 ' * Prevents the default action. It is equivalent to',
2130                 ' * @param {!goog.events.Event} e An event.',
2131                 ' * {@code e.preventDefault()}, but can be used as the callback argument of',
2132                 ' * {@link goog.events.listen} without declaring another function.',
2133                 ' */'
2134             ].join('\n'),
2135             { unwrap: true, sloppy: true }).should.eql({
2136             'description': 'Prevents the default action. It is equivalent to',
2137             'tags': [{
2138                 'title': 'param',
2139                 'description': 'An event.\n{@code e.preventDefault()}, but can be used as the callback argument of\n{@link goog.events.listen} without declaring another function.',
2140                 'type': {
2141                     'type': 'NonNullableType',
2142                     'expression': {
2143                         'type': 'NameExpression',
2144                         'name': 'goog.events.Event'
2145                     },
2146                     'prefix': true
2147                 },
2148                 'name': 'e'
2149             }]
2150         });
2151     });
2152 });
2153
2154 describe('function', function () {
2155     it ('recognize "function" type', function () {
2156                 var res = doctrine.parse(
2157             [
2158                 "@param {function} foo description",
2159             ].join('\n'), {});
2160          res.tags.should.have.length(1);
2161          res.tags[0].should.have.property('title', 'param');
2162          res.tags[0].should.have.property('type');
2163          res.tags[0].type.should.eql({
2164              "name": "function",
2165              "type": "NameExpression"
2166          });
2167          res.tags[0].should.have.property('name', 'foo');
2168          res.tags[0].should.have.property('description', 'description');
2169     });
2170 });
2171
2172 describe('tagged namepaths', function () {
2173     it ('recognize module:', function () {
2174         var res = doctrine.parse(
2175             [
2176                 "@alias module:Foo.bar"
2177             ].join('\n'), {});
2178         res.tags.should.have.length(1);
2179         res.tags[0].should.have.property('title', 'alias');
2180         res.tags[0].should.have.property('name', 'module:Foo.bar');
2181         res.tags[0].should.have.property('description', null);
2182     });
2183
2184     it ('recognize external:', function () {
2185         var res = doctrine.parse(
2186             [
2187                 "@param {external:Foo.bar} baz description"
2188             ].join('\n'), {});
2189         res.tags.should.have.length(1);
2190         res.tags[0].should.have.property('title', 'param');
2191          res.tags[0].type.should.eql({
2192              "name": "external:Foo.bar",
2193              "type": "NameExpression"
2194          });
2195         res.tags[0].should.have.property('name', 'baz');
2196         res.tags[0].should.have.property('description', 'description');
2197     });
2198
2199     it ('recognize event:', function () {
2200         var res = doctrine.parse(
2201             [
2202                 "@function event:Foo.bar"
2203             ].join('\n'), {});
2204         res.tags.should.have.length(1);
2205         res.tags[0].should.have.property('title', 'function');
2206         res.tags[0].should.have.property('name', 'event:Foo.bar');
2207         res.tags[0].should.have.property('description', null);
2208     });
2209
2210     it ('invalid bogus:', function () {
2211         var res = doctrine.parse(
2212             [
2213                 "@method bogus:Foo.bar"
2214             ].join('\n'), {});
2215         res.tags.should.have.length(0);
2216     });
2217 });
2218
2219 /* vim: set sw=4 ts=4 et tw=80 : */