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