namespace ts {
    describe("unittests:: JSDocParsing", () => {
        describe("TypeExpressions", () => {
            function parsesCorrectly(name: string, content: string) {
                it(name, () => {
                    const typeAndDiagnostics = parseJSDocTypeExpressionForTests(content);
                    assert.isTrue(typeAndDiagnostics && typeAndDiagnostics.diagnostics.length === 0, "no errors issued");

                    Harness.Baseline.runBaseline("JSDocParsing/TypeExpressions.parsesCorrectly." + name + ".json",
                        Utils.sourceFileToJSON(typeAndDiagnostics!.jsDocTypeExpression.type));
                });
            }

            function parsesIncorrectly(name: string, content: string) {
                it(name, () => {
                    const type = parseJSDocTypeExpressionForTests(content);
                    assert.isTrue(!type || type.diagnostics.length > 0);
                });
            }

            describe("parseCorrectly", () => {
                parsesCorrectly("unknownType", "{?}");
                parsesCorrectly("allType", "{*}");
                parsesCorrectly("nullableType", "{?number}");
                parsesCorrectly("nullableType2", "{number?}");
                parsesCorrectly("nonNullableType", "{!number}");
                parsesCorrectly("nonNullableType2", "{number!}");
                parsesCorrectly("recordType1", "{{}}");
                parsesCorrectly("recordType2", "{{foo}}");
                parsesCorrectly("recordType3", "{{foo: number}}");
                parsesCorrectly("recordType4", "{{foo, bar}}");
                parsesCorrectly("recordType5", "{{foo: number, bar}}");
                parsesCorrectly("recordType6", "{{foo, bar: number}}");
                parsesCorrectly("recordType7", "{{foo: number, bar: number}}");
                parsesCorrectly("recordType8", "{{function}}");
                parsesCorrectly("trailingCommaInRecordType", "{{a,}}");
                parsesCorrectly("callSignatureInRecordType", "{{(): number}}");
                parsesCorrectly("methodInRecordType", "{{foo(): number}}");
                parsesCorrectly("unionType", "{(number|string)}");
                parsesCorrectly("unionTypeWithLeadingOperator", "{( | number | string )}");
                parsesCorrectly("unionTypeWithOneElementAndLeadingOperator", "{( | number )}");
                parsesCorrectly("topLevelNoParenUnionType", "{number|string}");
                parsesCorrectly("functionType1", "{function()}");
                parsesCorrectly("functionType2", "{function(string, boolean)}");
                parsesCorrectly("functionReturnType1", "{function(string, boolean)}");
                parsesCorrectly("thisType1", "{function(this:a.b)}");
                parsesCorrectly("newType1", "{function(new:a.b)}");
                parsesCorrectly("variadicType", "{...number}");
                parsesCorrectly("optionalType", "{number=}");
                parsesCorrectly("optionalNullable", "{?=}");
                parsesCorrectly("typeReference1", "{a.<number>}");
                parsesCorrectly("typeReference2", "{a.<number,string>}");
                parsesCorrectly("typeReference3", "{a.function}");
                parsesCorrectly("arrayType1", "{a[]}");
                parsesCorrectly("arrayType2", "{a[][]}");
                parsesCorrectly("arrayType3", "{(a[][])=}");
                parsesCorrectly("keyword1", "{var}");
                parsesCorrectly("keyword2", "{null}");
                parsesCorrectly("keyword3", "{undefined}");
                parsesCorrectly("tupleType0", "{[]}");
                parsesCorrectly("tupleType1", "{[number]}");
                parsesCorrectly("tupleType2", "{[number,string]}");
                parsesCorrectly("tupleType3", "{[number,string,boolean]}");
                parsesCorrectly("tupleTypeWithTrailingComma", "{[number,]}");
                parsesCorrectly("typeOfType", "{typeof M}");
                parsesCorrectly("tsConstructorType", "{new () => string}");
                parsesCorrectly("tsFunctionType", "{() => string}");
                parsesCorrectly("typeArgumentsNotFollowingDot", "{a<>}");
                parsesCorrectly("functionTypeWithTrailingComma", "{function(a,)}");
            });

            describe("parsesIncorrectly", () => {
                parsesIncorrectly("emptyType", "{}");
                parsesIncorrectly("unionTypeWithTrailingBar", "{(a|)}");
                parsesIncorrectly("unionTypeWithoutTypes", "{()}");
                parsesIncorrectly("nullableTypeWithoutType", "{!}");
                parsesIncorrectly("thisWithoutType", "{this:}");
                parsesIncorrectly("newWithoutType", "{new:}");
                parsesIncorrectly("variadicWithoutType", "{...}");
                parsesIncorrectly("optionalWithoutType", "{=}");
                parsesIncorrectly("allWithType", "{*foo}");
                parsesIncorrectly("namedParameter", "{function(a: number)}");
                parsesIncorrectly("tupleTypeWithComma", "{[,]}");
                parsesIncorrectly("tupleTypeWithLeadingComma", "{[,number]}");
            });
        });

        describe("DocComments", () => {
            function parsesCorrectly(name: string, content: string) {
                it(name, () => {
                    const comment = parseIsolatedJSDocComment(content)!;
                    if (!comment) {
                        Debug.fail("Comment failed to parse entirely");
                    }
                    if (comment.diagnostics.length > 0) {
                        Debug.fail("Comment has at least one diagnostic: " + comment.diagnostics[0].messageText);
                    }

                    Harness.Baseline.runBaseline("JSDocParsing/DocComments.parsesCorrectly." + name + ".json",
                        JSON.stringify(comment.jsDoc,
                            (_, v) => v && v.pos !== undefined ? JSON.parse(Utils.sourceFileToJSON(v)) : v, 4));
                });
            }

            function parsesIncorrectly(name: string, content: string) {
                it(name, () => {
                    const type = parseIsolatedJSDocComment(content);
                    assert.isTrue(!type || type.diagnostics.length > 0);
                });
            }

            describe("parsesIncorrectly", () => {
                parsesIncorrectly("multipleTypes",
                    `/**
  * @type {number}
  * @type {string}
  */`);
                parsesIncorrectly("multipleReturnTypes",
                    `/**
  * @return {number}
  * @return {string}
  */`);
                parsesIncorrectly("noTypeParameters",
                    `/**
  * @template
  */`);
                parsesIncorrectly("trailingTypeParameterComma",
                    `/**
  * @template T,
  */`);
                parsesIncorrectly("paramWithoutName",
                    `/**
  * @param {number}
  */`);
                parsesIncorrectly("paramWithoutTypeOrName",
                    `/**
  * @param
  */`);

                parsesIncorrectly("noType",
                    `/**
* @type
*/`);

                parsesIncorrectly("@augments with no type",
                    `/**
 * @augments
 */`);
            });

            describe("parsesCorrectly", () => {
                parsesCorrectly("threeAsterisks", "/*** */");
                parsesCorrectly("emptyComment", "/***/");
                parsesCorrectly("noLeadingAsterisk",
                    `/**
    @type {number}
  */`);


                parsesCorrectly("noReturnType",
                    `/**
  * @return
  */`);

                parsesCorrectly("leadingAsterisk",
                    `/**
  * @type {number}
  */`);

                parsesCorrectly("asteriskAfterPreamble", "/** * @type {number} */");

                parsesCorrectly("typeTag",
                    `/**
  * @type {number}
  */`);


                parsesCorrectly("returnTag1",
                    `/**
  * @return {number}
  */`);


                parsesCorrectly("returnTag2",
                    `/**
  * @return {number} Description text follows
  */`);


                parsesCorrectly("returnsTag1",
                    `/**
  * @returns {number}
  */`);


                parsesCorrectly("oneParamTag",
                    `/**
  * @param {number} name1
  */`);


                parsesCorrectly("twoParamTag2",
                    `/**
  * @param {number} name1
  * @param {number} name2
  */`);


                parsesCorrectly("paramTag1",
                    `/**
  * @param {number} name1 Description text follows
  */`);


                parsesCorrectly("paramTagBracketedName1",
                    `/**
  * @param {number} [name1] Description text follows
  */`);


                parsesCorrectly("paramTagBracketedName2",
                    `/**
  * @param {number} [ name1 = 1] Description text follows
  */`);


                parsesCorrectly("twoParamTagOnSameLine",
                    `/**
  * @param {number} name1 @param {number} name2
  */`);


                parsesCorrectly("paramTagNameThenType1",
                    `/**
  * @param name1 {number}
  */`);


                parsesCorrectly("paramTagNameThenType2",
                    `/**
  * @param name1 {number} Description
  */`);


                parsesCorrectly("argSynonymForParamTag",
                    `/**
  * @arg {number} name1 Description
  */`);


                parsesCorrectly("argumentSynonymForParamTag",
                    `/**
  * @argument {number} name1 Description
  */`);


                parsesCorrectly("templateTag",
                    `/**
  * @template T
  */`);


                parsesCorrectly("templateTag2",
                    `/**
  * @template K,V
  */`);


                parsesCorrectly("templateTag3",
                    `/**
  * @template K ,V
  */`);


                parsesCorrectly("templateTag4",
                    `/**
  * @template K, V
  */`);


                parsesCorrectly("templateTag5",
                    `/**
  * @template K , V
  */`);

                parsesCorrectly("templateTag6",
                    `/**
  * @template K , V Description of type parameters.
  */`);

                parsesCorrectly("paramWithoutType",
                    `/**
  * @param foo
  */`);
                parsesCorrectly("typedefTagWithChildrenTags",
                    `/**
  * @typedef People
  * @type {Object}
  * @property {number} age
  * @property {string} name
  */`);
                parsesCorrectly("less-than and greater-than characters",
                    `/**
 * @param x hi
< > still part of the previous comment
 */`);

                parsesCorrectly("Nested @param tags",
                    `/**
* @param {object} o Doc doc
* @param {string} o.f Doc for f
*/`);
                parsesCorrectly("@link tags",
                    `/**
 * {@link first }
 * Inside {@link link text} thing
 * @param foo See also {@link A.Reference}
 * @param bar Or see {@link http://www.zombocom.com }
 * {@link Standalone.Complex }
 * This empty one: {@link} is OK.
 * This double-space one: {@link  doubled  } is OK too.
 * This should work, despite being badly formatted: {@link
oh.no
}
 * Forgot to close this one {@link https://typescriptlang.org
 * But it's still OK.
 * Although it skips the newline so parses the asterisks in the wrong state.
 * This shouldn't work: {@link
 * nope
 * }, because of the intermediate asterisks.
 * @author Alfa Romero <a@parsing.com> See my home page: {@link https://example.com}
 */`);
                parsesCorrectly("authorTag",
                    `/**
 * @author John Doe <john.doe@example.com>
 * @author John Doe <john.doe@example.com> unexpected comment
 * @author 108 <108@actionbutton.net> Video Games Forever
 * @author Multiple Ats <email@quoting@how@does@it@work>
 * @author Multiple Open Carets <hi<there@<>
 * @author Multiple Close Carets <probably>invalid>but>who>cares>
 * @author Unclosed Carets <joe@sloppy.gov
 * @author Multiple @author On One <one@two.three> @author Line
 * @author @author @author Empty authors
 * @author
 * @author
 *   Comments
 * @author Early Close Caret > <a@b>
 * @author No Line Breaks:
 *   <the email @address> must be on the same line to parse
 * @author Long Comment <long@comment.org> I
 *  want to keep commenting down here, I dunno.
 */`);

                parsesCorrectly("consecutive newline tokens",
                    `/**
 * @example
 * Some\n\n * text\r\n * with newlines.
 */`);
                parsesCorrectly("Chained tags, no leading whitespace", `/**@a @b @c@d*/`);
                parsesCorrectly("Initial star is not a tag", `/***@a*/`);
                parsesCorrectly("Initial star space is not a tag", `/*** @a*/`);
                parsesCorrectly("Initial email address is not a tag", `/**bill@example.com*/`);
                parsesCorrectly("no space before @ is not a new tag",
                    `/**
 * @param this (@is@)
 * @param fine its@fine
@zerowidth
*@singlestar
**@doublestar
 */`);
                parsesCorrectly("@@ does not start a new tag",
                    `/**
 * @param this is (@@fine@@and) is one comment
 */`);
            });
        });
        describe("getFirstToken", () => {
            it("gets jsdoc", () => {
                const root = createSourceFile("foo.ts", "/** comment */var a = true;", ScriptTarget.ES5, /*setParentNodes*/ true);
                assert.isDefined(root);
                assert.equal(root.kind, SyntaxKind.SourceFile);
                const first = root.getFirstToken();
                assert.isDefined(first);
                assert.equal(first!.kind, SyntaxKind.VarKeyword);
            });
        });
        describe("getLastToken", () => {
            it("gets jsdoc", () => {
                const root = createSourceFile("foo.ts", "var a = true;/** comment */", ScriptTarget.ES5, /*setParentNodes*/ true);
                assert.isDefined(root);
                const last = root.getLastToken();
                assert.isDefined(last);
                assert.equal(last!.kind, SyntaxKind.EndOfFileToken);
            });
        });
        describe("getStart", () => {
            it("runs when node with JSDoc but no parent pointers", () => {
                const root = createSourceFile("foo.ts", "/** */var a = true;", ScriptTarget.ES5, /*setParentNodes*/ false);
                root.statements[0].getStart(root, /*includeJsdocComment*/ true);
            });
        });
        describe("parseIsolatedJSDocComment", () => {
            it("doesn't create a 1-element array with missing type parameter in jsDoc", () => {
                const doc = parseIsolatedJSDocComment("/**\n    @template\n*/");
                assert.equal((doc?.jsDoc.tags?.[0] as JSDocTemplateTag).typeParameters.length, 0);
            });
        });
    });
}
