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 } 316 * Inside {@link link text} thing 317 * @param foo See also {@link A.Reference} 318 * @param bar Or see {@link http://www.zombocom.com } 319 * {@link Standalone.Complex } 320 * This empty one: {@link} is OK. 321 * This double-space one: {@link doubled } is OK too. 322 * This should work, despite being badly formatted: {@link 323oh.no 324} 325 * Forgot to close this one {@link https://typescriptlang.org 326 * But it's still OK. 327 * Although it skips the newline so parses the asterisks in the wrong state. 328 * This shouldn't work: {@link 329 * nope 330 * }, because of the intermediate asterisks. 331 * @author Alfa Romero <a@parsing.com> See my home page: {@link https://example.com} 332 */`); 333 parsesCorrectly("authorTag", 334 `/** 335 * @author John Doe <john.doe@example.com> 336 * @author John Doe <john.doe@example.com> unexpected comment 337 * @author 108 <108@actionbutton.net> Video Games Forever 338 * @author Multiple Ats <email@quoting@how@does@it@work> 339 * @author Multiple Open Carets <hi<there@<> 340 * @author Multiple Close Carets <probably>invalid>but>who>cares> 341 * @author Unclosed Carets <joe@sloppy.gov 342 * @author Multiple @author On One <one@two.three> @author Line 343 * @author @author @author Empty authors 344 * @author 345 * @author 346 * Comments 347 * @author Early Close Caret > <a@b> 348 * @author No Line Breaks: 349 * <the email @address> must be on the same line to parse 350 * @author Long Comment <long@comment.org> I 351 * want to keep commenting down here, I dunno. 352 */`); 353 354 parsesCorrectly("consecutive newline tokens", 355 `/** 356 * @example 357 * Some\n\n * text\r\n * with newlines. 358 */`); 359 parsesCorrectly("Chained tags, no leading whitespace", `/**@a @b @c@d*/`); 360 parsesCorrectly("Initial star is not a tag", `/***@a*/`); 361 parsesCorrectly("Initial star space is not a tag", `/*** @a*/`); 362 parsesCorrectly("Initial email address is not a tag", `/**bill@example.com*/`); 363 parsesCorrectly("no space before @ is not a new tag", 364 `/** 365 * @param this (@is@) 366 * @param fine its@fine 367@zerowidth 368*@singlestar 369**@doublestar 370 */`); 371 parsesCorrectly("@@ does not start a new tag", 372 `/** 373 * @param this is (@@fine@@and) is one comment 374 */`); 375 }); 376 }); 377 describe("getFirstToken", () => { 378 it("gets jsdoc", () => { 379 const root = createSourceFile("foo.ts", "/** comment */var a = true;", ScriptTarget.ES5, /*setParentNodes*/ true); 380 assert.isDefined(root); 381 assert.equal(root.kind, SyntaxKind.SourceFile); 382 const first = root.getFirstToken(); 383 assert.isDefined(first); 384 assert.equal(first!.kind, SyntaxKind.VarKeyword); 385 }); 386 }); 387 describe("getLastToken", () => { 388 it("gets jsdoc", () => { 389 const root = createSourceFile("foo.ts", "var a = true;/** comment */", ScriptTarget.ES5, /*setParentNodes*/ true); 390 assert.isDefined(root); 391 const last = root.getLastToken(); 392 assert.isDefined(last); 393 assert.equal(last!.kind, SyntaxKind.EndOfFileToken); 394 }); 395 }); 396 describe("getStart", () => { 397 it("runs when node with JSDoc but no parent pointers", () => { 398 const root = createSourceFile("foo.ts", "/** */var a = true;", ScriptTarget.ES5, /*setParentNodes*/ false); 399 root.statements[0].getStart(root, /*includeJsdocComment*/ true); 400 }); 401 }); 402 describe("parseIsolatedJSDocComment", () => { 403 it("doesn't create a 1-element array with missing type parameter in jsDoc", () => { 404 const doc = parseIsolatedJSDocComment("/**\n @template\n*/"); 405 assert.equal((doc?.jsDoc.tags?.[0] as JSDocTemplateTag).typeParameters.length, 0); 406 }); 407 }); 408 }); 409} 410