/*
 * Portions Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Copyright (c) Tor Norbye.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.ktfmt.kdoc

import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlin.io.path.createTempDirectory
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

@RunWith(JUnit4::class)
class KDocFormatterTest {
  private val tempDir = createTempDirectory().toFile()

  private fun checkFormatter(
      task: FormattingTask,
      expected: String,
      verify: Boolean = true,
      verifyDokka: Boolean = true,
  ) {
    val reformatted = reformatComment(task)

    val indent = task.initialIndent
    val options = task.options
    val source = task.comment

    // Because .trimIndent() will remove it:
    val indentedExpected = expected.split("\n").joinToString("\n") { indent + it }

    assertThat(reformatted).isEqualTo(indentedExpected)

    if (verifyDokka && !options.addPunctuation) {
      DokkaVerifier(tempDir).verify(source, reformatted)
    }

    // Make sure that formatting is stable -- format again and make sure it's the same
    if (verify) {
      val again =
          FormattingTask(
              options,
              reformatted.trim(),
              task.initialIndent,
              task.secondaryIndent,
              task.orderedParameterNames)
      val formattedAgain = reformatComment(again)
      if (reformatted != formattedAgain) {
        assertWithMessage("Formatting is unstable: if formatted a second time, it changes")
            .that("$indent// FORMATTED TWICE (implies unstable formatting)\n\n$formattedAgain")
            .isEqualTo("$indent// FORMATTED ONCE\n\n$reformatted")
      }
    }
  }

  private fun checkFormatter(
      source: String,
      options: KDocFormattingOptions,
      expected: String,
      indent: String = "    ",
      verify: Boolean = true,
      verifyDokka: Boolean = true
  ) {
    val task = FormattingTask(options, source.trim(), indent)
    checkFormatter(task, expected, verify, verifyDokka)
  }

  private fun reformatComment(task: FormattingTask): String {
    val formatter = KDocFormatter(task.options)
    val formatted = formatter.reformatComment(task)
    return task.initialIndent + formatted
  }

  @Test
  fun test1() {
    checkFormatter(
        """
            /**
            * Returns whether lint should check all warnings,
             * including those off by default, or null if
             *not configured in this configuration. This is a really really really long sentence which needs to be broken up.
             * And ThisIsALongSentenceWhichCannotBeBrokenUpAndMustBeIncludedAsAWholeWithoutNewlinesInTheMiddle.
             *
             * This is a separate section
             * which should be flowed together with the first one.
             * *bold* should not be removed even at beginning.
             */
            """
            .trimIndent(),
        KDocFormattingOptions(72),
        """
            /**
             * Returns whether lint should check all warnings, including those
             * off by default, or null if not configured in this configuration.
             * This is a really really really long sentence which needs to be
             * broken up. And
             * ThisIsALongSentenceWhichCannotBeBrokenUpAndMustBeIncludedAsAWholeWithoutNewlinesInTheMiddle.
             *
             * This is a separate section which should be flowed together with
             * the first one. *bold* should not be removed even at beginning.
             */
            """
            .trimIndent())
  }

  @Test
  fun testWithOffset() {
    val source =
        """
            /** Returns whether lint should check all warnings,
             * including those off by default */
            """
            .trimIndent()
    val reformatted =
        """
            /**
             * Returns whether lint should check all warnings, including those
             * off by default
             */
            """
            .trimIndent()
    checkFormatter(source, KDocFormattingOptions(72), reformatted, indent = "    ")
    val initialOffset = source.indexOf("default")
    val newOffset = findSamePosition(source, initialOffset, reformatted)
    assertThat(newOffset).isNotEqualTo(initialOffset)
    assertThat(reformatted.substring(newOffset, newOffset + "default".length)).isEqualTo("default")
  }

  @Test
  fun testWordBreaking() {
    // Without special handling, the "-" in the below would be placed at the
    // beginning of line 2, which then implies a list item.
    val source =
        """
            /** Returns whether lint should check all warnings,
             * including aaaaaa - off by default */
            """
            .trimIndent()
    val reformatted =
        """
            /**
             * Returns whether lint should check all warnings, including
             * aaaaaa - off by default
             */
            """
            .trimIndent()
    checkFormatter(source, KDocFormattingOptions(72), reformatted, indent = "    ")
    val initialOffset = source.indexOf("default")
    val newOffset = findSamePosition(source, initialOffset, reformatted)
    assertThat(newOffset).isNotEqualTo(initialOffset)
    assertThat(reformatted.substring(newOffset, newOffset + "default".length)).isEqualTo("default")
  }

  @Test
  fun testHeader() {
    val source =
        """
            /**
             * Information about a request to run lint.
             *
             * **NOTE: This is not a public or final API; if you rely on this be prepared
             * to adjust your code for the next tools release.**
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72),
        """
            /**
             * Information about a request to run lint.
             *
             * **NOTE: This is not a public or final API; if you rely on this be
             * prepared to adjust your code for the next tools release.**
             */
            """
            .trimIndent())

    checkFormatter(
        source,
        KDocFormattingOptions(40),
        """
            /**
             * Information about a request to run
             * lint.
             *
             * **NOTE: This is not a public or final
             * API; if you rely on this be prepared
             * to adjust your code for the next
             * tools release.**
             */
            """
            .trimIndent(),
        indent = "")

    checkFormatter(
        source,
        KDocFormattingOptions(100, 100),
        """
            /**
             * Information about a request to run lint.
             *
             * **NOTE: This is not a public or final API; if you rely on this be prepared to adjust your code
             * for the next tools release.**
             */
            """
            .trimIndent(),
        indent = "")
  }

  @Test
  fun testSingle() {
    val source =
        """
            /**
             * The lint client requesting the lint check
             *
             * @return the client, never null
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72),
        """
            /**
             * The lint client requesting the lint check
             *
             * @return the client, never null
             */
            """
            .trimIndent())
  }

  @Test
  fun testEmpty() {
    val source =
        """
            /** */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72),
        """
            /**  */
            """
            .trimIndent())

    checkFormatter(
        source,
        KDocFormattingOptions(72).apply { collapseSingleLine = false },
        """
            /**
             */
            """
            .trimIndent())
  }

  @Test
  fun testJavadocParams() {
    val source =
        """
            /**
             * Sets the scope to use; lint checks which require a wider scope set
             * will be ignored
             *
             * @param scope the scope
             *
             * @return this, for constructor chaining
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72),
        """
            /**
             * Sets the scope to use; lint checks which require a wider scope
             * set will be ignored
             *
             * @param scope the scope
             * @return this, for constructor chaining
             */
            """
            .trimIndent())
  }

  @Test
  fun testBracketParam() {
    // Regression test for https://github.com/tnorbye/kdoc-formatter/issues/72
    val source =
        """
            /**
             * Summary
             * @param [ param1  ] some value
             * @param[param2] another value
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72),
        """
            /**
             * Summary
             *
             * @param param1 some value
             * @param param2 another value
             */
            """
            .trimIndent())
  }

  @Test
  fun testMultiLineLink() {
    // Regression test for https://github.com/tnorbye/kdoc-formatter/issues/70
    val source =
        """
            /**
             * Single line is converted {@link foo}
             *
             * Multi line is converted {@link
             * foo}
             *
             * Single line with hash is converted {@link #foo}
             *
             * Multi line with has is converted {@link
             * #foo}
             *
             * Don't interpret {@code
             * # This is not a header
             * * this is
             *   * not a nested list
             * }
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72),
        """
            /**
             * Single line is converted [foo]
             *
             * Multi line is converted [foo]
             *
             * Single line with hash is converted [foo]
             *
             * Multi line with has is converted [foo]
             *
             * Don't interpret {@code # This is not a header * this is * not a
             * nested list }
             */
             """
            .trimIndent(),
        // {@link} text is not rendered by dokka when it cannot resolve the symbols
        verifyDokka = false)
  }

  @Test
  fun testPreformattedWithinCode() {
    // Regression test for https://github.com/tnorbye/kdoc-formatter/issues/77
    val source =
        """
            /**
             * Some summary.
             *  {@code
             *
             * foo < bar?}
             *  Done.
             *
             *
             * {@code
             * ```
             *    Some code.
             * ```
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72),
        """
            /**
             * Some summary. {@code
             *
             * foo < bar?} Done.
             *
             * {@code
             *
             * ```
             *    Some code.
             * ```
             */
             """
            .trimIndent())
  }

  @Test
  fun testPreStability() {
    // Regression test for https://github.com/tnorbye/kdoc-formatter/issues/78
    val source =
        """
            /**
             * Some summary
             *
             *

             * line one
             * ```
             *     line two
             * ```
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72),
        """
            /**
             * Some summary
             *

             * line one
             * ```
             *     line two
             * ```
             */
            """
            .trimIndent())
  }

  @Test
  fun testPreStability2() {
    // Regression test for https://github.com/tnorbye/kdoc-formatter/issues/78
    // (second scenario
    val source =
        """
            /**
             * Some summary
             *
             *

             * ```
             *     code
             * ```
             *

             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72),
        """
            /**
             * Some summary
             *

             * ```
             *     code
             * ```
             *

             */
            """
            .trimIndent())
  }

  @Test
  fun testConvertParamReference() {
    // Regression test for https://github.com/tnorbye/kdoc-formatter/issues/79
    val source =
        """
            /**
             * Some summary.
             *
             * Another summary about {@param someParam}.
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72),
        """
            /**
             * Some summary.
             *
             * Another summary about [someParam].
             */
            """
            .trimIndent(),
        // {@param reference} text is not rendered by dokka when it cannot resolve the symbols
        verifyDokka = false)
  }

  @Test
  fun testLineWidth1() {
    // Perform in KDocFileFormatter test too to make sure we properly account
    // for indent!
    val source =
        """
            /**
             * 89 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789
             *
             *   10        20        30        40        50        60        70        80
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72),
        """
            /**
             * 89 123456789 123456789 123456789 123456789 123456789 123456789
             * 123456789 123456789
             *
             * 10 20 30 40 50 60 70 80
             */
            """
            .trimIndent())

    checkFormatter(
        source,
        KDocFormattingOptions(40),
        """
            /**
             * 89 123456789 123456789 123456789
             * 123456789 123456789 123456789
             * 123456789 123456789
             *
             * 10 20 30 40 50 60 70 80
             */
            """
            .trimIndent())
  }

  @Test
  fun testBlockTagsNoSeparators() {
    checkFormatter(
        """
             /**
              * Marks the given warning as "ignored".
              *
             * @param context The scanning context
             * @param issue the issue to be ignored
             * @param location The location to ignore the warning at, if any
             * @param message The message for the warning
             */
            """
            .trimIndent(),
        KDocFormattingOptions(72),
        """
             /**
              * Marks the given warning as "ignored".
              *
              * @param context The scanning context
              * @param issue the issue to be ignored
              * @param location The location to ignore the warning at, if any
              * @param message The message for the warning
              */
            """
            .trimIndent())
  }

  @Test
  fun testBlockTagsHangingIndents() {
    val options = KDocFormattingOptions(40)
    options.hangingIndent = 6
    checkFormatter(
        """
            /**
             * Creates a list of class entries from the given class path and specific set of files within
             * it.
             *
             * @param client the client to report errors to and to use to read files
             * @param classFiles the specific set of class files to look for
             * @param classFolders the list of class folders to look in (to determine the package root)
             * @param sort if true, sort the results
             * @return the list of class entries, never null.
             */
            """
            .trimIndent(),
        options,
        """
            /**
             * Creates a list of class entries
             * from the given class path and
             * specific set of files within it.
             *
             * @param client the client to
             *       report errors to and to use
             *       to read files
             * @param classFiles the specific
             *       set of class files to look
             *       for
             * @param classFolders the list of
             *       class folders to look in
             *       (to determine the package
             *       root)
             * @param sort if true, sort the
             *       results
             * @return the list of class
             *       entries, never null.
             */
            """
            .trimIndent())
  }

  @Test
  fun testGreedyBlockIndent() {
    val options = KDocFormattingOptions(100, 72)
    options.hangingIndent = 6
    checkFormatter(
        """
            /**
             * Returns the project resources, if available
             *
             * @param includeModuleDependencies if true, include merged view of
             *     all module dependencies
             * @param includeLibraries if true, include merged view of all
             *     library dependencies (this also requires all module dependencies)
             * @return the project resources, or null if not available
             */
            """
            .trimIndent(),
        options,
        """
            /**
             * Returns the project resources, if available
             *
             * @param includeModuleDependencies if true, include merged view of all
             *       module dependencies
             * @param includeLibraries if true, include merged view of all library
             *       dependencies (this also requires all module dependencies)
             * @return the project resources, or null if not available
             */
            """
            .trimIndent())
  }

  @Test
  fun testBlockTagsHangingIndents2() {
    checkFormatter(
        """
            /**
             * @param client the client to
             *     report errors to and to use to
             *     read files
             */
            """
            .trimIndent(),
        KDocFormattingOptions(40),
        """
            /**
             * @param client the client to
             *   report errors to and to use to
             *   read files
             */
            """
            .trimIndent())
  }

  @Test
  fun testSingleLine() {
    // Also tests punctuation feature.
    val source =
        """
             /**
              * This could all fit on one line
              */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72),
        """
             /** This could all fit on one line */
            """
            .trimIndent())
    val options = KDocFormattingOptions(72)
    options.collapseSingleLine = false
    options.addPunctuation = true
    checkFormatter(
        source,
        options,
        """
             /**
              * This could all fit on one line.
              */
            """
            .trimIndent())
  }

  @Test
  fun testPunctuationWithLabelLink() {
    val source =
        """
             /** Default implementation of [MyInterface] */
            """
            .trimIndent()

    val options = KDocFormattingOptions(72)
    options.addPunctuation = true
    checkFormatter(
        source,
        options,
        """
             /** Default implementation of [MyInterface]. */
            """
            .trimIndent())
  }

  @Test
  fun testWrapingOfLinkText() {
    val source =
        """
             /**
              * Sometimes the text of a link can have spaces, like [this link's text](https://example.com).
              * The following text should wrap like usual.
              */
            """
            .trimIndent()

    val options = KDocFormattingOptions(72)
    checkFormatter(
        source,
        options,
        """
            /**
             * Sometimes the text of a link can have spaces, like
             * [this link's text](https://example.com). The following text
             * should wrap like usual.
             */
            """
            .trimIndent())
  }

  @Test
  fun testPreformattedTextIndented() {
    val source =
        """
            /**
             * Parser for the list of forward socket connection returned by the
             * `host:forward-list` command.
             *
             * Input example
             *
             *  ```
             *
             * HT75B1A00212 tcp:51222 tcp:5000 HT75B1A00212 tcp:51227 tcp:5001
             * HT75B1A00212 tcp:51232 tcp:5002 HT75B1A00212 tcp:51239 tcp:5003
             * HT75B1A00212 tcp:51244 tcp:5004
             *
             *  ```
             */
            """
            .trimIndent()
    checkFormatter(
        source, KDocFormattingOptions(72, 72).apply { convertMarkup = true }, source, indent = "")
  }

  @Test
  fun testPreformattedText() {
    val source =
        """
            /**
             * Code sample:
             *
             *     val s = "hello, and   this is code so should not be line broken at all, it should stay on one line";
             *     println(s);
             *
             * This is not preformatted and can be combined into multiple sentences again.
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(40),
        """
            /**
             * Code sample:
             *
             *     val s = "hello, and   this is code so should not be line broken at all, it should stay on one line";
             *     println(s);
             *
             * This is not preformatted and
             * can be combined into multiple
             * sentences again.
             */
            """
            .trimIndent())
  }

  @Test
  fun testPreformattedText2() {
    val source =
        """
            /**
             * Code sample:
             * ```kotlin
             * val s = "hello, and this is code so should not be line broken at all, it should stay on one line";
             * println(s);
             * ```
             *
             * This is not preformatted and can be combined into multiple sentences again.
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(40),
        """
            /**
             * Code sample:
             * ```kotlin
             * val s = "hello, and this is code so should not be line broken at all, it should stay on one line";
             * println(s);
             * ```
             *
             * This is not preformatted and
             * can be combined into multiple
             * sentences again.
             */
            """
            .trimIndent())
  }

  @Test
  fun testPreformattedText3() {
    val source =
        """
            /**
             * Code sample:
             *

             *     val s = "hello, and   this is code so should not be line broken at all, it should stay on one line";
             *     println(s);
             *

             * This is not preformatted and can be combined into multiple sentences again.
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(40),
        """
            /**
             * Code sample:
             * ```
             *     val s = "hello, and   this is code so should not be line broken at all, it should stay on one line";
             *     println(s);
             * ```
             *
             * This is not preformatted and
             * can be combined into multiple
             * sentences again.
             */
            """
            .trimIndent(),
        //
 and ``` are rendered differently; this is an intentional diff
        verifyDokka = false)
  }

  @Test
  fun testPreformattedTextWithBlankLines() {
    val source =
        """
            /**
             * Code sample:
             * ```kotlin
             * val s = "hello, and this is code so should not be line broken at all, it should stay on one line";
             *
             * println(s);
             * ```
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(40),
        """
            /**
             * Code sample:
             * ```kotlin
             * val s = "hello, and this is code so should not be line broken at all, it should stay on one line";
             *
             * println(s);
             * ```
             */
            """
            .trimIndent())
  }

  @Test
  fun testPreformattedTextWithBlankLinesAndTrailingSpaces() {
    val source =
        """
            /**
             * Code sample:
             * ```kotlin
             * val s = "hello, and this is code so should not be line broken at all, it should stay on one line";
             *
             * println(s);
             * ```
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(40),
        """
            /**
             * Code sample:
             * ```kotlin
             * val s = "hello, and this is code so should not be line broken at all, it should stay on one line";
             *
             * println(s);
             * ```
             */
            """
            .trimIndent())
  }

  @Test
  fun testPreformattedTextSeparation() {
    val source =
        """
            /**
             * For example,
             *
             *     val s = "hello, and   this is code so should not be line broken at all, it should stay on one line";
             *     println(s);
             * And here's another example:
             *     This is not preformatted text.
             *
             * And a third example,
             *
             * ```
             * Preformatted.
             * ```
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(40),
        """
            /**
             * For example,
             *
             *     val s = "hello, and   this is code so should not be line broken at all, it should stay on one line";
             *     println(s);
             *
             * And here's another example: This
             * is not preformatted text.
             *
             * And a third example,
             * ```
             * Preformatted.
             * ```
             */
            """
            .trimIndent())
  }

  @Test
  fun testSeparateParagraphMarkers1() {
    // If the markup still contains HTML paragraph separators, separate
    // paragraphs
    val source =
        """
            /**
             * Here's paragraph 1.
             *
             * And here's paragraph 2.
             * <p>And here's paragraph 3.
             * <P/>And here's paragraph 4.
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(40).apply { convertMarkup = true },
        """
            /**
             * Here's paragraph 1.
             *
             * And here's paragraph 2.
             *
             * And here's paragraph 3.
             *
             * And here's paragraph 4.
             */
            """
            .trimIndent())
  }

  @Test
  fun testSeparateParagraphMarkers2() {
    // From ktfmt Tokenizer.kt
    val source =
        """
            /**
             * Tokenizer traverses a Kotlin parse tree (which blessedly contains whitespaces and comments,
             * unlike Javac) and constructs a list of 'Tok's.
             *
             * <p>The google-java-format infra expects newline Toks to be separate from maximal-whitespace Toks,
             * but Kotlin emits them together. So, we split them using Java's \R regex matcher. We don't use
             * 'split' et al. because we want Toks for the newlines themselves.
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(100, 100).apply {
          convertMarkup = true
          optimal = false
        },
        """
            /**
             * Tokenizer traverses a Kotlin parse tree (which blessedly contains whitespaces and comments,
             * unlike Javac) and constructs a list of 'Tok's.
             *
             * The google-java-format infra expects newline Toks to be separate from maximal-whitespace Toks,
             * but Kotlin emits them together. So, we split them using Java's \R regex matcher. We don't use
             * 'split' et al. because we want Toks for the newlines themselves.
             */
            """
            .trimIndent(),
        indent = "")
  }

  @Test
  fun testConvertMarkup() {
    // If the markup still contains HTML paragraph separators, separate
    // paragraphs
    val source =
        """
            /**
             * This is <b>bold</b>, this is <i>italics</i>, but nothing
             * should be converted in `<b>code</b>` or in
             * ```
             * preformatted text
             * ```
             * And this \` is <b>not code and should be converted</b>.
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(40).apply { convertMarkup = true },
        """
            /**
             * This is **bold**, this is
             * *italics*, but nothing should be
             * converted in `<b>code</b>` or in
             *
             * ```
             * preformatted text
             * ```
             *
             * And this \` is **not code and
             * should be converted**.
             */
            """
            .trimIndent())
  }

  @Test
  fun testFormattingList() {
    val source =
        """
            /**
             * 1. This is a numbered list.
             * 2. This is another item. We should be wrapping extra text under the same item.
             * 3. This is the third item.
             *
             * Unordered list:
             * * First
             * * Second
             * * Third
             *
             * Other alternatives:
             * - First
             * - Second
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(40),
        """
            /**
             * 1. This is a numbered list.
             * 2. This is another item. We
             *    should be wrapping extra text
             *    under the same item.
             * 3. This is the third item.
             *
             * Unordered list:
             * * First
             * * Second
             * * Third
             *
             * Other alternatives:
             * - First
             * - Second
             */
            """
            .trimIndent())
  }

  @Test
  fun testList1() {
    val source =
        """
            /**
             *  * pre.errorlines: General > Text > Default Text
             *  * .prefix: XML > Namespace Prefix
             *  * .attribute: XML > Attribute name
             *  * .value: XML > Attribute value
             *  * .tag: XML > Tag name
             *  * .lineno: For color, General > Code > Line number, Foreground, and for background-color,
             *  Editor > Gutter background
             *  * .error: General > Errors and Warnings > Error
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(40),
        """
            /**
             * * pre.errorlines: General >
             *   Text > Default Text
             * * .prefix: XML > Namespace Prefix
             * * .attribute: XML > Attribute
             *   name
             * * .value: XML > Attribute value
             * * .tag: XML > Tag name
             * * .lineno: For color, General >
             *   Code > Line number, Foreground,
             *   and for background-color,
             *   Editor > Gutter background
             * * .error: General > Errors and
             *   Warnings > Error
             */
            """
            .trimIndent())
  }

  @Test
  fun testIndentedList() {
    val source =
        """
            /**
            * Basic usage:
            *   1. Create a configuration via [UastEnvironment.Configuration.create] and mutate it as needed.
            *   2. Create a project environment via [UastEnvironment.create].
            *      You can create multiple environments in the same process (one for each "module").
            *   3. Call [analyzeFiles] to initialize PSI machinery and precompute resolve information.
            */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(40),
        """
            /**
             * Basic usage:
             * 1. Create a configuration via
             *    [UastEnvironment.Configuration.create]
             *    and mutate it as needed.
             * 2. Create a project environment
             *    via [UastEnvironment.create].
             *    You can create multiple
             *    environments in the same
             *    process (one for each
             *    "module").
             * 3. Call [analyzeFiles] to
             *    initialize PSI machinery and
             *    precompute resolve
             *    information.
             */
            """
            .trimIndent())
  }

  @Test
  fun testDocTags() {
    val source =
        """
            /**
             * @param configuration the configuration to look up which issues are
             * enabled etc from
             * @param platforms the platforms applying to this analysis
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(40),
        """
            /**
             * @param configuration the
             *   configuration to look up which
             *   issues are enabled etc from
             * @param platforms the platforms
             *   applying to this analysis
             */
            """
            .trimIndent())
  }

  @Test
  fun testAtInMiddle() {
    val source =
        """
            /**
             * If non-null, this issue can **only** be suppressed with one of the
             * given annotations: not with @Suppress, not with @SuppressLint, not
             * with lint.xml, not with lintOptions{} and not with baselines.
             *
             * Test @IntRange and @FloatRange support annotation applied to
             * arrays and vargs.
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72),
        """
            /**
             * If non-null, this issue can **only** be suppressed with
             * one of the given annotations: not with @Suppress, not
             * with @SuppressLint, not with lint.xml, not with lintOptions{} and
             * not with baselines.
             *
             * Test @IntRange and @FloatRange support annotation applied to
             * arrays and vargs.
             */
            """
            .trimIndent(),
    )
  }

  @Test
  fun testMaxCommentWidth() {
    checkFormatter(
        """
            /**
            * Returns whether lint should check all warnings,
             * including those off by default, or null if
             *not configured in this configuration. This is a really really really long sentence which needs to be broken up.
             * This is a separate section
             * which should be flowed together with the first one.
             * *bold* should not be removed even at beginning.
             */
            """
            .trimIndent(),
        KDocFormattingOptions(maxLineWidth = 100, maxCommentWidth = 30),
        """
            /**
             * Returns whether lint should
             * check all warnings, including
             * those off by default, or
             * null if not configured in
             * this configuration. This is
             * a really really really long
             * sentence which needs to be
             * broken up. This is a separate
             * section which should be flowed
             * together with the first one.
             * *bold* should not be removed
             * even at beginning.
             */
            """
            .trimIndent())
  }

  @Test
  fun testHorizontalRuler() {
    checkFormatter(
        """
            /**
            * This is a header. Should appear alone.
            * --------------------------------------
            *
            * This should not be on the same line as the header.
             */
            """
            .trimIndent(),
        KDocFormattingOptions(maxLineWidth = 100, maxCommentWidth = 30),
        """
            /**
             * This is a header. Should
             * appear alone.
             * --------------------------------------
             * This should not be on the same
             * line as the header.
             */
            """
            .trimIndent(),
        verifyDokka = false)
  }

  @Test
  fun testQuoteOnlyOnFirstLine() {
    checkFormatter(
        """
            /**
            * More:
            * > This whole paragraph should be treated as a block quote.
            * This whole paragraph should be treated as a block quote.
            * This whole paragraph should be treated as a block quote.
            * This whole paragraph should be treated as a block quote.
            * @sample Sample
                 */
            """
            .trimIndent(),
        KDocFormattingOptions(maxLineWidth = 100, maxCommentWidth = 30),
        """
            /**
             * More:
             * > This whole paragraph should
             * > be treated as a block quote.
             * > This whole paragraph should
             * > be treated as a block quote.
             * > This whole paragraph should
             * > be treated as a block quote.
             * > This whole paragraph should
             * > be treated as a block quote.
             *
             * @sample Sample
             */
            """
            .trimIndent())
  }

  @Test
  fun testNoBreakUrl() {
    checkFormatter(
        """
            /**
             *  # Design
             *  The splash screen icon uses the same specifications as
             *  [Adaptive Icons](https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive)
             */
            """
            .trimIndent(),
        KDocFormattingOptions(maxLineWidth = 100, maxCommentWidth = 100),
        """
            /**
             * # Design
             * The splash screen icon uses the same specifications as
             * [Adaptive Icons](https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive)
             */
            """
            .trimIndent())
  }

  @Test
  fun testAsciiArt() {
    // Comment from
    // https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:build-system/integration-test/application/src/test/java/com/android/build/gradle/integration/bundle/DynamicFeatureAndroidTestBuildTest.kt
    checkFormatter(
        """
            /**
             *       Base <------------ Middle DF <------------- DF <--------- Android Test DF
             *      /    \              /       \                |               /        \   \
             *     v      v            v         v               v              v          \   \
             *  appLib  sharedLib   midLib   sharedMidLib    featureLib    testFeatureLib   \   \
             *              ^                      ^_______________________________________/   /
             *              |________________________________________________________________/
             *
             *  DF has a feature-on-feature dep on Middle DF, both depend on Base, Android Test DF is an
             *  android test variant for DF.
             *
             *  Base depends on appLib and sharedLib.
             *  Middle DF depends on midLib and sharedMidLib.
             *  DF depends on featureLib.
             *  DF also has an android test dependency on testFeatureLib, shared and sharedMidLib.
             */
            """
            .trimIndent(),
        KDocFormattingOptions(maxLineWidth = 100, maxCommentWidth = 30),
        """
            /**
             *       Base <------------ Middle DF <------------- DF <--------- Android Test DF
             *      /    \              /       \                |               /        \   \
             *     v      v            v         v               v              v          \   \
             *  appLib  sharedLib   midLib   sharedMidLib    featureLib    testFeatureLib   \   \
             *              ^                      ^_______________________________________/   /
             *              |________________________________________________________________/
             *
             * DF has a feature-on-feature
             * dep on Middle DF, both depend
             * on Base, Android Test DF is an
             * android test variant for DF.
             *
             * Base depends on appLib and
             * sharedLib. Middle DF depends
             * on midLib and sharedMidLib. DF
             * depends on featureLib. DF also
             * has an android test dependency
             * on testFeatureLib, shared and
             * sharedMidLib.
             */
            """
            .trimIndent())
  }

  @Test
  fun testAsciiArt2() {
    checkFormatter(
        """
            /**
             *                 +-> lib1
             *                 |
             *     feature1 ---+-> javalib1
             *                 |
             *                 +-> baseModule
             */
            """
            .trimIndent(),
        KDocFormattingOptions(maxLineWidth = 100, maxCommentWidth = 30),
        """
            /**
             *                 +-> lib1
             *                 |
             *     feature1 ---+-> javalib1
             *                 |
             *                 +-> baseModule
             */
            """
            .trimIndent())
  }

  @Test
  fun testAsciiArt3() {
    val source =
        """
            /**
             * This test creates a layout of this shape:
             *
             *  ---------------
             *  | t      |    |
             *  |        |    |
             *  |  |-------|  |
             *  |  | t     |  |
             *  |  |       |  |
             *  |  |       |  |
             *  |--|  |-------|
             *  |  |  | t     |
             *  |  |  |       |
             *  |  |  |       |
             *  |  |--|       |
             *  |     |       |
             *  ---------------
             *
             * There are 3 staggered children and 3 pointers, the first is on child 1, the second is on
             * child 2 in a space that overlaps child 1, and the third is in a space in child 3 that
             * overlaps child 2.
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(maxLineWidth = 100, maxCommentWidth = 30),
        """
            /**
             * This test creates a layout of
             * this shape:
             * ---------------
             * | t | | | | | | |-------| | |
             * | t | | | | | | | | | | |--|
             * |-------| | | | t | | | | | |
             * | | | | |--| | | | |
             * ---------------
             * There are 3 staggered children
             * and 3 pointers, the first is
             * on child 1, the second is
             * on child 2 in a space that
             * overlaps child 1, and the
             * third is in a space in child
             * 3 that overlaps child 2.
             */
            """
            .trimIndent(),
        indent = "")
  }

  @Test
  fun testBrokenAsciiArt() {
    // The first illustration has indentation 3, not 4, so isn't preformatted.
    // The formatter will garble this -- but so will Dokka!
    // From androidx' TwoDimensionalFocusTraversalOutTest.kt
    checkFormatter(
        """
            /**
             *    ___________________________
             *   |  grandparent             |
             *   |   _____________________  |
             *   |  |  parent            |  |
             *   |  |   _______________  |  |   ____________
             *   |  |  | focusedItem  |  |  |  | nextItem  |
             *   |  |  |______________|  |  |  |___________|
             *   |  |____________________|  |
             *   |__________________________|
             *
             *      __________________________
             *     |  grandparent            |
             *     |   ____________________  |
             *     |  |  parent           |  |
             *     |  |   ______________  |  |
             *     |  |  | focusedItem |  |  |
             *     |  |  |_____________|  |  |
             *     |  |___________________|  |
             *     |_________________________|
             */
            """
            .trimIndent(),
        KDocFormattingOptions(maxLineWidth = 100, 100),
        """
            /**
             * ___________________________ | grandparent | | _____________________ | | | parent
             * | | | | _______________ | | ____________ | | | focusedItem | | | | nextItem | | |
             * |______________| | | |___________| | |____________________| | |__________________________|
             *
             *      __________________________
             *     |  grandparent            |
             *     |   ____________________  |
             *     |  |  parent           |  |
             *     |  |   ______________  |  |
             *     |  |  | focusedItem |  |  |
             *     |  |  |_____________|  |  |
             *     |  |___________________|  |
             *     |_________________________|
             */
            """
            .trimIndent(),
        verifyDokka = false)
  }

  @Test
  fun testHtmlLists() {
    checkFormatter(
        """
            /**
             *

             *
Incremental merge will never clean the output.
             *
The inputs must be able to tell which changes to relative files have been made.
             *
Intermediate state must be saved between merges.
             *

             */
            """
            .trimIndent(),
        KDocFormattingOptions(maxLineWidth = 100, maxCommentWidth = 60),
        """
            /**
             *

             *
Incremental merge will never clean the output.
             *
The inputs must be able to tell which changes to
             *   relative files have been made.
             *
Intermediate state must be saved between merges.
             *

             */
            """
            .trimIndent())
  }

  @Test
  fun testVariousMarkup() {
    val source =
        """
            /**
             * This document contains a bunch of markup examples
             * that I will use
             * to verify that things are handled
             * correctly via markdown.
             *
             * This is a header. Should appear alone.
             * --------------------------------------
             * This should not be on the same line as the header.
             *
             * This is a header. Should appear alone.
             * -
             * This should not be on the same line as the header.
             *
             * This is a header. Should appear alone.
             * ======================================
             * This should not be on the same line as the header.
             *
             * This is a header. Should appear alone.
             * =
             * This should not be on the same line as the header.
             * Note that we don't treat this as a header
             * because it's not on its own line. Instead
             * it's considered a separating line.
             * ---
             * More text. Should not be on the previous line.
             *
             * --- This usage of --- where it's not on its own
             * line should not be used as a header or separator line.
             *
             * List stuff:
             * 1. First item
             * 2. Second item
             * 3. Third item
             *
             * # Text styles #
             * **Bold**, *italics*. \*Not italics\*.
             *
             * ## More text styles
             * ~~strikethrough~~, _underlined_.
             *
             * ### Blockquotes #
             *
             * More:
             * > This whole paragraph should be treated as a block quote.
             * This whole paragraph should be treated as a block quote.
             * This whole paragraph should be treated as a block quote.
             * This whole paragraph should be treated as a block quote.
             *
             * ### Lists
             * Plus lists:
             * + First
             * + Second
             * + Third
             *
             * Dash lists:
             * - First
             * - Second
             * - Third
             *
             * List items with multiple paragraphs:
             *
             * * This is my list item. It has
             *   text on many lines.
             *
             *   This is a continuation of the first bullet.
             * * And this is the second.
             *
             * ### Code blocks in list items
             *
             * Escapes: I should look for cases where I place a number followed
             * by a period (or asterisk) at the beginning of a line and if so,
             * escape it:
             *
             * The meaning of life:
             * 42\. This doesn't seem to work in IntelliJ's markdown formatter.
             *
             * ### Horizontal rules
             * *********
             * ---------
             * ***
             * * * *
             * - - -
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(100, 100),
        """
            /**
             * This document contains a bunch of markup examples that I will use to verify that things are
             * handled correctly via markdown.
             *
             * This is a header. Should appear alone.
             * --------------------------------------
             * This should not be on the same line as the header.
             *
             * This is a header. Should appear alone.
             * -
             * This should not be on the same line as the header.
             *
             * This is a header. Should appear alone.
             * ======================================
             * This should not be on the same line as the header.
             *
             * This is a header. Should appear alone.
             * =
             * This should not be on the same line as the header. Note that we don't treat this as a header
             * because it's not on its own line. Instead it's considered a separating line.
             * ---
             * More text. Should not be on the previous line.
             *
             * --- This usage of --- where it's not on its own line should not be used as a header or
             * separator line.
             *
             * List stuff:
             * 1. First item
             * 2. Second item
             * 3. Third item
             *
             * # Text styles #
             * **Bold**, *italics*. \*Not italics\*.
             *
             * ## More text styles
             * ~~strikethrough~~, _underlined_.
             *
             * ### Blockquotes #
             *
             * More:
             * > This whole paragraph should be treated as a block quote. This whole paragraph should be
             * > treated as a block quote. This whole paragraph should be treated as a block quote. This whole
             * > paragraph should be treated as a block quote.
             *
             * ### Lists
             * Plus lists:
             * + First
             * + Second
             * + Third
             *
             * Dash lists:
             * - First
             * - Second
             * - Third
             *
             * List items with multiple paragraphs:
             * * This is my list item. It has text on many lines.
             *
             *   This is a continuation of the first bullet.
             * * And this is the second.
             *
             * ### Code blocks in list items
             *
             * Escapes: I should look for cases where I place a number followed by a period (or asterisk) at
             * the beginning of a line and if so, escape it:
             *
             * The meaning of life: 42\. This doesn't seem to work in IntelliJ's markdown formatter.
             *
             * ### Horizontal rules
             * *********
             * ---------
             * ***
             * * * *
             * - - -
             */
             """
            .trimIndent())
  }

  @Test
  fun testLineComments() {
    val source =
        """
            //
            // Information about a request to run lint.
            //
            // **NOTE: This is not a public or final API; if you rely on this be prepared
            // to adjust your code for the next tools release.**
            //
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(40),
        """
            // Information about a request to
            // run lint.
            //
            // **NOTE: This is not a public or
            // final API; if you rely on this be
            // prepared to adjust your code for
            // the next tools release.**
            """
            .trimIndent())
  }

  @Test
  fun testMoreLineComments() {
    val source =
        """
            // Do not clean
            // this
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(70),
        """
            // Do not clean this
            """
            .trimIndent())
  }

  @Test
  fun testListContinuations() {
    val source =
        """
            /**
             * * This is my list item. It has
             *   text on many lines.
             *
             *   This is a continuation of the first bullet.
             * * And this is the second.
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(40),
        """
            /**
             * * This is my list item. It has
             *   text on many lines.
             *
             *   This is a continuation of the
             *   first bullet.
             * * And this is the second.
             */
            """
            .trimIndent())
  }

  @Test
  fun testListContinuations2() {
    val source =
        "/**\n" +
            """
            List items with multiple paragraphs:

            * This is my list item. It has
              text on many lines.

              This is a continuation of the first bullet.
            * And this is the second.
        """
                .trimIndent()
                .split("\n")
                .joinToString(separator = "\n") { " * $it".trimEnd() } +
            "\n */"

    checkFormatter(
        source,
        KDocFormattingOptions(100),
        """
            /**
             * List items with multiple paragraphs:
             * * This is my list item. It has text on many lines.
             *
             *   This is a continuation of the first bullet.
             * * And this is the second.
             */
            """
            .trimIndent())
  }

  @Test
  fun testAccidentalHeader() {
    val source =
        """
             /**
             * Constructs a simplified version of the internal JVM description of the given method. This is
             * in the same format as {@link #getMethodDescription} above, the difference being we don't have
             * the actual PSI for the method type, we just construct the signature from the [method] name,
             * the list of [argumentTypes] and optionally include the [returnType].
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72),
        // Note how this places the "#" in column 0 which will then
        // be re-interpreted as a header next time we format it!
        // Idea: @{link #} should become {@link#} or with a nbsp;
        """
            /**
             * Constructs a simplified version of the internal JVM
             * description of the given method. This is in the same format as
             * [getMethodDescription] above, the difference being we don't
             * have the actual PSI for the method type, we just construct the
             * signature from the [method] name, the list of [argumentTypes] and
             * optionally include the [returnType].
             */
            """
            .trimIndent(),
        // {@link} text is not rendered by dokka when it cannot resolve the symbols
        verifyDokka = false)
  }

  @Test
  fun testTODO() {
    val source =
        """
            /**
             * Adds the given dependency graph (the output of the Gradle dependency task)
             * to be constructed when mocking a Gradle model for this project.
             *

             * To generate this, run for example
             *

             *     ./gradlew :app:dependencies
             *

             * and then look at the debugCompileClasspath (or other graph that you want
             * to model).
             * TODO: Adds the given dependency graph (the output of the Gradle dependency task)
             * to be constructed when mocking a Gradle model for this project.
             * TODO: More stuff to do here
             * @param dependencyGraph the graph description
             * @return this for constructor chaining
             * TODO: Consider looking at the localization="suggested" attribute in
             * the platform attrs.xml to catch future recommended attributes.
             * TODO: Also adds the given dependency graph (the output of the Gradle dependency task)
             * to be constructed when mocking a Gradle model for this project.
             * TODO(b/144576310): Cover multi-module search.
             *  Searching in the search bar should show an option to change module if there are resources in it.
             * TODO(myldap): Cover filter usage. Eg: Look for a framework resource by enabling its filter.
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72).apply { orderDocTags = true },
        // Note how this places the "#" in column 0 which will then
        // be re-interpreted as a header next time we format it!
        // Idea: @{link #} should become {@link#} or with a nbsp;
        """
            /**
             * Adds the given dependency graph (the output of the Gradle
             * dependency task) to be constructed when mocking a Gradle model
             * for this project.
             *
             * To generate this, run for example
             *
             * ```
             *     ./gradlew :app:dependencies
             * ```
             *
             * and then look at the debugCompileClasspath (or other graph that
             * you want to model).
             *
             * @param dependencyGraph the graph description
             * @return this for constructor chaining
             *
             * TODO: Adds the given dependency graph (the output of the Gradle
             *   dependency task) to be constructed when mocking a Gradle model
             *   for this project.
             * TODO: More stuff to do here
             * TODO: Consider looking at the localization="suggested" attribute
             *   in the platform attrs.xml to catch future recommended
             *   attributes.
             * TODO: Also adds the given dependency graph (the output of the
             *   Gradle dependency task) to be constructed when mocking a Gradle
             *   model for this project.
             * TODO(b/144576310): Cover multi-module search. Searching in the
             *   search bar should show an option to change module if there are
             *   resources in it.
             * TODO(myldap): Cover filter usage. Eg: Look for a framework
             *   resource by enabling its filter.
             */
            """
            .trimIndent(),
        // We indent TO-DO text deliberately, though this changes the structure to
        // make each item have its own paragraph which doesn't happen by default.
        // Working as intended.
        verifyDokka = false)
  }

  @Test
  fun testReorderTags() {
    val source =
        """
            /**
             * Constructs a new location range for the given file, from start to
             * end. If the length of the range is not known, end may be null.
             *
             * @return Something
             * @sample Other
             * @param file the associated file (but see the documentation for
             * [Location.file] for more information on what the file
             * represents)
             *
             * @param end the ending position, or null
             * @param[ start ]   the starting position, or null
             * @see More
             */
            """
            .trimIndent()
    checkFormatter(
        FormattingTask(
            KDocFormattingOptions(72),
            source,
            "    ",
            orderedParameterNames = listOf("file", "start", "end")),
        // Note how this places the "#" in column 0 which will then
        // be re-interpreted as a header next time we format it!
        // Idea: @{link #} should become {@link#} or with a nbsp;
        """
            /**
             * Constructs a new location range for the given file, from start to
             * end. If the length of the range is not known, end may be null.
             *
             * @param file the associated file (but see the documentation for
             *   [Location.file] for more information on what the file
             *   represents)
             * @param start the starting position, or null
             * @param end the ending position, or null
             * @return Something
             * @sample Other
             * @see More
             */
            """
            .trimIndent(),
    )
  }

  @Test
  fun testKDocOrdering() {
    // From AndroidX'
    // frameworks/support/biometric/biometric-ktx/src/main/java/androidx/biometric/auth/CredentialAuthExtensions.kt
    val source =
        """
            /**
             * Shows an authentication prompt to the user.
             *
             * @param host A wrapper for the component that will host the prompt.
             * @param crypto A cryptographic object to be associated with this authentication.
             *
             * @return [AuthenticationResult] for a successful authentication.
             *
             * @throws AuthPromptErrorException  when an unrecoverable error has been encountered and
             * authentication has stopped.
             * @throws AuthPromptFailureException when an authentication attempt by the user has been rejected.
             *
             * @see CredentialAuthPrompt.authenticate(
             *     AuthPromptHost host,
             *     BiometricPrompt.CryptoObject,
             *     AuthPromptCallback
             * )
             *
             * @sample androidx.biometric.samples.auth.credentialAuth
             */
        """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72, 72),
        """
            /**
             * Shows an authentication prompt to the user.
             *
             * @param host A wrapper for the component that will host the prompt.
             * @param crypto A cryptographic object to be associated with this
             *   authentication.
             * @return [AuthenticationResult] for a successful authentication.
             * @throws AuthPromptErrorException when an unrecoverable error has been
             *   encountered and authentication has stopped.
             * @throws AuthPromptFailureException when an authentication attempt by
             *   the user has been rejected.
             * @sample androidx.biometric.samples.auth.credentialAuth
             * @see CredentialAuthPrompt.authenticate( AuthPromptHost host,
             *   BiometricPrompt.CryptoObject, AuthPromptCallback )
             */
            """
            .trimIndent(),
        indent = "",
    )
  }

  @Test
  fun testHtml() {
    // Comment from lint's SourceCodeScanner class doc. Tests a number of
    // things -- markup conversion (
 to ##,
 to blank lines), list item
    // indentation, trimming blank lines from the end, etc.
    val source =
        """
             /**
             * Interface to be implemented by lint detectors that want to analyze
             * Java source files (or other similar source files, such as Kotlin files.)
             *

             * There are several different common patterns for detecting issues:
             *

             *
 Checking calls to a given method. For this see
             * {@link #getApplicableMethodNames()} and
             * {@link #visitMethodCall(JavaContext, UCallExpression, PsiMethod)}

             *
 Instantiating a given class. For this, see
             * {@link #getApplicableConstructorTypes()} and
             * {@link #visitConstructor(JavaContext, UCallExpression, PsiMethod)}

             *
 Referencing a given constant. For this, see
             * {@link #getApplicableReferenceNames()} and
             * {@link #visitReference(JavaContext, UReferenceExpression, PsiElement)}

             *
 Extending a given class or implementing a given interface.
             * For this, see {@link #applicableSuperClasses()} and
             * {@link #visitClass(JavaContext, UClass)}

             *
 More complicated scenarios: perform a general AST
             * traversal with a visitor. In this case, first tell lint which
             * AST node types you're interested in with the
             * {@link #getApplicableUastTypes()} method, and then provide a
             * {@link UElementHandler} from the {@link #createUastHandler(JavaContext)}
             * where you override the various applicable handler methods. This is
             * done rather than a general visitor from the root node to avoid
             * having to have every single lint detector (there are hundreds) do a full
             * tree traversal on its own.

             *

             *

             * {@linkplain SourceCodeScanner} exposes the UAST API to lint checks.
             * UAST is short for "Universal AST" and is an abstract syntax tree library
             * which abstracts away details about Java versus Kotlin versus other similar languages
             * and lets the client of the library access the AST in a unified way.
             *

             * UAST isn't actually a full replacement for PSI; it augments PSI.
             * Essentially, UAST is used for the inside of methods (e.g. method bodies),
             * and things like field initializers. PSI continues to be used at the outer
             * level: for packages, classes, and methods (declarations and signatures).
             * There are also wrappers around some of these for convenience.
             *

             * The {@linkplain SourceCodeScanner} interface reflects this fact. For example,
             * when you indicate that you want to check calls to a method named {@code foo},
             * the call site node is a UAST node (in this case, {@link UCallExpression},
             * but the called method itself is a {@link PsiMethod}, since that method
             * might be anywhere (including in a library that we don't have source for,
             * so UAST doesn't make sense.)
             *

             *
Migrating JavaPsiScanner to SourceCodeScanner

             * As described above, PSI is still used, so a lot of code will remain the
             * same. For example, all resolve methods, including those in UAST, will
             * continue to return PsiElement, not necessarily a UElement. For example,
             * if you resolve a method call or field reference, you'll get a
             * {@link PsiMethod} or {@link PsiField} back.
             *

             * However, the visitor methods have all changed, generally to change
             * to UAST types. For example, the signature
             * {@link JavaPsiScanner#visitMethodCall(JavaContext, JavaElementVisitor, PsiMethodCallExpression, PsiMethod)}
             * should be changed to {@link SourceCodeScanner#visitMethodCall(JavaContext, UCallExpression, PsiMethod)}.
             *

             * Similarly, replace {@link JavaPsiScanner#createPsiVisitor} with {@link SourceCodeScanner#createUastHandler},
             * {@link JavaPsiScanner#getApplicablePsiTypes()} with {@link SourceCodeScanner#getApplicableUastTypes()}, etc.
             *

             * There are a bunch of new methods on classes like {@link JavaContext} which lets
             * you pass in a {@link UElement} to match the existing {@link PsiElement} methods.
             *

             * If you have code which does something specific with PSI classes,
             * the following mapping table in alphabetical order might be helpful, since it lists the
             * corresponding UAST classes.
             *

             *
Mapping between PSI and UAST classes

             *
PSI	UAST

             *
com.intellij.psi.	org.jetbrains.uast.

             *
IElementType	UastBinaryOperator

             *
PsiAnnotation	UAnnotation

             *
PsiAnonymousClass	UAnonymousClass

             *
PsiArrayAccessExpression	UArrayAccessExpression

             *
PsiBinaryExpression	UBinaryExpression

             *
PsiCallExpression	UCallExpression

             *
PsiCatchSection	UCatchClause

             *
PsiClass	UClass

             *
PsiClassObjectAccessExpression	UClassLiteralExpression

             *
PsiConditionalExpression	UIfExpression

             *
PsiDeclarationStatement	UDeclarationsExpression

             *
PsiDoWhileStatement	UDoWhileExpression

             *
PsiElement	UElement

             *
PsiExpression	UExpression

             *
PsiForeachStatement	UForEachExpression

             *
PsiIdentifier	USimpleNameReferenceExpression

             *
PsiIfStatement	UIfExpression

             *
PsiImportStatement	UImportStatement

             *
PsiImportStaticStatement	UImportStatement

             *
PsiJavaCodeReferenceElement	UReferenceExpression

             *
PsiLiteral	ULiteralExpression

             *
PsiLocalVariable	ULocalVariable

             *
PsiMethod	UMethod

             *
PsiMethodCallExpression	UCallExpression

             *
PsiNameValuePair	UNamedExpression

             *
PsiNewExpression	UCallExpression

             *
PsiParameter	UParameter

             *
PsiParenthesizedExpression	UParenthesizedExpression

             *
PsiPolyadicExpression	UPolyadicExpression

             *
PsiPostfixExpression	UPostfixExpression or UUnaryExpression

             *
PsiPrefixExpression	UPrefixExpression or UUnaryExpression

             *
PsiReference	UReferenceExpression

             *
PsiReference	UResolvable

             *
PsiReferenceExpression	UReferenceExpression

             *
PsiReturnStatement	UReturnExpression

             *
PsiSuperExpression	USuperExpression

             *
PsiSwitchLabelStatement	USwitchClauseExpression

             *
PsiSwitchStatement	USwitchExpression

             *
PsiThisExpression	UThisExpression

             *
PsiThrowStatement	UThrowExpression

             *
PsiTryStatement	UTryExpression

             *
PsiTypeCastExpression	UBinaryExpressionWithType

             *
PsiWhileStatement	UWhileExpression

             *

             * Note however that UAST isn't just a "renaming of classes"; there are
             * some changes to the structure of the AST as well. Particularly around
             * calls.
             *
             *
Parents

             * In UAST, you get your parent {@linkplain UElement} by calling
             * {@code getUastParent} instead of {@code getParent}. This is to avoid
             * method name clashes on some elements which are both UAST elements
             * and PSI elements at the same time - such as {@link UMethod}.
             *
Children

             * When you're going in the opposite direction (e.g. you have a {@linkplain PsiMethod}
             * and you want to look at its content, you should not use
             * {@link PsiMethod#getBody()}. This will only give you the PSI child content,
             * which won't work for example when dealing with Kotlin methods.
             * Normally lint passes you the {@linkplain UMethod} which you should be procesing
             * instead. But if for some reason you need to look up the UAST method
             * body from a {@linkplain PsiMethod}, use this:
             *

             *     UastContext context = UastUtils.getUastContext(element);
             *     UExpression body = context.getMethodBody(method);
             *

             * Similarly if you have a {@link PsiField} and you want to look up its field
             * initializer, use this:
             *

             *     UastContext context = UastUtils.getUastContext(element);
             *     UExpression initializer = context.getInitializerBody(field);
             *

             *
             *
Call names

             * In PSI, a call was represented by a PsiCallExpression, and to get to things
             * like the method called or to the operand/qualifier, you'd first need to get
             * the "method expression". In UAST there is no method expression and this
             * information is available directly on the {@linkplain UCallExpression} element.
             * Therefore, here's how you'd change the code:
             *

             * <    call.getMethodExpression().getReferenceName();
             * ---
             * >    call.getMethodName()
             *

             *
Call qualifiers

             * Similarly,
             *

             * <    call.getMethodExpression().getQualifierExpression();
             * ---
             * >    call.getReceiver()
             *

             *
Call arguments

             * PSI had a separate PsiArgumentList element you had to look up before you could
             * get to the actual arguments, as an array. In UAST these are available directly on
             * the call, and are represented as a list instead of an array.
             *

             * <    PsiExpression[] args = call.getArgumentList().getExpressions();
             * ---
             * >    List<UExpression> args = call.getValueArguments();
             *

             * Typically you also need to go through your code and replace array access,
             * arg\[i], with list access, {@code arg.get(i)}. Or in Kotlin, just arg\[i]...
             *
             *
Instanceof

             * You may have code which does something like "parent instanceof PsiAssignmentExpression"
             * to see if something is an assignment. Instead, use one of the many utilities
             * in {@link UastExpressionUtils} - such as {@link UastExpressionUtils#isAssignment(UElement)}.
             * Take a look at all the methods there now - there are methods for checking whether
             * a call is a constructor, whether an expression is an array initializer, etc etc.
             *
             *
Android Resources

             * Don't do your own AST lookup to figure out if something is a reference to
             * an Android resource (e.g. see if the class refers to an inner class of a class
             * named "R" etc.)  There is now a new utility class which handles this:
             * {@link ResourceReference}. Here's an example of code which has a {@link UExpression}
             * and wants to know it's referencing a R.styleable resource:
             *

             *        ResourceReference reference = ResourceReference.get(expression);
             *        if (reference == null || reference.getType() != ResourceType.STYLEABLE) {
             *            return;
             *        }
             *        ...
             *

             *
             *
Binary Expressions

             * If you had been using {@link PsiBinaryExpression} for things like checking comparator
             * operators or arithmetic combination of operands, you can replace this with
             * {@link UBinaryExpression}. But you normally shouldn't; you should use
             * {@link UPolyadicExpression} instead. A polyadic expression is just like a binary
             * expression, but possibly with more than two terms. With the old parser backend,
             * an expression like "A + B + C" would be represented by nested binary expressions
             * (first A + B, then a parent element which combined that binary expression with C).
             * However, this will now be provided as a {@link UPolyadicExpression} instead. And
             * the binary case is handled trivially without the need to special case it.
             *
Method name changes

             * The following table maps some common method names and what their corresponding
             * names are in UAST.
             *

             *
Mapping between PSI and UAST method names

             *
PSI	UAST

             *
getArgumentList	getValueArguments

             *
getCatchSections	getCatchClauses

             *
getDeclaredElements	getDeclarations

             *
getElseBranch	getElseExpression

             *
getInitializer	getUastInitializer

             *
getLExpression	getLeftOperand

             *
getOperationTokenType	getOperator

             *
getOwner	getUastParent

             *
getParent	getUastParent

             *
getRExpression	getRightOperand

             *
getReturnValue	getReturnExpression

             *
getText	asSourceString

             *
getThenBranch	getThenExpression

             *
getType	getExpressionType

             *
getTypeParameters	getTypeArguments

             *
resolveMethod	resolve

             *

             *
Handlers versus visitors

             * If you are processing a method on your own, or even a full class, you should switch
             * from JavaRecursiveElementVisitor to AbstractUastVisitor.
             * However, most lint checks don't do their own full AST traversal; they instead
             * participate in a shared traversal of the tree, registering element types they're
             * interested with using {@link #getApplicableUastTypes()} and then providing
             * a visitor where they implement the corresponding visit methods. However, from
             * these visitors you should not be calling super.visitX. To remove this
             * whole confusion, lint now provides a separate class, {@link UElementHandler}.
             * For the shared traversal, just provide this handler instead and implement the
             * appropriate visit methods. It will throw an error if you register element types
             * in {@linkplain #getApplicableUastTypes()} that you don't override.
             *
             *

             *
Migrating JavaScanner to SourceCodeScanner

             * First read the javadoc on how to convert from the older {@linkplain JavaScanner}
             * interface over to {@linkplain JavaPsiScanner}. While {@linkplain JavaPsiScanner} is itself
             * deprecated, it's a lot closer to {@link SourceCodeScanner} so a lot of the same concepts
             * apply; then follow the above section.
             *

             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(120, 120),
        """
            /**
             * Interface to be implemented by lint detectors that want to analyze Java source files (or other similar source
             * files, such as Kotlin files.)
             *
             * There are several different common patterns for detecting issues:
             *

             *
 Checking calls to a given method. For this see [getApplicableMethodNames] and [visitMethodCall]

             *
 Instantiating a given class. For this, see [getApplicableConstructorTypes] and [visitConstructor]

             *
 Referencing a given constant. For this, see [getApplicableReferenceNames] and [visitReference]

             *
 Extending a given class or implementing a given interface. For this, see [applicableSuperClasses] and
             *   [visitClass]

             *
 More complicated scenarios: perform a general AST traversal with a visitor. In this case, first tell lint
             *   which AST node types you're interested in with the [getApplicableUastTypes] method, and then provide a
             *   [UElementHandler] from the [createUastHandler] where you override the various applicable handler methods. This
             *   is done rather than a general visitor from the root node to avoid having to have every single lint detector
             *   (there are hundreds) do a full tree traversal on its own.

             *

             *
             * {@linkplain SourceCodeScanner} exposes the UAST API to lint checks. UAST is short for "Universal AST" and is an
             * abstract syntax tree library which abstracts away details about Java versus Kotlin versus other similar languages
             * and lets the client of the library access the AST in a unified way.
             *
             * UAST isn't actually a full replacement for PSI; it **augments** PSI. Essentially, UAST is used for the **inside**
             * of methods (e.g. method bodies), and things like field initializers. PSI continues to be used at the outer level:
             * for packages, classes, and methods (declarations and signatures). There are also wrappers around some of these
             * for convenience.
             *
             * The {@linkplain SourceCodeScanner} interface reflects this fact. For example, when you indicate that you want to
             * check calls to a method named {@code foo}, the call site node is a UAST node (in this case, [UCallExpression],
             * but the called method itself is a [PsiMethod], since that method might be anywhere (including in a library that
             * we don't have source for, so UAST doesn't make sense.)
             *
             * ## Migrating JavaPsiScanner to SourceCodeScanner
             * As described above, PSI is still used, so a lot of code will remain the same. For example, all resolve methods,
             * including those in UAST, will continue to return PsiElement, not necessarily a UElement. For example, if you
             * resolve a method call or field reference, you'll get a [PsiMethod] or [PsiField] back.
             *
             * However, the visitor methods have all changed, generally to change to UAST types. For example, the signature
             * [JavaPsiScanner.visitMethodCall] should be changed to [SourceCodeScanner.visitMethodCall].
             *
             * Similarly, replace [JavaPsiScanner.createPsiVisitor] with [SourceCodeScanner.createUastHandler],
             * [JavaPsiScanner.getApplicablePsiTypes] with [SourceCodeScanner.getApplicableUastTypes], etc.
             *
             * There are a bunch of new methods on classes like [JavaContext] which lets you pass in a [UElement] to match the
             * existing [PsiElement] methods.
             *
             * If you have code which does something specific with PSI classes, the following mapping table in alphabetical
             * order might be helpful, since it lists the corresponding UAST classes.
             *

             *
Mapping between PSI and UAST classes

             *
PSI	UAST

             *
com.intellij.psi.	org.jetbrains.uast.

             *
IElementType	UastBinaryOperator

             *
PsiAnnotation	UAnnotation

             *
PsiAnonymousClass	UAnonymousClass

             *
PsiArrayAccessExpression	UArrayAccessExpression

             *
PsiBinaryExpression	UBinaryExpression

             *
PsiCallExpression	UCallExpression

             *
PsiCatchSection	UCatchClause

             *
PsiClass	UClass

             *
PsiClassObjectAccessExpression	UClassLiteralExpression

             *
PsiConditionalExpression	UIfExpression

             *
PsiDeclarationStatement	UDeclarationsExpression

             *
PsiDoWhileStatement	UDoWhileExpression

             *
PsiElement	UElement

             *
PsiExpression	UExpression

             *
PsiForeachStatement	UForEachExpression

             *
PsiIdentifier	USimpleNameReferenceExpression

             *
PsiIfStatement	UIfExpression

             *
PsiImportStatement	UImportStatement

             *
PsiImportStaticStatement	UImportStatement

             *
PsiJavaCodeReferenceElement	UReferenceExpression

             *
PsiLiteral	ULiteralExpression

             *
PsiLocalVariable	ULocalVariable

             *
PsiMethod	UMethod

             *
PsiMethodCallExpression	UCallExpression

             *
PsiNameValuePair	UNamedExpression

             *
PsiNewExpression	UCallExpression

             *
PsiParameter	UParameter

             *
PsiParenthesizedExpression	UParenthesizedExpression

             *
PsiPolyadicExpression	UPolyadicExpression

             *
PsiPostfixExpression	UPostfixExpression or UUnaryExpression

             *
PsiPrefixExpression	UPrefixExpression or UUnaryExpression

             *
PsiReference	UReferenceExpression

             *
PsiReference	UResolvable

             *
PsiReferenceExpression	UReferenceExpression

             *
PsiReturnStatement	UReturnExpression

             *
PsiSuperExpression	USuperExpression

             *
PsiSwitchLabelStatement	USwitchClauseExpression

             *
PsiSwitchStatement	USwitchExpression

             *
PsiThisExpression	UThisExpression

             *
PsiThrowStatement	UThrowExpression

             *
PsiTryStatement	UTryExpression

             *
PsiTypeCastExpression	UBinaryExpressionWithType

             *
PsiWhileStatement	UWhileExpression

 Note however that UAST isn't just a
             * "renaming of classes"; there are some changes to the structure of the AST as well. Particularly around calls.
             *
             * ### Parents
             * In UAST, you get your parent {@linkplain UElement} by calling {@code getUastParent} instead of {@code getParent}.
             * This is to avoid method name clashes on some elements which are both UAST elements and PSI elements at the same
             * time - such as [UMethod].
             *
             * ### Children
             * When you're going in the opposite direction (e.g. you have a {@linkplain PsiMethod} and you want to look at its
             * content, you should **not** use [PsiMethod.getBody]. This will only give you the PSI child content, which won't
             * work for example when dealing with Kotlin methods. Normally lint passes you the {@linkplain UMethod} which you
             * should be procesing instead. But if for some reason you need to look up the UAST method body from a {@linkplain
             * PsiMethod}, use this:
             * ```
             *     UastContext context = UastUtils.getUastContext(element);
             *     UExpression body = context.getMethodBody(method);
             * ```
             *
             * Similarly if you have a [PsiField] and you want to look up its field initializer, use this:
             * ```
             *     UastContext context = UastUtils.getUastContext(element);
             *     UExpression initializer = context.getInitializerBody(field);
             * ```
             *
             * ### Call names
             * In PSI, a call was represented by a PsiCallExpression, and to get to things like the method called or to the
             * operand/qualifier, you'd first need to get the "method expression". In UAST there is no method expression and
             * this information is available directly on the {@linkplain UCallExpression} element. Therefore, here's how you'd
             * change the code:
             * ```
             * <    call.getMethodExpression().getReferenceName();
             * ---
             * >    call.getMethodName()
             * ```
             *
             * ### Call qualifiers
             * Similarly,
             * ```
             * <    call.getMethodExpression().getQualifierExpression();
             * ---
             * >    call.getReceiver()
             * ```
             *
             * ### Call arguments
             * PSI had a separate PsiArgumentList element you had to look up before you could get to the actual arguments, as an
             * array. In UAST these are available directly on the call, and are represented as a list instead of an array.
             *
             * ```
             * <    PsiExpression[] args = call.getArgumentList().getExpressions();
             * ---
             * >    List<UExpression> args = call.getValueArguments();
             * ```
             *
             * Typically you also need to go through your code and replace array access, arg\[i], with list access, {@code
             * arg.get(i)}. Or in Kotlin, just arg\[i]...
             *
             * ### Instanceof
             * You may have code which does something like "parent instanceof PsiAssignmentExpression" to see if
             * something is an assignment. Instead, use one of the many utilities in [UastExpressionUtils] - such
             * as [UastExpressionUtils.isAssignment]. Take a look at all the methods there now - there are methods
             * for checking whether a call is a constructor, whether an expression is an array initializer, etc etc.
             *
             * ### Android Resources
             * Don't do your own AST lookup to figure out if something is a reference to an Android resource (e.g. see if the
             * class refers to an inner class of a class named "R" etc.) There is now a new utility class which handles this:
             * [ResourceReference]. Here's an example of code which has a [UExpression] and wants to know it's referencing a
             * R.styleable resource:
             * ```
             *        ResourceReference reference = ResourceReference.get(expression);
             *        if (reference == null || reference.getType() != ResourceType.STYLEABLE) {
             *            return;
             *        }
             *        ...
             * ```
             *
             * ### Binary Expressions
             * If you had been using [PsiBinaryExpression] for things like checking comparator operators or arithmetic
             * combination of operands, you can replace this with [UBinaryExpression]. **But you normally shouldn't; you should
             * use [UPolyadicExpression] instead**. A polyadic expression is just like a binary expression, but possibly with
             * more than two terms. With the old parser backend, an expression like "A + B + C" would be represented by nested
             * binary expressions (first A + B, then a parent element which combined that binary expression with C). However,
             * this will now be provided as a [UPolyadicExpression] instead. And the binary case is handled trivially without
             * the need to special case it.
             *
             * ### Method name changes
             * The following table maps some common method names and what their corresponding names are in UAST.
             *

             *
Mapping between PSI and UAST method names

             *
PSI	UAST

             *
getArgumentList	getValueArguments

             *
getCatchSections	getCatchClauses

             *
getDeclaredElements	getDeclarations

             *
getElseBranch	getElseExpression

             *
getInitializer	getUastInitializer

             *
getLExpression	getLeftOperand

             *
getOperationTokenType	getOperator

             *
getOwner	getUastParent

             *
getParent	getUastParent

             *
getRExpression	getRightOperand

             *
getReturnValue	getReturnExpression

             *
getText	asSourceString

             *
getThenBranch	getThenExpression

             *
getType	getExpressionType

             *
getTypeParameters	getTypeArguments

             *
resolveMethod	resolve

             *
             * ### Handlers versus visitors
             * If you are processing a method on your own, or even a full class, you should switch from
             * JavaRecursiveElementVisitor to AbstractUastVisitor. However, most lint checks don't do their own full AST
             * traversal; they instead participate in a shared traversal of the tree, registering element types they're
             * interested with using [getApplicableUastTypes] and then providing a visitor where they implement the
             * corresponding visit methods. However, from these visitors you should **not** be calling super.visitX. To remove
             * this whole confusion, lint now provides a separate class, [UElementHandler]. For the shared traversal, just
             * provide this handler instead and implement the appropriate visit methods. It will throw an error if you register
             * element types in {@linkplain #getApplicableUastTypes()} that you don't override.
             *
             * ### Migrating JavaScanner to SourceCodeScanner
             * First read the javadoc on how to convert from the older {@linkplain JavaScanner} interface over to {@linkplain
             * JavaPsiScanner}. While {@linkplain JavaPsiScanner} is itself deprecated, it's a lot closer to [SourceCodeScanner]
             * so a lot of the same concepts apply; then follow the above section.
             */
            """
            .trimIndent(),
        // {@link} tags are not rendered from [references] when Dokka cannot resolve the symbols
        verifyDokka = false)
  }

  @Test
  fun testPreserveParagraph() {
    // Make sure that when we convert
, it's preserved.
    val source =
        """
             /**
             *

             *
test

             *

             *

             * After.
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(120, 120),
        """
            /**
             *

             *
test

             *

             *
             * After.
             */
            """
            .trimIndent())
  }

  @Test
  fun testWordJoining() {
    // "-" alone can mean beginning of a list, but not as part of a word
    val source =
        """
            /**
             * which you can render with something like this:
             * `dot -Tpng -o/tmp/graph.png toString.dot`
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(65),
        """
            /**
             * which you can render with something like this: `dot -Tpng
             * -o/tmp/graph.png toString.dot`
             */
            """
            .trimIndent())

    val source2 =
        """
            /**
             * ABCDE which you can render with something like this:
             * `dot - Tpng -o/tmp/graph.png toString.dot`
             */
            """
            .trimIndent()
    checkFormatter(
        source2,
        KDocFormattingOptions(65),
        """
            /**
             * ABCDE which you can render with something like this:
             * `dot - Tpng -o/tmp/graph.png toString.dot`
             */
            """
            .trimIndent())
  }

  @Test
  fun testEarlyBreakForTodo() {
    // Don't break before a TODO
    val source =
        """
            /**
             * This is a long line that will break a little early to breaking at TODO:
             *
             * This is a long line that wont break a little early to breaking at DODO:
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72, 72).apply { optimal = false },
        """
            /**
             * This is a long line that will break a little early to breaking
             * at TODO:
             *
             * This is a long line that wont break a little early to breaking at
             * DODO:
             */
            """
            .trimIndent())
  }

  @Test
  fun testPreformat() {
    // Don't join preformatted text with previous TODO comment
    val source =
        """
            /**
             * TODO: Work.
             * ```
             * Preformatted.
             *
             * More preformatted.
             * ```
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72, 72),
        """
            /**
             * TODO: Work.
             *
             * ```
             * Preformatted.
             *
             * More preformatted.
             * ```
             */
            """
            .trimIndent())
  }

  @Test
  fun testConvertLinks() {
    // Make sure we convert {@link} and NOT {@linkplain} if convertMarkup is true.
    val source =
        """
            /**
             * {@link SourceCodeScanner} exposes the UAST API to lint checks.
             * The {@link SourceCodeScanner} interface reflects this fact.
             *
             * {@linkplain SourceCodeScanner} exposes the UAST API to lint checks.
             * The {@linkplain SourceCodeScanner} interface reflects this fact.
             *
             * It will throw an error if you register element types in
             * {@link #getApplicableUastTypes()} that you don't override.
             *
             * First read the javadoc on how to convert from the older {@link
             * JavaScanner} interface over to {@link JavaPsiScanner}.
             *
             * 1. A file header, which is the exact contents of {@link FILE_HEADER} encoded
             *     as ASCII characters.
             *
             * Given an error message produced by this lint detector for the
             * given issue type, determines whether this corresponds to the
             * warning (produced by {@link #reportBaselineIssues(LintDriver,
             * Project)} above) that one or more issues have been
             * fixed (present in baseline but not in project.)
             *
             * {@link #getQualifiedName(PsiClass)} method.
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72, 72),
        """
            /**
             * [SourceCodeScanner] exposes the UAST API to lint checks. The
             * [SourceCodeScanner] interface reflects this fact.
             *
             * {@linkplain SourceCodeScanner} exposes the UAST API to lint
             * checks. The {@linkplain SourceCodeScanner} interface reflects
             * this fact.
             *
             * It will throw an error if you register element types in
             * [getApplicableUastTypes] that you don't override.
             *
             * First read the javadoc on how to convert from the older
             * [JavaScanner] interface over to [JavaPsiScanner].
             * 1. A file header, which is the exact contents of [FILE_HEADER]
             *    encoded as ASCII characters.
             *
             * Given an error message produced by this lint detector for the
             * given issue type, determines whether this corresponds to the
             * warning (produced by [reportBaselineIssues] above) that one or
             * more issues have been fixed (present in baseline but not in
             * project.)
             *
             * [getQualifiedName] method.
             */
            """
            .trimIndent(),
        // When dokka cannot resolve the links it doesn't render {@link} which makes
        // before and after not match
        verifyDokka = false)
  }

  @Test
  fun testNestedBullets() {
    // Regression test for https://github.com/tnorbye/kdoc-formatter/issues/36
    val source =
        """
            /**
             * Paragraph
             * * Top Bullet
             *    * Sub-Bullet 1
             *    * Sub-Bullet 2
             *       * Sub-Sub-Bullet 1
             * 1. Top level
             *    1. First item
             *    2. Second item
             */
            """
            .trimIndent()

    checkFormatter(
        source,
        KDocFormattingOptions(72, 72),
        """
            /**
             * Paragraph
             * * Top Bullet
             *    * Sub-Bullet 1
             *    * Sub-Bullet 2
             *       * Sub-Sub-Bullet 1
             * 1. Top level
             *    1. First item
             *    2. Second item
             */
            """
            .trimIndent())

    checkFormatter(
        source,
        KDocFormattingOptions(72, 72).apply { nestedListIndent = 4 },
        """
            /**
             * Paragraph
             * * Top Bullet
             *     * Sub-Bullet 1
             *     * Sub-Bullet 2
             *         * Sub-Sub-Bullet 1
             * 1. Top level
             *     1. First item
             *     2. Second item
             */
            """
            .trimIndent())
  }

  @Test
  fun testTripledQuotedPrefixNotBreakable() {
    // Corresponds to b/189247595
    val source =
        """
            /**
             * Gets current ABCD Workspace information from the output of ```abcdtools info```.
             *
             * Migrated from
             * http://com.example
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72, 72),
        """
            /**
             * Gets current ABCD Workspace information from the output
             * of ```abcdtools info```.
             *
             * Migrated from http://com.example
             */
            """
            .trimIndent())
  }

  @Test
  fun testGreedyLineBreak() {
    // Make sure we correctly break at the max line width
    val source =
        """
            /**
             * Handles a chain of qualified expressions, i.e. `a[5].b!!.c()[4].f()`
             *
             * This is by far the most complicated part of this formatter. We start by breaking the expression
             * to the steps it is executed in (which are in the opposite order of how the syntax tree is
             * built).
             *
             * We then calculate information to know which parts need to be groups, and finally go part by
             * part, emitting it to the [builder] while closing and opening groups.
             *
             * @param brokeBeforeBrace used for tracking if a break was taken right before the lambda
             * expression. Useful for scoping functions where we want good looking indentation. For example,
             * here we have correct indentation before `bar()` and `car()` because we can detect the break
             * after the equals:
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(100, 100).apply { optimal = false },
        """
            /**
             * Handles a chain of qualified expressions, i.e. `a[5].b!!.c()[4].f()`
             *
             * This is by far the most complicated part of this formatter. We start by breaking the
             * expression to the steps it is executed in (which are in the opposite order of how the syntax
             * tree is built).
             *
             * We then calculate information to know which parts need to be groups, and finally go part by
             * part, emitting it to the [builder] while closing and opening groups.
             *
             * @param brokeBeforeBrace used for tracking if a break was taken right before the lambda
             *   expression. Useful for scoping functions where we want good looking indentation. For
             *   example, here we have correct indentation before `bar()` and `car()` because we can detect
             *   the break after the equals:
             */
             """
            .trimIndent())
  }

  @Test
  fun test193246766() {
    val source =
        // Nonsensical text derived from the original using the lorem() method and
        // replacing same-length & same capitalization words from lorem ipsum
        """
            /**
             * * Do do occaecat sunt in culpa:
             *   * Id id reprehenderit cillum non `adipiscing` enim enim ad occaecat
             *   * Cupidatat non officia anim adipiscing enim non reprehenderit in officia est:
             *     * Do non officia anim voluptate esse non mollit mollit id tempor, enim u consequat. irure
             *     in occaecat
             *     * Cupidatat, in qui officia anim voluptate esse eu fugiat fugiat in mollit, anim anim id
             *     occaecat
             * * In h anim id laborum:
             *   * Do non sunt voluptate esse non culpa mollit id tempor, enim u consequat. irure in occaecat
             *   * Cupidatat, in qui anim voluptate esse non culpa mollit est do tempor, enim enim ad occaecat
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72, 72),
        """
            /**
             * * Do do occaecat sunt in culpa:
             *    * Id id reprehenderit cillum non `adipiscing` enim enim ad
             *      occaecat
             *    * Cupidatat non officia anim adipiscing enim non reprehenderit
             *      in officia est:
             *       * Do non officia anim voluptate esse non mollit mollit id
             *         tempor, enim u consequat. irure in occaecat
             *       * Cupidatat, in qui officia anim voluptate esse eu fugiat
             *         fugiat in mollit, anim anim id occaecat
             * * In h anim id laborum:
             *    * Do non sunt voluptate esse non culpa mollit id tempor, enim
             *      u consequat. irure in occaecat
             *    * Cupidatat, in qui anim voluptate esse non culpa mollit est
             *      do tempor, enim enim ad occaecat
             */
            """
            .trimIndent(),
        // We indent the last bullets as if they are nested list items; this
        // is likely the intent (though with indent only being 2, dokka would
        // interpret it as top level text.)
        verifyDokka = false)
  }

  @Test
  fun test203584301() {
    // https://github.com/facebookincubator/ktfmt/issues/310
    val source =
        """
            /**
             * This is my SampleInterface interface.
             * @sample com.example.java.sample.library.extra.long.path.MyCustomSampleInterfaceImplementationForTesting
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72, 72),
        """
            /**
             * This is my SampleInterface interface.
             *
             * @sample com.example.java.sample.library.extra.long.path.MyCustomSampleInterfaceImplementationForTesting
             */
            """
            .trimIndent())
  }

  @Test
  fun test209435082() {
    // b/209435082
    val source =
        // Nonsensical text derived from the original using the lorem() method and
        // replacing same-length & same capitalization words from lorem ipsum
        """
            /**
             * eiusmod.com
             * - - -
             * PARIATUR_MOLLIT
             * - - -
             * Laborum: 1.4
             * - - -
             * Pariatur:
             * https://officia.officia.com
             * https://id.laborum.laborum.com
             * https://sit.eiusmod.com
             * https://non-in.officia.com
             * https://anim.laborum.com
             * https://exercitation.ullamco.com
             * - - -
             * Adipiscing do tempor:
             * - NON: IN/IN
             * - in 2IN officia? EST
             * - do EIUSMOD eiusmod? NON
             * - Mollit est do incididunt Nostrud non? IN
             * - Mollit pariatur pariatur culpa? QUI
             * - - -
             * Lorem eiusmod magna/adipiscing:
             * - Do eiusmod magna/adipiscing
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72, 72),
        """
            /**
             * eiusmod.com
             * - - -
             * PARIATUR_MOLLIT
             * - - -
             * Laborum: 1.4
             * - - -
             * Pariatur: https://officia.officia.com
             * https://id.laborum.laborum.com https://sit.eiusmod.com
             * https://non-in.officia.com https://anim.laborum.com
             * https://exercitation.ullamco.com
             * - - -
             * Adipiscing do tempor:
             * - NON: IN/IN
             * - in 2IN officia? EST
             * - do EIUSMOD eiusmod? NON
             * - Mollit est do incididunt Nostrud non? IN
             * - Mollit pariatur pariatur culpa? QUI
             * - - -
             * Lorem eiusmod magna/adipiscing:
             * - Do eiusmod magna/adipiscing
             */
            """
            .trimIndent())
  }

  @Test
  fun test236743270() {
    val source =
        // Nonsensical text derived from the original using the lorem() method and
        // replacing same-length & same capitalization words from lorem ipsum
        """
            /**
             * @return Amet do non adipiscing sed consequat duis non Officia ID (amet sed consequat non
             * adipiscing sed eiusmod), magna consequat.
             */
            """
            .trimIndent()
    val lorem = loremize(source)
    assertThat(lorem).isEqualTo(source)
    checkFormatter(
        source,
        KDocFormattingOptions(72, 72),
        """
            /**
             * @return Amet do non adipiscing sed consequat duis non Officia ID
             *   (amet sed consequat non adipiscing sed eiusmod), magna
             *   consequat.
             */
            """
            .trimIndent())
  }

  @Test
  fun test238279769() {
    val source =
        // Nonsensical text derived from the original using the lorem() method and
        // replacing same-length & same capitalization words from lorem ipsum
        """
            /**
             * @property dataItemOrderRandomizer sit tempor enim pariatur non culpa id [Pariatur]z in qui anim.
             *  Anim id-lorem sit magna [Consectetur] pariatur.
             * @property randomBytesProvider non mollit anim pariatur non culpa qui qui `mollit` lorem amet
             *   consectetur [Pariatur]z in IssuerSignedItem culpa.
             * @property preserveMapOrder officia id pariatur non culpa id lorem pariatur culpa culpa id o est
             *    amet consectetur sed sed do ENIM minim.
             * @property reprehenderit p esse cillum officia est do enim enim nostrud nisi d non sunt mollit id
             *     est tempor enim.
             */
            """
            .trimIndent()
    checkFormatter(
        source,
        KDocFormattingOptions(72, 72),
        """
            /**
             * @property dataItemOrderRandomizer sit tempor enim pariatur non
             *   culpa id [Pariatur]z in qui anim. Anim id-lorem sit magna
             *   [Consectetur] pariatur.
             * @property randomBytesProvider non mollit anim pariatur non culpa
             *   qui qui `mollit` lorem amet consectetur [Pariatur]z in
             *   IssuerSignedItem culpa.
             * @property preserveMapOrder officia id pariatur non culpa id lorem
             *   pariatur culpa culpa id o est amet consectetur sed sed do ENIM
             *   minim.
             * @property reprehenderit p esse cillum officia est do enim enim
             *   nostrud nisi d non sunt mollit id est tempor enim.
             */
            """
            .trimIndent())
  }

  @Test
  fun testKnit() {
    // Some tests for the knit plugin -- https://github.com/Kotlin/kotlinx-knit
    val source =
        """
           /**
            *
            *
            *
            *
            *
            * ```kotlin
            * fun exit(): Nothing = exitProcess(0)
            * ```
            *
            *
            *
            *
            *
            *
            *
            *
            * [captureOutput]: https://example.com/kotlinx-knit-test/kotlinx.knit.test/capture-output.html
            *
            *
            * Make sure we never line break
             *
             *
             *
             * ```kotlin
             * fun exit(): Nothing = exitProcess(0)
             * ```
             *
             *
             *
             *
             *
             *
             *
             *
             * [captureOutput]:
             * https://example.com/kotlinx-knit-test/kotlinx.knit.test/capture-output.html
             *
             *
             * Make sure we never line break