• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts {
2    describe("unittests:: JSDocParsing", () => {
3        describe("TypeExpressions", () => {
4            function parsesCorrectly(name: string, content: string) {
5                it(name, () => {
6                    const typeAndDiagnostics = parseJSDocTypeExpressionForTests(content);
7                    assert.isTrue(typeAndDiagnostics && typeAndDiagnostics.diagnostics.length === 0, "no errors issued");
8
9                    Harness.Baseline.runBaseline("JSDocParsing/TypeExpressions.parsesCorrectly." + name + ".json",
10                        Utils.sourceFileToJSON(typeAndDiagnostics!.jsDocTypeExpression.type));
11                });
12            }
13
14            function parsesIncorrectly(name: string, content: string) {
15                it(name, () => {
16                    const type = parseJSDocTypeExpressionForTests(content);
17                    assert.isTrue(!type || type.diagnostics.length > 0);
18                });
19            }
20
21            describe("parseCorrectly", () => {
22                parsesCorrectly("unknownType", "{?}");
23                parsesCorrectly("allType", "{*}");
24                parsesCorrectly("nullableType", "{?number}");
25                parsesCorrectly("nullableType2", "{number?}");
26                parsesCorrectly("nonNullableType", "{!number}");
27                parsesCorrectly("nonNullableType2", "{number!}");
28                parsesCorrectly("recordType1", "{{}}");
29                parsesCorrectly("recordType2", "{{foo}}");
30                parsesCorrectly("recordType3", "{{foo: number}}");
31                parsesCorrectly("recordType4", "{{foo, bar}}");
32                parsesCorrectly("recordType5", "{{foo: number, bar}}");
33                parsesCorrectly("recordType6", "{{foo, bar: number}}");
34                parsesCorrectly("recordType7", "{{foo: number, bar: number}}");
35                parsesCorrectly("recordType8", "{{function}}");
36                parsesCorrectly("trailingCommaInRecordType", "{{a,}}");
37                parsesCorrectly("callSignatureInRecordType", "{{(): number}}");
38                parsesCorrectly("methodInRecordType", "{{foo(): number}}");
39                parsesCorrectly("unionType", "{(number|string)}");
40                parsesCorrectly("unionTypeWithLeadingOperator", "{( | number | string )}");
41                parsesCorrectly("unionTypeWithOneElementAndLeadingOperator", "{( | number )}");
42                parsesCorrectly("topLevelNoParenUnionType", "{number|string}");
43                parsesCorrectly("functionType1", "{function()}");
44                parsesCorrectly("functionType2", "{function(string, boolean)}");
45                parsesCorrectly("functionReturnType1", "{function(string, boolean)}");
46                parsesCorrectly("thisType1", "{function(this:a.b)}");
47                parsesCorrectly("newType1", "{function(new:a.b)}");
48                parsesCorrectly("variadicType", "{...number}");
49                parsesCorrectly("optionalType", "{number=}");
50                parsesCorrectly("optionalNullable", "{?=}");
51                parsesCorrectly("typeReference1", "{a.<number>}");
52                parsesCorrectly("typeReference2", "{a.<number,string>}");
53                parsesCorrectly("typeReference3", "{a.function}");
54                parsesCorrectly("arrayType1", "{a[]}");
55                parsesCorrectly("arrayType2", "{a[][]}");
56                parsesCorrectly("arrayType3", "{(a[][])=}");
57                parsesCorrectly("keyword1", "{var}");
58                parsesCorrectly("keyword2", "{null}");
59                parsesCorrectly("keyword3", "{undefined}");
60                parsesCorrectly("tupleType0", "{[]}");
61                parsesCorrectly("tupleType1", "{[number]}");
62                parsesCorrectly("tupleType2", "{[number,string]}");
63                parsesCorrectly("tupleType3", "{[number,string,boolean]}");
64                parsesCorrectly("tupleTypeWithTrailingComma", "{[number,]}");
65                parsesCorrectly("typeOfType", "{typeof M}");
66                parsesCorrectly("tsConstructorType", "{new () => string}");
67                parsesCorrectly("tsFunctionType", "{() => string}");
68                parsesCorrectly("typeArgumentsNotFollowingDot", "{a<>}");
69                parsesCorrectly("functionTypeWithTrailingComma", "{function(a,)}");
70            });
71
72            describe("parsesIncorrectly", () => {
73                parsesIncorrectly("emptyType", "{}");
74                parsesIncorrectly("unionTypeWithTrailingBar", "{(a|)}");
75                parsesIncorrectly("unionTypeWithoutTypes", "{()}");
76                parsesIncorrectly("nullableTypeWithoutType", "{!}");
77                parsesIncorrectly("thisWithoutType", "{this:}");
78                parsesIncorrectly("newWithoutType", "{new:}");
79                parsesIncorrectly("variadicWithoutType", "{...}");
80                parsesIncorrectly("optionalWithoutType", "{=}");
81                parsesIncorrectly("allWithType", "{*foo}");
82                parsesIncorrectly("namedParameter", "{function(a: number)}");
83                parsesIncorrectly("tupleTypeWithComma", "{[,]}");
84                parsesIncorrectly("tupleTypeWithLeadingComma", "{[,number]}");
85            });
86        });
87
88        describe("DocComments", () => {
89            function parsesCorrectly(name: string, content: string) {
90                it(name, () => {
91                    const comment = parseIsolatedJSDocComment(content)!;
92                    if (!comment) {
93                        Debug.fail("Comment failed to parse entirely");
94                    }
95                    if (comment.diagnostics.length > 0) {
96                        Debug.fail("Comment has at least one diagnostic: " + comment.diagnostics[0].messageText);
97                    }
98
99                    Harness.Baseline.runBaseline("JSDocParsing/DocComments.parsesCorrectly." + name + ".json",
100                        JSON.stringify(comment.jsDoc,
101                            (_, v) => v && v.pos !== undefined ? JSON.parse(Utils.sourceFileToJSON(v)) : v, 4));
102                });
103            }
104
105            function parsesIncorrectly(name: string, content: string) {
106                it(name, () => {
107                    const type = parseIsolatedJSDocComment(content);
108                    assert.isTrue(!type || type.diagnostics.length > 0);
109                });
110            }
111
112            describe("parsesIncorrectly", () => {
113                parsesIncorrectly("multipleTypes",
114                    `/**
115  * @type {number}
116  * @type {string}
117  */`);
118                parsesIncorrectly("multipleReturnTypes",
119                    `/**
120  * @return {number}
121  * @return {string}
122  */`);
123                parsesIncorrectly("noTypeParameters",
124                    `/**
125  * @template
126  */`);
127                parsesIncorrectly("trailingTypeParameterComma",
128                    `/**
129  * @template T,
130  */`);
131                parsesIncorrectly("paramWithoutName",
132                    `/**
133  * @param {number}
134  */`);
135                parsesIncorrectly("paramWithoutTypeOrName",
136                    `/**
137  * @param
138  */`);
139
140                parsesIncorrectly("noType",
141                    `/**
142* @type
143*/`);
144
145                parsesIncorrectly("@augments with no type",
146                    `/**
147 * @augments
148 */`);
149            });
150
151            describe("parsesCorrectly", () => {
152                parsesCorrectly("threeAsterisks", "/*** */");
153                parsesCorrectly("emptyComment", "/***/");
154                parsesCorrectly("noLeadingAsterisk",
155                    `/**
156    @type {number}
157  */`);
158
159
160                parsesCorrectly("noReturnType",
161                    `/**
162  * @return
163  */`);
164
165                parsesCorrectly("leadingAsterisk",
166                    `/**
167  * @type {number}
168  */`);
169
170                parsesCorrectly("asteriskAfterPreamble", "/** * @type {number} */");
171
172                parsesCorrectly("typeTag",
173                    `/**
174  * @type {number}
175  */`);
176
177
178                parsesCorrectly("returnTag1",
179                    `/**
180  * @return {number}
181  */`);
182
183
184                parsesCorrectly("returnTag2",
185                    `/**
186  * @return {number} Description text follows
187  */`);
188
189
190                parsesCorrectly("returnsTag1",
191                    `/**
192  * @returns {number}
193  */`);
194
195
196                parsesCorrectly("oneParamTag",
197                    `/**
198  * @param {number} name1
199  */`);
200
201
202                parsesCorrectly("twoParamTag2",
203                    `/**
204  * @param {number} name1
205  * @param {number} name2
206  */`);
207
208
209                parsesCorrectly("paramTag1",
210                    `/**
211  * @param {number} name1 Description text follows
212  */`);
213
214
215                parsesCorrectly("paramTagBracketedName1",
216                    `/**
217  * @param {number} [name1] Description text follows
218  */`);
219
220
221                parsesCorrectly("paramTagBracketedName2",
222                    `/**
223  * @param {number} [ name1 = 1] Description text follows
224  */`);
225
226
227                parsesCorrectly("twoParamTagOnSameLine",
228                    `/**
229  * @param {number} name1 @param {number} name2
230  */`);
231
232
233                parsesCorrectly("paramTagNameThenType1",
234                    `/**
235  * @param name1 {number}
236  */`);
237
238
239                parsesCorrectly("paramTagNameThenType2",
240                    `/**
241  * @param name1 {number} Description
242  */`);
243
244
245                parsesCorrectly("argSynonymForParamTag",
246                    `/**
247  * @arg {number} name1 Description
248  */`);
249
250
251                parsesCorrectly("argumentSynonymForParamTag",
252                    `/**
253  * @argument {number} name1 Description
254  */`);
255
256
257                parsesCorrectly("templateTag",
258                    `/**
259  * @template T
260  */`);
261
262
263                parsesCorrectly("templateTag2",
264                    `/**
265  * @template K,V
266  */`);
267
268
269                parsesCorrectly("templateTag3",
270                    `/**
271  * @template K ,V
272  */`);
273
274
275                parsesCorrectly("templateTag4",
276                    `/**
277  * @template K, V
278  */`);
279
280
281                parsesCorrectly("templateTag5",
282                    `/**
283  * @template K , V
284  */`);
285
286                parsesCorrectly("templateTag6",
287                    `/**
288  * @template K , V Description of type parameters.
289  */`);
290
291                parsesCorrectly("paramWithoutType",
292                    `/**
293  * @param foo
294  */`);
295                parsesCorrectly("typedefTagWithChildrenTags",
296                    `/**
297  * @typedef People
298  * @type {Object}
299  * @property {number} age
300  * @property {string} name
301  */`);
302                parsesCorrectly("less-than and greater-than characters",
303                    `/**
304 * @param x hi
305< > still part of the previous comment
306 */`);
307
308                parsesCorrectly("Nested @param tags",
309                    `/**
310* @param {object} o Doc doc
311* @param {string} o.f Doc for f
312*/`);
313                parsesCorrectly("@link tags",
314                    `/**
315 * {@link first link}
316 * Inside {@link link text} thing
317 */`);
318                parsesCorrectly("authorTag",
319                    `/**
320 * @author John Doe <john.doe@example.com>
321 * @author John Doe <john.doe@example.com> unexpected comment
322 * @author 108 <108@actionbutton.net> Video Games Forever
323 * @author Multiple Ats <email@quoting@how@does@it@work>
324 * @author Multiple Open Carets <hi<there@<>
325 * @author Multiple Close Carets <probably>invalid>but>who>cares>
326 * @author Unclosed Carets <joe@sloppy.gov
327 * @author Multiple @author On One <one@two.three> @author Line
328 * @author @author @author Empty authors
329 * @author
330 * @author
331 *   Comments
332 * @author Early Close Caret > <a@b>
333 * @author No Line Breaks:
334 *   <the email @address> must be on the same line to parse
335 * @author Long Comment <long@comment.org> I
336 *  want to keep commenting down here, I dunno.
337 */`);
338
339                parsesCorrectly("consecutive newline tokens",
340                    `/**
341 * @example
342 * Some\n\n * text\r\n * with newlines.
343 */`);
344                parsesCorrectly("Chained tags, no leading whitespace", `/**@a @b @c@d*/`);
345                parsesCorrectly("Initial star is not a tag", `/***@a*/`);
346                parsesCorrectly("Initial star space is not a tag", `/*** @a*/`);
347                parsesCorrectly("Initial email address is not a tag", `/**bill@example.com*/`);
348                parsesCorrectly("no space before @ is not a new tag",
349                    `/**
350 * @param this (@is@)
351 * @param fine its@fine
352@zerowidth
353*@singlestar
354**@doublestar
355 */`);
356                parsesCorrectly("@@ does not start a new tag",
357                    `/**
358 * @param this is (@@fine@@and) is one comment
359 */`);
360            });
361        });
362        describe("getFirstToken", () => {
363            it("gets jsdoc", () => {
364                const root = createSourceFile("foo.ts", "/** comment */var a = true;", ScriptTarget.ES5, /*setParentNodes*/ true);
365                assert.isDefined(root);
366                assert.equal(root.kind, SyntaxKind.SourceFile);
367                const first = root.getFirstToken();
368                assert.isDefined(first);
369                assert.equal(first!.kind, SyntaxKind.VarKeyword);
370            });
371        });
372        describe("getLastToken", () => {
373            it("gets jsdoc", () => {
374                const root = createSourceFile("foo.ts", "var a = true;/** comment */", ScriptTarget.ES5, /*setParentNodes*/ true);
375                assert.isDefined(root);
376                const last = root.getLastToken();
377                assert.isDefined(last);
378                assert.equal(last!.kind, SyntaxKind.EndOfFileToken);
379            });
380        });
381        describe("getStart of node with JSDoc but no parent pointers", () => {
382            const root = createSourceFile("foo.ts", "/** */var a = true;", ScriptTarget.ES5, /*setParentNodes*/ false);
383            root.statements[0].getStart(root, /*includeJsdocComment*/ true);
384        });
385    });
386}
387