aboutsummaryrefslogtreecommitdiff
path: root/core/src/test/java/com/facebook/ktfmt/kdoc/KDocFormatterTest.kt
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/test/java/com/facebook/ktfmt/kdoc/KDocFormatterTest.kt')
-rw-r--r--core/src/test/java/com/facebook/ktfmt/kdoc/KDocFormatterTest.kt4706
1 files changed, 4690 insertions, 16 deletions
diff --git a/core/src/test/java/com/facebook/ktfmt/kdoc/KDocFormatterTest.kt b/core/src/test/java/com/facebook/ktfmt/kdoc/KDocFormatterTest.kt
index 4efdf42..bb1b156 100644
--- a/core/src/test/java/com/facebook/ktfmt/kdoc/KDocFormatterTest.kt
+++ b/core/src/test/java/com/facebook/ktfmt/kdoc/KDocFormatterTest.kt
@@ -1,5 +1,21 @@
/*
- * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * 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.
@@ -16,28 +32,4686 @@
package com.facebook.ktfmt.kdoc
-import com.facebook.ktfmt.kdoc.KDocFormatter.tokenizeKdocText
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
+ *
+ * <pre>
+ * line one
+ * ```
+ * line two
+ * ```
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(72),
+ """
+ /**
+ * Some summary
+ * <pre>
+ * 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
+ *
+ * <pre>
+ * ```
+ * code
+ * ```
+ * </pre>
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(72),
+ """
+ /**
+ * Some summary
+ * <pre>
+ * ```
+ * code
+ * ```
+ * </pre>
+ */
+ """
+ .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 testTokenizeKdocText() {
- assertThat(tokenizeKdocText(" one two three ").asIterable())
- .containsExactly(" ", "one", " ", "two", " ", "three", " ")
- .inOrder()
- assertThat(tokenizeKdocText("one two three ").asIterable())
- .containsExactly("one", " ", "two", " ", "three", " ")
- .inOrder()
- assertThat(tokenizeKdocText("one two three").asIterable())
- .containsExactly("one", " ", "two", " ", "three")
- .inOrder()
- assertThat(tokenizeKdocText("onetwothree").asIterable())
- .containsExactly("onetwothree")
- .inOrder()
- assertThat(tokenizeKdocText("").asIterable()).isEmpty()
+ 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:
+ * <PRE>
+ * val s = "hello, and this is code so should not be line broken at all, it should stay on one line";
+ * println(s);
+ * </pre>
+ * 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(),
+ // <pre> 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
+ * ```
+ * <i>preformatted text</i>
+ * ```
+ * 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
+ *
+ * ```
+ * <i>preformatted text</i>
+ * ```
+ *
+ * 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(
+ """
+ /**
+ * <ul>
+ * <li>Incremental merge will never clean the output.
+ * <li>The inputs must be able to tell which changes to relative files have been made.
+ * <li>Intermediate state must be saved between merges.
+ * </ul>
+ */
+ """
+ .trimIndent(),
+ KDocFormattingOptions(maxLineWidth = 100, maxCommentWidth = 60),
+ """
+ /**
+ * <ul>
+ * <li>Incremental merge will never clean the output.
+ * <li>The inputs must be able to tell which changes to
+ * relative files have been made.
+ * <li>Intermediate state must be saved between merges.
+ * </ul>
+ */
+ """
+ .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.
+ * <p>
+ * To generate this, run for example
+ * <pre>
+ * ./gradlew :app:dependencies
+ * </pre>
+ * 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 (<h2> to ##, <p> 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.)
+ * <p>
+ * There are several different common patterns for detecting issues:
+ * <ul>
+ * <li> Checking calls to a given method. For this see
+ * {@link #getApplicableMethodNames()} and
+ * {@link #visitMethodCall(JavaContext, UCallExpression, PsiMethod)}</li>
+ * <li> Instantiating a given class. For this, see
+ * {@link #getApplicableConstructorTypes()} and
+ * {@link #visitConstructor(JavaContext, UCallExpression, PsiMethod)}</li>
+ * <li> Referencing a given constant. For this, see
+ * {@link #getApplicableReferenceNames()} and
+ * {@link #visitReference(JavaContext, UReferenceExpression, PsiElement)}</li>
+ * <li> Extending a given class or implementing a given interface.
+ * For this, see {@link #applicableSuperClasses()} and
+ * {@link #visitClass(JavaContext, UClass)}</li>
+ * <li> 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.</li>
+ * </ul>
+ * <p>
+ * {@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.
+ * <p>
+ * UAST isn't actually a full replacement for PSI; it <b>augments</b> PSI.
+ * Essentially, UAST is used for the <b>inside</b> 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.
+ * <p>
+ * 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.)
+ * <p>
+ * <h2>Migrating JavaPsiScanner to SourceCodeScanner</h2>
+ * 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.
+ * <p>
+ * 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)}.
+ * <p>
+ * Similarly, replace {@link JavaPsiScanner#createPsiVisitor} with {@link SourceCodeScanner#createUastHandler},
+ * {@link JavaPsiScanner#getApplicablePsiTypes()} with {@link SourceCodeScanner#getApplicableUastTypes()}, etc.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <table>
+ * <caption>Mapping between PSI and UAST classes</caption>
+ * <tr><th>PSI</th><th>UAST</th></tr>
+ * <tr><th>com.intellij.psi.</th><th>org.jetbrains.uast.</th></tr>
+ * <tr><td>IElementType</td><td>UastBinaryOperator</td></tr>
+ * <tr><td>PsiAnnotation</td><td>UAnnotation</td></tr>
+ * <tr><td>PsiAnonymousClass</td><td>UAnonymousClass</td></tr>
+ * <tr><td>PsiArrayAccessExpression</td><td>UArrayAccessExpression</td></tr>
+ * <tr><td>PsiBinaryExpression</td><td>UBinaryExpression</td></tr>
+ * <tr><td>PsiCallExpression</td><td>UCallExpression</td></tr>
+ * <tr><td>PsiCatchSection</td><td>UCatchClause</td></tr>
+ * <tr><td>PsiClass</td><td>UClass</td></tr>
+ * <tr><td>PsiClassObjectAccessExpression</td><td>UClassLiteralExpression</td></tr>
+ * <tr><td>PsiConditionalExpression</td><td>UIfExpression</td></tr>
+ * <tr><td>PsiDeclarationStatement</td><td>UDeclarationsExpression</td></tr>
+ * <tr><td>PsiDoWhileStatement</td><td>UDoWhileExpression</td></tr>
+ * <tr><td>PsiElement</td><td>UElement</td></tr>
+ * <tr><td>PsiExpression</td><td>UExpression</td></tr>
+ * <tr><td>PsiForeachStatement</td><td>UForEachExpression</td></tr>
+ * <tr><td>PsiIdentifier</td><td>USimpleNameReferenceExpression</td></tr>
+ * <tr><td>PsiIfStatement</td><td>UIfExpression</td></tr>
+ * <tr><td>PsiImportStatement</td><td>UImportStatement</td></tr>
+ * <tr><td>PsiImportStaticStatement</td><td>UImportStatement</td></tr>
+ * <tr><td>PsiJavaCodeReferenceElement</td><td>UReferenceExpression</td></tr>
+ * <tr><td>PsiLiteral</td><td>ULiteralExpression</td></tr>
+ * <tr><td>PsiLocalVariable</td><td>ULocalVariable</td></tr>
+ * <tr><td>PsiMethod</td><td>UMethod</td></tr>
+ * <tr><td>PsiMethodCallExpression</td><td>UCallExpression</td></tr>
+ * <tr><td>PsiNameValuePair</td><td>UNamedExpression</td></tr>
+ * <tr><td>PsiNewExpression</td><td>UCallExpression</td></tr>
+ * <tr><td>PsiParameter</td><td>UParameter</td></tr>
+ * <tr><td>PsiParenthesizedExpression</td><td>UParenthesizedExpression</td></tr>
+ * <tr><td>PsiPolyadicExpression</td><td>UPolyadicExpression</td></tr>
+ * <tr><td>PsiPostfixExpression</td><td>UPostfixExpression or UUnaryExpression</td></tr>
+ * <tr><td>PsiPrefixExpression</td><td>UPrefixExpression or UUnaryExpression</td></tr>
+ * <tr><td>PsiReference</td><td>UReferenceExpression</td></tr>
+ * <tr><td>PsiReference</td><td>UResolvable</td></tr>
+ * <tr><td>PsiReferenceExpression</td><td>UReferenceExpression</td></tr>
+ * <tr><td>PsiReturnStatement</td><td>UReturnExpression</td></tr>
+ * <tr><td>PsiSuperExpression</td><td>USuperExpression</td></tr>
+ * <tr><td>PsiSwitchLabelStatement</td><td>USwitchClauseExpression</td></tr>
+ * <tr><td>PsiSwitchStatement</td><td>USwitchExpression</td></tr>
+ * <tr><td>PsiThisExpression</td><td>UThisExpression</td></tr>
+ * <tr><td>PsiThrowStatement</td><td>UThrowExpression</td></tr>
+ * <tr><td>PsiTryStatement</td><td>UTryExpression</td></tr>
+ * <tr><td>PsiTypeCastExpression</td><td>UBinaryExpressionWithType</td></tr>
+ * <tr><td>PsiWhileStatement</td><td>UWhileExpression</td></tr>
+ * </table>
+ * 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.
+ *
+ * <h3>Parents</h3>
+ * 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}.
+ * <h3>Children</h3>
+ * 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 <b>not</b> 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:
+ * <pre>
+ * UastContext context = UastUtils.getUastContext(element);
+ * UExpression body = context.getMethodBody(method);
+ * </pre>
+ * Similarly if you have a {@link PsiField} and you want to look up its field
+ * initializer, use this:
+ * <pre>
+ * UastContext context = UastUtils.getUastContext(element);
+ * UExpression initializer = context.getInitializerBody(field);
+ * </pre>
+ *
+ * <h3>Call names</h3>
+ * 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:
+ * <pre>
+ * &lt; call.getMethodExpression().getReferenceName();
+ * ---
+ * &gt; call.getMethodName()
+ * </pre>
+ * <h3>Call qualifiers</h3>
+ * Similarly,
+ * <pre>
+ * &lt; call.getMethodExpression().getQualifierExpression();
+ * ---
+ * &gt; call.getReceiver()
+ * </pre>
+ * <h3>Call arguments</h3>
+ * 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.
+ * <pre>
+ * &lt; PsiExpression[] args = call.getArgumentList().getExpressions();
+ * ---
+ * &gt; List<UExpression> args = call.getValueArguments();
+ * </pre>
+ * 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]...
+ *
+ * <h3>Instanceof</h3>
+ * 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.
+ *
+ * <h3>Android Resources</h3>
+ * 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:
+ * <pre>
+ * ResourceReference reference = ResourceReference.get(expression);
+ * if (reference == null || reference.getType() != ResourceType.STYLEABLE) {
+ * return;
+ * }
+ * ...
+ * </pre>
+ *
+ * <h3>Binary Expressions</h3>
+ * 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}. <b>But you normally shouldn't; you should use
+ * {@link UPolyadicExpression} instead</b>. 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.
+ * <h3>Method name changes</h3>
+ * The following table maps some common method names and what their corresponding
+ * names are in UAST.
+ * <table>
+ * <caption>Mapping between PSI and UAST method names</caption></caption>
+ * <tr><th>PSI</th><th>UAST</th></tr>
+ * <tr><td>getArgumentList</td><td>getValueArguments</td></tr>
+ * <tr><td>getCatchSections</td><td>getCatchClauses</td></tr>
+ * <tr><td>getDeclaredElements</td><td>getDeclarations</td></tr>
+ * <tr><td>getElseBranch</td><td>getElseExpression</td></tr>
+ * <tr><td>getInitializer</td><td>getUastInitializer</td></tr>
+ * <tr><td>getLExpression</td><td>getLeftOperand</td></tr>
+ * <tr><td>getOperationTokenType</td><td>getOperator</td></tr>
+ * <tr><td>getOwner</td><td>getUastParent</td></tr>
+ * <tr><td>getParent</td><td>getUastParent</td></tr>
+ * <tr><td>getRExpression</td><td>getRightOperand</td></tr>
+ * <tr><td>getReturnValue</td><td>getReturnExpression</td></tr>
+ * <tr><td>getText</td><td>asSourceString</td></tr>
+ * <tr><td>getThenBranch</td><td>getThenExpression</td></tr>
+ * <tr><td>getType</td><td>getExpressionType</td></tr>
+ * <tr><td>getTypeParameters</td><td>getTypeArguments</td></tr>
+ * <tr><td>resolveMethod</td><td>resolve</td></tr>
+ * </table>
+ * <h3>Handlers versus visitors</h3>
+ * 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 <b>not</b> 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.
+ *
+ * <p>
+ * <h3>Migrating JavaScanner to SourceCodeScanner</h3>
+ * 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.
+ * <p>
+ */
+ """
+ .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:
+ * <ul>
+ * <li> Checking calls to a given method. For this see [getApplicableMethodNames] and [visitMethodCall]</li>
+ * <li> Instantiating a given class. For this, see [getApplicableConstructorTypes] and [visitConstructor]</li>
+ * <li> Referencing a given constant. For this, see [getApplicableReferenceNames] and [visitReference]</li>
+ * <li> Extending a given class or implementing a given interface. For this, see [applicableSuperClasses] and
+ * [visitClass]</li>
+ * <li> 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.</li>
+ * </ul>
+ *
+ * {@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.
+ * <table>
+ * <caption>Mapping between PSI and UAST classes</caption>
+ * <tr><th>PSI</th><th>UAST</th></tr>
+ * <tr><th>com.intellij.psi.</th><th>org.jetbrains.uast.</th></tr>
+ * <tr><td>IElementType</td><td>UastBinaryOperator</td></tr>
+ * <tr><td>PsiAnnotation</td><td>UAnnotation</td></tr>
+ * <tr><td>PsiAnonymousClass</td><td>UAnonymousClass</td></tr>
+ * <tr><td>PsiArrayAccessExpression</td><td>UArrayAccessExpression</td></tr>
+ * <tr><td>PsiBinaryExpression</td><td>UBinaryExpression</td></tr>
+ * <tr><td>PsiCallExpression</td><td>UCallExpression</td></tr>
+ * <tr><td>PsiCatchSection</td><td>UCatchClause</td></tr>
+ * <tr><td>PsiClass</td><td>UClass</td></tr>
+ * <tr><td>PsiClassObjectAccessExpression</td><td>UClassLiteralExpression</td></tr>
+ * <tr><td>PsiConditionalExpression</td><td>UIfExpression</td></tr>
+ * <tr><td>PsiDeclarationStatement</td><td>UDeclarationsExpression</td></tr>
+ * <tr><td>PsiDoWhileStatement</td><td>UDoWhileExpression</td></tr>
+ * <tr><td>PsiElement</td><td>UElement</td></tr>
+ * <tr><td>PsiExpression</td><td>UExpression</td></tr>
+ * <tr><td>PsiForeachStatement</td><td>UForEachExpression</td></tr>
+ * <tr><td>PsiIdentifier</td><td>USimpleNameReferenceExpression</td></tr>
+ * <tr><td>PsiIfStatement</td><td>UIfExpression</td></tr>
+ * <tr><td>PsiImportStatement</td><td>UImportStatement</td></tr>
+ * <tr><td>PsiImportStaticStatement</td><td>UImportStatement</td></tr>
+ * <tr><td>PsiJavaCodeReferenceElement</td><td>UReferenceExpression</td></tr>
+ * <tr><td>PsiLiteral</td><td>ULiteralExpression</td></tr>
+ * <tr><td>PsiLocalVariable</td><td>ULocalVariable</td></tr>
+ * <tr><td>PsiMethod</td><td>UMethod</td></tr>
+ * <tr><td>PsiMethodCallExpression</td><td>UCallExpression</td></tr>
+ * <tr><td>PsiNameValuePair</td><td>UNamedExpression</td></tr>
+ * <tr><td>PsiNewExpression</td><td>UCallExpression</td></tr>
+ * <tr><td>PsiParameter</td><td>UParameter</td></tr>
+ * <tr><td>PsiParenthesizedExpression</td><td>UParenthesizedExpression</td></tr>
+ * <tr><td>PsiPolyadicExpression</td><td>UPolyadicExpression</td></tr>
+ * <tr><td>PsiPostfixExpression</td><td>UPostfixExpression or UUnaryExpression</td></tr>
+ * <tr><td>PsiPrefixExpression</td><td>UPrefixExpression or UUnaryExpression</td></tr>
+ * <tr><td>PsiReference</td><td>UReferenceExpression</td></tr>
+ * <tr><td>PsiReference</td><td>UResolvable</td></tr>
+ * <tr><td>PsiReferenceExpression</td><td>UReferenceExpression</td></tr>
+ * <tr><td>PsiReturnStatement</td><td>UReturnExpression</td></tr>
+ * <tr><td>PsiSuperExpression</td><td>USuperExpression</td></tr>
+ * <tr><td>PsiSwitchLabelStatement</td><td>USwitchClauseExpression</td></tr>
+ * <tr><td>PsiSwitchStatement</td><td>USwitchExpression</td></tr>
+ * <tr><td>PsiThisExpression</td><td>UThisExpression</td></tr>
+ * <tr><td>PsiThrowStatement</td><td>UThrowExpression</td></tr>
+ * <tr><td>PsiTryStatement</td><td>UTryExpression</td></tr>
+ * <tr><td>PsiTypeCastExpression</td><td>UBinaryExpressionWithType</td></tr>
+ * <tr><td>PsiWhileStatement</td><td>UWhileExpression</td></tr> </table> 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:
+ * ```
+ * &lt; call.getMethodExpression().getReferenceName();
+ * ---
+ * &gt; call.getMethodName()
+ * ```
+ *
+ * ### Call qualifiers
+ * Similarly,
+ * ```
+ * &lt; call.getMethodExpression().getQualifierExpression();
+ * ---
+ * &gt; 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.
+ *
+ * ```
+ * &lt; PsiExpression[] args = call.getArgumentList().getExpressions();
+ * ---
+ * &gt; 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.
+ * <table>
+ * <caption>Mapping between PSI and UAST method names</caption></caption>
+ * <tr><th>PSI</th><th>UAST</th></tr>
+ * <tr><td>getArgumentList</td><td>getValueArguments</td></tr>
+ * <tr><td>getCatchSections</td><td>getCatchClauses</td></tr>
+ * <tr><td>getDeclaredElements</td><td>getDeclarations</td></tr>
+ * <tr><td>getElseBranch</td><td>getElseExpression</td></tr>
+ * <tr><td>getInitializer</td><td>getUastInitializer</td></tr>
+ * <tr><td>getLExpression</td><td>getLeftOperand</td></tr>
+ * <tr><td>getOperationTokenType</td><td>getOperator</td></tr>
+ * <tr><td>getOwner</td><td>getUastParent</td></tr>
+ * <tr><td>getParent</td><td>getUastParent</td></tr>
+ * <tr><td>getRExpression</td><td>getRightOperand</td></tr>
+ * <tr><td>getReturnValue</td><td>getReturnExpression</td></tr>
+ * <tr><td>getText</td><td>asSourceString</td></tr>
+ * <tr><td>getThenBranch</td><td>getThenExpression</td></tr>
+ * <tr><td>getType</td><td>getExpressionType</td></tr>
+ * <tr><td>getTypeParameters</td><td>getTypeArguments</td></tr>
+ * <tr><td>resolveMethod</td><td>resolve</td></tr> </table>
+ *
+ * ### 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 <p>, it's preserved.
+ val source =
+ """
+ /**
+ * <ul>
+ * <li>test</li>
+ * </ul>
+ * <p>
+ * After.
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(120, 120),
+ """
+ /**
+ * <ul>
+ * <li>test</li>
+ * </ul>
+ *
+ * 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 =
+ """
+ /**
+ * <!--- <directive> [<parameters>] -->
+ * <!--- <directive> [<parameters>]
+ * Some text here.
+ * This should all be merged into one
+ * line.
+ * -->
+ * <!--- super long text here; this not be broken into lines; super long text here super long text here super long text here super long text here -->
+ *
+ * <!--- INCLUDE
+ * import kotlin.system.*
+ * -->
+ * ```kotlin
+ * fun exit(): Nothing = exitProcess(0)
+ * ```
+ * <!--- PREFIX -->
+ * <!--- TEST_NAME BasicTest -->
+ * <!--- TEST
+ * Hello, world!
+ * -->
+ * <!--- TEST lines.single().toInt() in 1..100 -->
+ * <!--- TOC -->
+ * <!--- END -->
+ * <!--- MODULE kotlinx-knit-test -->
+ * <!--- INDEX kotlinx.knit.test -->
+ * [captureOutput]: https://example.com/kotlinx-knit-test/kotlinx.knit.test/capture-output.html
+ * <!--- END -->
+ *
+ * Make sure we never line break <!--- to the beginning a line: <!--- <!--- <!--- end.
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(72, 72),
+ """
+ /**
+ * <!--- <directive> [<parameters>] -->
+ * <!--- <directive> [<parameters>]
+ * Some text here. This should all be merged into one line.
+ * -->
+ * <!--- super long text here; this not be broken into lines; super long text here super long text here super long text here super long text here -->
+ * <!--- INCLUDE
+ * import kotlin.system.*
+ * -->
+ * ```kotlin
+ * fun exit(): Nothing = exitProcess(0)
+ * ```
+ * <!--- PREFIX -->
+ * <!--- TEST_NAME BasicTest -->
+ * <!--- TEST
+ * Hello, world!
+ * -->
+ * <!--- TEST lines.single().toInt() in 1..100 -->
+ * <!--- TOC -->
+ * <!--- END -->
+ * <!--- MODULE kotlinx-knit-test -->
+ * <!--- INDEX kotlinx.knit.test -->
+ * [captureOutput]:
+ * https://example.com/kotlinx-knit-test/kotlinx.knit.test/capture-output.html
+ * <!--- END -->
+ *
+ * Make sure we never line break <!--- to the beginning a
+ * line: <!--- <!--- <!--- end.
+ */
+ """
+ .trimIndent())
+ }
+
+ @Test
+ fun testNPE() {
+ // Reproduces formatting bug found in androidx' SplashScreen.kt:
+ val source =
+ """
+ /**
+ * ## Specs
+ * - With icon background (`Theme.SplashScreen.IconBackground`)
+ * + Image Size: 240x240 dp
+ * + Inner Circle diameter: 160 dp
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(72, 72),
+ """
+ /**
+ * ## Specs
+ * - With icon background (`Theme.SplashScreen.IconBackground`)
+ * + Image Size: 240x240 dp
+ * + Inner Circle diameter: 160 dp
+ */
+ """
+ .trimIndent())
+ }
+
+ @Test
+ fun testExtraNewlines() {
+ // Reproduced a bug which was inserting extra newlines in preformatted text
+ val source =
+ """
+ /**
+ * Simple helper class useful for creating a message bundle for your module.
+ *
+ * It creates a soft reference to an underlying text bundle, which means that it can
+ * be garbage collected if needed (although it will be reallocated again if you request
+ * a new message from it).
+ *
+ * You might use it like so:
+ *
+ * ```
+ * # In module 'custom'...
+ *
+ * # resources/messages/CustomBundle.properties:
+ * sample.text.key=This is a sample text value.
+ *
+ * # src/messages/CustomBundle.kt:
+ * private const val BUNDLE_NAME = "messages.CustomBundle"
+ * object CustomBundle {
+ * private val bundleRef = MessageBundleReference(BUNDLE_NAME)
+ * fun message(@PropertyKey(resourceBundle = BUNDLE_NAME) key: String, vararg params: Any) = bundleRef.message(key, *params)
+ * }
+ * ```
+ *
+ * That's it! Now you can call `CustomBundle.message("sample.text.key")` to fetch the text value.
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(72, 72),
+ """
+ /**
+ * Simple helper class useful for creating a message bundle for your
+ * module.
+ *
+ * It creates a soft reference to an underlying text bundle, which
+ * means that it can be garbage collected if needed (although it
+ * will be reallocated again if you request a new message from it).
+ *
+ * You might use it like so:
+ * ```
+ * # In module 'custom'...
+ *
+ * # resources/messages/CustomBundle.properties:
+ * sample.text.key=This is a sample text value.
+ *
+ * # src/messages/CustomBundle.kt:
+ * private const val BUNDLE_NAME = "messages.CustomBundle"
+ * object CustomBundle {
+ * private val bundleRef = MessageBundleReference(BUNDLE_NAME)
+ * fun message(@PropertyKey(resourceBundle = BUNDLE_NAME) key: String, vararg params: Any) = bundleRef.message(key, *params)
+ * }
+ * ```
+ *
+ * That's it! Now you can call
+ * `CustomBundle.message("sample.text.key")`
+ * to fetch the text value.
+ */
+ """
+ .trimIndent())
+ }
+
+ @Test
+ fun testQuotedBug() {
+ // Reproduced a bug which was mishandling quoted strings: when you have
+ // *separate* but adjacent quoted lists, make sure we preserve line break
+ // between them
+ val source =
+ """
+ /**
+ * Eg:
+ * > anydpi-v26 &emsp; | &emsp; Adaptive icon - ic_launcher.xml
+ *
+ *
+ * > hdpi &emsp;&emsp;&emsp;&emsp;&nbsp; | &emsp; Mip Map File - ic_launcher.png
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(100, 72),
+ """
+ /**
+ * Eg:
+ * > anydpi-v26 &emsp; | &emsp; Adaptive icon - ic_launcher.xml
+ *
+ * > hdpi &emsp;&emsp;&emsp;&emsp;&nbsp; | &emsp; Mip Map File -
+ * > ic_launcher.png
+ */
+ """
+ .trimIndent(),
+ indent = " ")
+ }
+
+ @Test
+ fun testListBreaking() {
+ // If we have, in a list, "* very-long-word", we cannot break this line
+ // with a bullet on its line by itself. In the below, prior to the bug fix,
+ // the "- spec:width..." would get split into "-" and "spec:width..." on
+ // its own hanging indent line.
+ val source =
+ """
+ /**
+ * In other words, completes the parameters so that either of these declarations can be achieved:
+ * - spec:width=...,height=...,dpi=...,isRound=...,chinSize=...,orientation=...
+ * - spec:parent=...,orientation=...
+ * > spec:width=...,height=...,dpi=...,isRound=...,chinSize=...,orientation=...
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(100, 72),
+ """
+ /**
+ * In other words, completes the parameters so that either of these
+ * declarations can be achieved:
+ * - spec:width=...,height=...,dpi=...,isRound=...,chinSize=...,orientation=...
+ * - spec:parent=...,orientation=...
+ * > spec:width=...,height=...,dpi=...,isRound=...,chinSize=...,orientation=...
+ */
+ """
+ .trimIndent(),
+ indent = "")
+ }
+
+ @Test
+ fun testNewList() {
+ // Make sure we never place "1)" or "+" at the beginning of a new line.
+ val source =
+ """
+ /**
+ * Handles both the START_ALLOC_TRACKING and STOP_ALLOC_TRACKING commands in tests. This is responsible for generating a status event.
+ * For the start tracking command, if |trackStatus| is set to be |SUCCESS|, this generates a start event with timestamp matching what is
+ * specified in |trackStatus|. For the end tracking command, an event (start timestamp + 1) is only added if a start event already
+ * exists in the input event list.
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(100, 72),
+ """
+ /**
+ * Handles both the START_ALLOC_TRACKING and STOP_ALLOC_TRACKING commands
+ * in tests. This is responsible for generating a status event. For the
+ * start tracking command, if |trackStatus| is set to be |SUCCESS|, this
+ * generates a start event with timestamp matching what is specified
+ * in |trackStatus|. For the end tracking command, an event (start
+ * timestamp + 1) is only added if a start event already exists in the
+ * input event list.
+ */
+ """
+ .trimIndent(),
+ indent = "")
+ }
+
+ @Test
+ fun testSplashScreen() {
+ val source =
+ """
+ /**
+ * Provides control over the splash screen once the application is started.
+ *
+ * On API 31+ (Android 12+) this class calls the platform methods.
+ *
+ * Prior API 31, the platform behavior is replicated with the exception of the Animated Vector
+ * Drawable support on the launch screen.
+ *
+ * # Usage of the `core-splashscreen` library:
+ *
+ * To replicate the splash screen behavior from Android 12 on older APIs the following steps need to
+ * be taken:
+ * 1. Create a new Theme (e.g `Theme.App.Starting`) and set its parent to `Theme.SplashScreen` or
+ * `Theme.SplashScreen.IconBackground`
+ *
+ * 2. In your manifest, set the `theme` attribute of the whole `<application>` or just the
+ * starting `<activity>` to `Theme.App.Starting`
+ *
+ * 3. In the `onCreate` method the starting activity, call [installSplashScreen] just before
+ * `super.onCreate()`. You also need to make sure that `postSplashScreenTheme` is set
+ * to the application's theme. Alternatively, this call can be replaced by [Activity#setTheme]
+ * if a [SplashScreen] instance isn't needed.
+ *
+ * ## Themes
+ *
+ * The library provides two themes: [R.style.Theme_SplashScreen] and
+ * [R.style.Theme_SplashScreen_IconBackground]. If you wish to display a background right under
+ * your icon, the later needs to be used. This ensure that the scale and masking of the icon are
+ * similar to the Android 12 Splash Screen.
+ *
+ * `windowSplashScreenAnimatedIcon`: The splash screen icon. On API 31+ it can be an animated
+ * vector drawable.
+ *
+ * `windowSplashScreenAnimationDuration`: Duration of the Animated Icon Animation. The value
+ * needs to be > 0 if the icon is animated.
+ *
+ * **Note:** This has no impact on the time during which the splash screen is displayed and is
+ * only used in [SplashScreenViewProvider.iconAnimationDurationMillis]. If you need to display the
+ * splash screen for a longer time, you can use [SplashScreen.setKeepOnScreenCondition]
+ *
+ * `windowSplashScreenIconBackgroundColor`: _To be used in with
+ * `Theme.SplashScreen.IconBackground`_. Sets a background color under the splash screen icon.
+ *
+ * `windowSplashScreenBackground`: Background color of the splash screen. Defaults to the theme's
+ * `?attr/colorBackground`.
+ *
+ * `postSplashScreenTheme`* Theme to apply to the Activity when [installSplashScreen] is called.
+ *
+ * **Known incompatibilities:**
+ * - On API < 31, `windowSplashScreenAnimatedIcon` cannot be animated. If you want to provide an
+ * animated icon for API 31+ and a still icon for API <31, you can do so by overriding the still
+ * icon with an animated vector drawable in `res/drawable-v31`.
+ *
+ * - On API < 31, if the value of `windowSplashScreenAnimatedIcon` is an
+ * [adaptive icon](http://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive)
+ * , it will be cropped and scaled. The workaround is to respectively assign
+ * `windowSplashScreenAnimatedIcon` and `windowSplashScreenIconBackgroundColor` to the values of
+ * the adaptive icon `foreground` and `background`.
+ *
+ * - On API 21-22, The icon isn't displayed until the application starts, only the background is
+ * visible.
+ *
+ * # Design
+ * The splash screen icon uses the same specifications as
+ * [Adaptive Icons](https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive)
+ * . This means that the icon needs to fit within a circle whose diameter is 2/3 the size of the
+ * icon. The actual values don't really matter if you use a vector icon.
+ *
+ * ## Specs
+ * - With icon background (`Theme.SplashScreen.IconBackground`)
+ * + Image Size: 240x240 dp
+ * + Inner Circle diameter: 160 dp
+ * - Without icon background (`Theme.SplashScreen`)
+ * + Image size: 288x288 dp
+ * + Inner circle diameter: 192 dp
+ *
+ * _Example:_ if the full size of the image is 300dp*300dp, the icon needs to fit within a
+ * circle with a diameter of 200dp. Everything outside the circle will be invisible (masked).
+ *
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(72, 72),
+ """
+ /**
+ * Provides control over the splash screen once the application is
+ * started.
+ *
+ * On API 31+ (Android 12+) this class calls the platform methods.
+ *
+ * Prior API 31, the platform behavior is replicated with the
+ * exception of the Animated Vector Drawable support on the launch
+ * screen.
+ *
+ * # Usage of the `core-splashscreen` library:
+ *
+ * To replicate the splash screen behavior from Android 12 on older
+ * APIs the following steps need to be taken:
+ * 1. Create a new Theme (e.g `Theme.App.Starting`) and set its
+ * parent to `Theme.SplashScreen` or
+ * `Theme.SplashScreen.IconBackground`
+ * 2. In your manifest, set the `theme` attribute of the whole
+ * `<application>` or just the starting `<activity>` to
+ * `Theme.App.Starting`
+ * 3. In the `onCreate` method the starting activity, call
+ * [installSplashScreen] just before `super.onCreate()`. You also
+ * need to make sure that `postSplashScreenTheme` is set to the
+ * application's theme. Alternatively, this call can be replaced
+ * by [Activity#setTheme] if a [SplashScreen] instance isn't
+ * needed.
+ *
+ * ## Themes
+ *
+ * The library provides two themes: [R.style.Theme_SplashScreen]
+ * and [R.style.Theme_SplashScreen_IconBackground]. If you wish to
+ * display a background right under your icon, the later needs to
+ * be used. This ensure that the scale and masking of the icon are
+ * similar to the Android 12 Splash Screen.
+ *
+ * `windowSplashScreenAnimatedIcon`: The splash screen icon. On API
+ * 31+ it can be an animated vector drawable.
+ *
+ * `windowSplashScreenAnimationDuration`: Duration of the Animated
+ * Icon Animation. The value needs to be > 0 if the icon is
+ * animated.
+ *
+ * **Note:** This has no impact on the time during which
+ * the splash screen is displayed and is only used in
+ * [SplashScreenViewProvider.iconAnimationDurationMillis]. If you
+ * need to display the splash screen for a longer time, you can use
+ * [SplashScreen.setKeepOnScreenCondition]
+ *
+ * `windowSplashScreenIconBackgroundColor`: _To be used in with
+ * `Theme.SplashScreen.IconBackground`_. Sets a background color
+ * under the splash screen icon.
+ *
+ * `windowSplashScreenBackground`: Background color of the splash
+ * screen. Defaults to the theme's `?attr/colorBackground`.
+ *
+ * `postSplashScreenTheme`* Theme to apply to the Activity when
+ * [installSplashScreen] is called.
+ *
+ * **Known incompatibilities:**
+ * - On API < 31, `windowSplashScreenAnimatedIcon` cannot be
+ * animated. If you want to provide an animated icon for API 31+
+ * and a still icon for API <31, you can do so by overriding the
+ * still icon with an animated vector drawable in
+ * `res/drawable-v31`.
+ * - On API < 31, if the value of `windowSplashScreenAnimatedIcon`
+ * is an
+ * [adaptive icon](http://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive)
+ * , it will be cropped and scaled. The workaround is to
+ * respectively assign `windowSplashScreenAnimatedIcon` and
+ * `windowSplashScreenIconBackgroundColor` to the values of the
+ * adaptive icon `foreground` and `background`.
+ * - On API 21-22, The icon isn't displayed until the application
+ * starts, only the background is visible.
+ *
+ * # Design
+ * The splash screen icon uses the same specifications as
+ * [Adaptive Icons](https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive)
+ * . This means that the icon needs to fit within a circle
+ * whose diameter is 2/3 the size of the icon. The actual
+ * values don't really matter if you use a vector icon.
+ *
+ * ## Specs
+ * - With icon background (`Theme.SplashScreen.IconBackground`)
+ * + Image Size: 240x240 dp
+ * + Inner Circle diameter: 160 dp
+ * - Without icon background (`Theme.SplashScreen`)
+ * + Image size: 288x288 dp
+ * + Inner circle diameter: 192 dp
+ *
+ * _Example:_ if the full size of the image is 300dp*300dp, the icon
+ * needs to fit within a circle with a diameter of 200dp. Everything
+ * outside the circle will be invisible (masked).
+ */
+ """
+ .trimIndent())
+ }
+
+ @Test
+ fun testRaggedIndentation() {
+ // From Dokka's plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt
+ val source =
+ """
+ /**
+ * We would like to know if we need to have a space after a this tag
+ *
+ * The space is required when:
+ * - tag spans multiple lines, between every line we would need a space
+ *
+ * We wouldn't like to render a space if:
+ * - tag is followed by an end of comment
+ * - after a tag there is another tag (eg. multiple @author tags)
+ * - they end with an html tag like: <a href="...">Something</a> since then the space will be displayed in the following text
+ * - next line starts with a <p> or <pre> token
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(72, 72),
+ """
+ /**
+ * We would like to know if we need to have a space after a this tag
+ *
+ * The space is required when:
+ * - tag spans multiple lines, between every line we would need a
+ * space
+ *
+ * We wouldn't like to render a space if:
+ * - tag is followed by an end of comment
+ * - after a tag there is another tag (eg. multiple @author tags)
+ * - they end with an html tag like: <a href="...">Something</a>
+ * since then the space will be displayed in the following text
+ * - next line starts with a <p> or <pre> token
+ */
+ """
+ .trimIndent())
+ }
+
+ @Test
+ fun testCustomKDocTag() {
+ // From Dokka's core/testdata/comments/multilineSection.kt
+ val source =
+ """
+ /**
+ * Summary
+ * @one
+ * line one
+ * line two
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(72, 72),
+ """
+ /**
+ * Summary
+ *
+ * @one line one line two
+ */
+ """
+ .trimIndent())
+ }
+
+ @Test
+ fun testTables() {
+ val source =
+ """
+ /**
+ * ### Tables
+ * column 1 | column 2
+ * ---------|---------
+ * value\| 1 | value 2
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(40),
+ """
+ /**
+ * ### Tables
+ * | column 1 | column 2 |
+ * |-----------|----------|
+ * | value\| 1 | value 2 |
+ */
+ """
+ .trimIndent())
+ }
+
+ @Test
+ fun testTableMixedWithHtml() {
+ // https://stackoverflow.com/questions/19950648/how-to-write-lists-inside-a-markdown-table
+ val source =
+ """
+ /**
+ | Tables | Are | Cool |
+ | ------------- |:-------------:| -----:|
+ | col 3 is | right-aligned | 1600 |
+ | col 2 is | centered | 12 |
+ | zebra stripes | are neat | 1 |
+ | <ul><li>item1</li><li>item2</li></ul>| See the list | from the first column|
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(100),
+ """
+ /**
+ * | Tables | Are | Cool |
+ * |---------------------------------------|:-------------:|----------------------:|
+ * | col 3 is | right-aligned | 1600 |
+ * | col 2 is | centered | 12 |
+ * | zebra stripes | are neat | 1 |
+ * | <ul><li>item1</li><li>item2</li></ul> | See the list | from the first column |
+ */
+ """
+ .trimIndent())
+
+ // Reduce formatting width to 40; table won't fit, but we'll skip the padding
+ checkFormatter(
+ source,
+ KDocFormattingOptions(40),
+ """
+ /**
+ * |Tables |Are |Cool |
+ * |-------------------------------------|:-----------:|--------------------:|
+ * |col 3 is |right-aligned| 1600|
+ * |col 2 is | centered | 12|
+ * |zebra stripes | are neat | 1|
+ * |<ul><li>item1</li><li>item2</li></ul>|See the list |from the first column|
+ */
+ """
+ .trimIndent())
+
+ checkFormatter(
+ source,
+ KDocFormattingOptions(40).apply { alignTableColumns = false },
+ """
+ /**
+ * | Tables | Are | Cool |
+ * | ------------- |:-------------:| -----:|
+ * | col 3 is | right-aligned | 1600 |
+ * | col 2 is | centered | 12 |
+ * | zebra stripes | are neat | 1 |
+ * | <ul><li>item1</li><li>item2</li></ul>| See the list | from the first column|
+ */
+ """
+ .trimIndent())
+ }
+
+ @Test
+ fun testTableExtraCells() {
+ // If there are extra columns in a row (after the header and divider),
+ // preserve these (though Dokka will drop them from the rendering); don't
+ // widen the table to accommodate it.
+ val source =
+ """
+ /**
+ | Tables | Are | Cool |
+ | ------------- |:-------------:| -----:|
+ | col 3 is | right-aligned | 1600 |
+ | col 2 is | centered | 12 | extra
+ | zebra stripes | are neat | 1 |
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(100),
+ """
+ /**
+ * | Tables | Are | Cool |
+ * |---------------|:-------------:|-----:|
+ * | col 3 is | right-aligned | 1600 |
+ * | col 2 is | centered | 12 | extra |
+ * | zebra stripes | are neat | 1 |
+ */
+ """
+ .trimIndent())
+ }
+
+ @Test
+ fun testTables2() {
+ // See https://github.com/Kotlin/dokka/issues/199
+ val source =
+ """
+ /**
+ * | Level | Color |
+ * | ----- | ----- |
+ * | ERROR | RED |
+ * | WARN | YELLOW |
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(40),
+ """
+ /**
+ * | Level | Color |
+ * |-------|--------|
+ * | ERROR | RED |
+ * | WARN | YELLOW |
+ */
+ """
+ .trimIndent())
+
+ // With alignTableColumns=false, leave formatting within table cells alone
+ checkFormatter(
+ source,
+ KDocFormattingOptions(40).apply { alignTableColumns = false },
+ """
+ /**
+ * | Level | Color |
+ * | ----- | ----- |
+ * | ERROR | RED |
+ * | WARN | YELLOW |
+ */
+ """
+ .trimIndent())
+ }
+
+ @Test
+ fun testTables3() {
+ val source =
+ """
+ /**
+ * Line Before
+ * # test
+ * |column 1 | column 2 | column3
+ * |---|---|---
+ * value 1 | value 3
+ * this is missing
+ * this is more
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(40).apply { alignTableColumns = true },
+ """
+ /**
+ * Line Before
+ *
+ * # test
+ * | column 1 | column 2 | column3 |
+ * |----------|----------|---------|
+ * | value 1 | value 3 | |
+ *
+ * this is missing this is more
+ */
+ """
+ .trimIndent())
+
+ checkFormatter(
+ source,
+ KDocFormattingOptions(40).apply { alignTableColumns = false },
+ """
+ /**
+ * Line Before
+ *
+ * # test
+ * |column 1 | column 2 | column3
+ * |---|---|---
+ * value 1 | value 3
+ *
+ * this is missing this is more
+ */
+ """
+ .trimIndent())
+ }
+
+ @Test
+ fun testTables4() {
+ // Test short dividers (:--, :-:, --:)
+ val source =
+ """
+ /**
+ * ### Tables
+ * column 1 | column 2 | column3
+ * :-:|--:|:--
+ * cell 1|cell2|cell3
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(40),
+ """
+ /**
+ * ### Tables
+ * | column 1 | column 2 | column3 |
+ * |:--------:|---------:|---------|
+ * | cell 1 | cell2 | cell3 |
+ */
+ """
+ .trimIndent(),
+ // Dokka doesn't actually handle this right; it looks for ---
+ verifyDokka = false)
+ }
+
+ @Test
+ fun testTablesEmptyCells() {
+ // Checks what happens with blank cells (here in column 0 on the last row). Test case from
+ // Studio's
+ // designer/testSrc/com/android/tools/idea/uibuilder/property/testutils/AndroidAttributeTypeLookup.kt
+ val source =
+ """
+ /**
+ * | Function | Type | Notes |
+ * | -------------------------------- | ------------------------------- | --------------------------------------|
+ * | TypedArray.getDrawable | NlPropertyType.DRAWABLE | |
+ * | TypedArray.getColor | NlPropertyType.COLOR | Make sure this is not a color list !! |
+ * | TypedArray.getColorStateList | NlPropertyType.COLOR_STATE_LIST | |
+ * | TypedArray.getDimensionPixelSize | NlPropertyType.DIMENSION | |
+ * | TypedArray.getResourceId | NlPropertyType.ID | |
+ * | TypedArray.getInt | NlPropertyType.ENUM | If attrs.xml defines this as an enum |
+ * | | NlPropertyType.INTEGER | If this is not an enum |
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(72),
+ """
+ /**
+ * |Function |Type |Notes |
+ * |--------------------------------|-------------------------------|-------------------------------------|
+ * |TypedArray.getDrawable |NlPropertyType.DRAWABLE | |
+ * |TypedArray.getColor |NlPropertyType.COLOR |Make sure this is not a color list !!|
+ * |TypedArray.getColorStateList |NlPropertyType.COLOR_STATE_LIST| |
+ * |TypedArray.getDimensionPixelSize|NlPropertyType.DIMENSION | |
+ * |TypedArray.getResourceId |NlPropertyType.ID | |
+ * |TypedArray.getInt |NlPropertyType.ENUM |If attrs.xml defines this as an enum |
+ * | |NlPropertyType.INTEGER |If this is not an enum |
+ */
+ """
+ .trimIndent())
+ }
+
+ @Test
+ fun testTables5() {
+ // Test case from Studio's
+ // project-system-gradle-upgrade/src/com/android/tools/idea/gradle/project/upgrade/AgpUpgradeRefactoringProcessor.kt
+ val source =
+ """
+ /**
+ | 1 | 2 | 3 | 4 | Necessity
+ |---|---|---|---|----------
+ |v_n|v_o|cur|new| [IRRELEVANT_PAST]
+ |cur|new|v_n|v_o| [IRRELEVANT_FUTURE]
+ |cur|v_n|v_o|new| [MANDATORY_CODEPENDENT] (must do the refactoring in the same action as the AGP version upgrade)
+ |v_n|cur|v_o|new| [MANDATORY_INDEPENDENT] (must do the refactoring, but can do it before the AGP version upgrade)
+ |cur|v_n|new|v_o| [OPTIONAL_CODEPENDENT] (need not do the refactoring, but if done must be with or after the AGP version upgrade)
+ |v_n|cur|new|v_o| [OPTIONAL_INDEPENDENT] (need not do the refactoring, but if done can be at any point in the process)
+
+ For the possibly-simpler case where we have a discontinuity in behaviour, v_o = v_n = vvv, and the three possible cases are:
+
+ | 1 | 2 | 3 | Necessity
+ +---+---+---+----------
+ |vvv|cur|new| [IRRELEVANT_PAST]
+ |cur|vvv|new| [MANDATORY_CODEPENDENT]
+ |cur|new|vvv| [IRRELEVANT_FUTURE]
+
+ (again in case of equality, vvv sorts before cur and new)
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(72),
+ """
+ /**
+ * |1 |2 |3 |4 |Necessity |
+ * |---|---|---|---|---------------------------------------------------------------------------------------------------------------|
+ * |v_n|v_o|cur|new|[IRRELEVANT_PAST] |
+ * |cur|new|v_n|v_o|[IRRELEVANT_FUTURE] |
+ * |cur|v_n|v_o|new|[MANDATORY_CODEPENDENT] (must do the refactoring in the same action as the AGP version upgrade) |
+ * |v_n|cur|v_o|new|[MANDATORY_INDEPENDENT] (must do the refactoring, but can do it before the AGP version upgrade) |
+ * |cur|v_n|new|v_o|[OPTIONAL_CODEPENDENT] (need not do the refactoring, but if done must be with or after the AGP version upgrade)|
+ * |v_n|cur|new|v_o|[OPTIONAL_INDEPENDENT] (need not do the refactoring, but if done can be at any point in the process) |
+ *
+ * For the possibly-simpler case where we have a discontinuity in
+ * behaviour, v_o = v_n = vvv, and the three possible cases are:
+ *
+ * | 1 | 2 | 3 | Necessity +---+---+---+---------- |vvv|cur|new|
+ * [IRRELEVANT_PAST] |cur|vvv|new| [MANDATORY_CODEPENDENT] |cur|new|vvv|
+ * [IRRELEVANT_FUTURE]
+ *
+ * (again in case of equality, vvv sorts before cur and new)
+ */
+ """
+ .trimIndent(),
+ indent = "")
+ }
+
+ @Test
+ fun testTables6() {
+ // Test case from IntelliJ's
+ // plugins/kotlin/idea/tests/testData/editor/quickDoc/OnFunctionDeclarationWithGFMTable.kt
+ val source =
+ """
+ /**
+ * | left | center | right | default |
+ * | :---- | :----: | ----: | ------- |
+ * | 1 | 2 | 3 | 4 |
+ *
+ *
+ * | foo | bar | baz |
+ * | --- | --- | --- |
+ * | 1 | 2 |
+ * | 3 | 4 | 5 | 6 |
+ *
+ * | header | only |
+ * | ------ | ---- |
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(72),
+ """
+ /**
+ * | left | center | right | default |
+ * |------|:------:|------:|---------|
+ * | 1 | 2 | 3 | 4 |
+ *
+ * | foo | bar | baz |
+ * |-----|-----|-----|
+ * | 1 | 2 | |
+ * | 3 | 4 | 5 | 6 |
+ *
+ * | header | only |
+ * |--------|------|
+ */
+ """
+ .trimIndent(),
+ indent = "")
+ }
+
+ @Test
+ fun testTables7() {
+ val source =
+ """
+ /**
+ * This is my code
+ * @author Me
+ * And here's.
+ * Another.
+ * Thing.
+ *
+ * my | table
+ * ---|---
+ * item 1|item 2
+ * item 3|
+ * item 4|item 5
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(72),
+ """
+ /**
+ * This is my code
+ *
+ * @author Me And here's. Another. Thing.
+ *
+ * | my | table |
+ * |--------|--------|
+ * | item 1 | item 2 |
+ * | item 3 | |
+ * | item 4 | item 5 |
+ */
+ """
+ .trimIndent(),
+ indent = "")
+ }
+
+ @Test
+ fun testTables7b() {
+ val source =
+ """
+ /**
+ * This is my code
+ * @author Me
+ * Plain text.
+ *
+ * my | table
+ * ---|---
+ * item 1|item 2
+ * item 3|
+ * item 4|item 5
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(72).apply {
+ orderDocTags = false
+ alignTableColumns = false
+ },
+ """
+ /**
+ * This is my code
+ *
+ * @author Me Plain text.
+ *
+ * my | table
+ * ---|---
+ * item 1|item 2
+ * item 3|
+ * item 4|item 5
+ */
+ """
+ .trimIndent(),
+ indent = "")
+ }
+
+ @Test
+ fun testBulletsUnderParamTags() {
+ // Regression test for
+ // https://github.com/tnorbye/kdoc-formatter/issues/56
+ val source =
+ """
+ /**
+ * This supports bullets
+ * - one
+ * - two
+ *
+ * @param thisDoesNot
+ * Here's some parameter text.
+ * - a
+ * - b
+ * Here's some more text
+ *
+ * And here's even more parameter doc text.
+ *
+ * @param another paragraph
+ * * With some bulleted items
+ * * Even nested ones
+ * ```
+ * and some preformatted text
+ * ```
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(maxLineWidth = 72).apply { orderDocTags = false },
+ """
+ /**
+ * This supports bullets
+ * - one
+ * - two
+ *
+ * @param thisDoesNot Here's some parameter text.
+ * - a
+ * - b Here's some more text
+ *
+ * And here's even more parameter doc text.
+ *
+ * @param another paragraph
+ * * With some bulleted items
+ * * Even nested ones
+ *
+ * ```
+ * and some preformatted text
+ * ```
+ */
+ """
+ .trimIndent())
+ }
+
+ @Test
+ fun testLineBreaking() {
+ // Regression test for
+ // https://github.com/tnorbye/kdoc-formatter/issues/57
+ val source =
+ """
+ /** aa aa aa aa a */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(maxLineWidth = 20, maxCommentWidth = 20).apply { optimal = false },
+ """
+ /** aa aa aa aa a */
+ """
+ .trimIndent(),
+ indent = "")
+ }
+
+ @Test
+ fun testPreTag() {
+ // Regression test for
+ // https://github.com/tnorbye/kdoc-formatter/issues/58
+ val source =
+ """
+ /**
+ * This tag messes things up.
+ * <pre>
+ * This is pre.
+ *
+ * @return some correct
+ * value
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(maxLineWidth = 72),
+ """
+ /**
+ * This tag messes things up.
+ * <pre>
+ * This is pre.
+ *
+ * @return some correct
+ * value
+ */
+ """
+ .trimIndent(),
+ verifyDokka = false // this triggers a bug in the diff lookup; TODO investigate
+ )
+ }
+
+ @Test
+ fun testPreTag2() {
+ // Regression test for
+ // https://github.com/tnorbye/kdoc-formatter/issues/58
+ val source =
+ """
+ /**
+ * Even if it's closed.
+ * <pre>My Pre</pre>
+ *
+ * @return some correct
+ * value
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(maxLineWidth = 72),
+ """
+ /**
+ * Even if it's closed.
+ *
+ * ```
+ * My Pre
+ * ```
+ *
+ * @return some correct value
+ */
+ """
+ .trimIndent(),
+ // <pre> and ``` are rendered differently; this is an intentional diff
+ verifyDokka = false)
+ }
+
+ @Test
+ fun testPreTag3() {
+ // From Studio's
+ // build-system/builder-model/src/main/java/com/android/builder/model/DataBindingOptions.kt
+ val source =
+ """
+ /**
+ * Whether we want tests to be able to use data binding as well.
+ *
+ * <p>
+ * Data Binding classes generated from the application can always be
+ * accessed in the test code but test itself cannot introduce new
+ * Data Binding layouts, bindables etc unless this flag is turned
+ * on.
+ *
+ * <p>
+ * This settings help with an issue in older devices where class
+ * verifier throws an exception when the application class is
+ * overwritten by the test class. It also makes it easier to run
+ * proguarded tests.
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(maxLineWidth = 72),
+ """
+ /**
+ * Whether we want tests to be able to use data binding as well.
+ *
+ * Data Binding classes generated from the application can always be
+ * accessed in the test code but test itself cannot introduce new
+ * Data Binding layouts, bindables etc unless this flag is turned
+ * on.
+ *
+ * This settings help with an issue in older devices where class
+ * verifier throws an exception when the application class is
+ * overwritten by the test class. It also makes it easier to run
+ * proguarded tests.
+ */
+ """
+ .trimIndent())
+ }
+
+ @Test
+ fun testNoConversionInReferences() {
+ val source =
+ """
+ /**
+ * A thread safe in-memory cache of [Key&lt;T&gt;][Key] to `T` values whose lifetime is tied
+ * to a [CoroutineScope].
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(maxLineWidth = 72),
+ """
+ /**
+ * A thread safe in-memory cache of [Key&lt;T&gt;][Key] to `T` values
+ * whose lifetime is tied to a [CoroutineScope].
+ */
+ """
+ .trimIndent(),
+ indent = "")
+ }
+
+ @Test
+ fun testCaseSensitiveMarkup() {
+ // Regression test for
+ // https://github.com/tnorbye/kdoc-formatter/issues/59
+ val source =
+ """
+ /** <A> to <B> should remain intact, not <b>bolded</b> */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(maxLineWidth = 72),
+ """
+ /** <A> to <B> should remain intact, not **bolded** */
+ """
+ .trimIndent(),
+ // This is a broken comment (unterminated <B> etc) so the behaviors differ
+ verifyDokka = false)
+ }
+
+ @Test
+ fun testAsteriskRemoval() {
+ // Regression test for
+ // https://github.com/tnorbye/kdoc-formatter/issues/60
+ val source =
+ """
+ /** *** Testing */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(maxLineWidth = 72),
+ """
+ /** *** Testing */
+ """
+ .trimIndent())
+ }
+
+ @Test
+ fun testParagraphTagRemoval() {
+ // Regression test for
+ // https://github.com/tnorbye/kdoc-formatter/issues/61
+ val source =
+ """
+ /**
+ * Ptag removal should remove extra space
+ *
+ * <p> Some paragraph
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(maxLineWidth = 72),
+ """
+ /**
+ * Ptag removal should remove extra space
+ *
+ * Some paragraph
+ */
+ """
+ .trimIndent())
+ }
+
+ @Test
+ fun testDashedLineIndentation() {
+ // Regression test for
+ // https://github.com/tnorbye/kdoc-formatter/issues/62
+ val source =
+ """
+ /**
+ * Some summary.
+ *
+ * - Some bullet.
+ *
+ * ------------------------------------------------------------------------------
+ *
+ * Some paragraph.
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(maxLineWidth = 72),
+ """
+ /**
+ * Some summary.
+ * - Some bullet.
+ *
+ * ------------------------------------------------------------------------------
+ *
+ * Some paragraph.
+ */
+ """
+ .trimIndent())
+ }
+
+ @Test
+ fun testParagraphRemoval() {
+ // Regression test for
+ // https://github.com/tnorbye/kdoc-formatter/issues/63
+ val source =
+ """
+ /**
+ * 1. Test
+ *
+ * <p>2. Test
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(maxLineWidth = 72),
+ """
+ /**
+ * 1. Test
+ * 2. Test
+ */
+ """
+ .trimIndent(),
+ // We deliberately allow list items to jump up across blank lines
+ verifyDokka = false)
+ }
+
+ @Test
+ fun testParagraphRemoval2() {
+ // Regression test for
+ // https://github.com/tnorbye/kdoc-formatter/issues/69
+ val source =
+ """
+ /**
+ * Some title
+ *
+ * <p>1. Test
+ * 2. Test
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(maxLineWidth = 72),
+ """
+ /**
+ * Some title
+ * 1. Test
+ * 2. Test
+ */
+ """
+ .trimIndent(),
+ // We deliberately allow list items to jump up across blank lines
+ verifyDokka = false)
+ }
+
+ @Test
+ fun testAtBreak2() {
+ // Regression test for
+ // https://github.com/tnorbye/kdoc-formatter/issues/64
+ // This behavior is deliberate: we cannot put @aa at the beginning of a new line;
+ // if so KDoc will treat it as a doc and silently drop it because it isn't a known
+ // custom tag.
+ val source =
+ """
+ /**
+ * aa aa aa aa aa @aa
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(maxLineWidth = 20, maxCommentWidth = 20),
+ """
+ /**
+ * aa aa aa aa
+ * aa @aa
+ */
+ """
+ .trimIndent())
+ }
+
+ @Test
+ fun testNoBreakAfterAt() {
+ // Regression test for
+ // https://github.com/tnorbye/kdoc-formatter/issues/65
+ val source =
+ """
+ /**
+ * Weird break
+ *
+ * alink aaaaaaa
+ *
+ * @param a aaaaaa
+ * @link aaaaaaa
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(maxLineWidth = 20, maxCommentWidth = 20),
+ """
+ /**
+ * Weird break
+ *
+ * alink aaaaaaa
+ *
+ * @param a aaaaaa
+ * @link aaaaaaa
+ */
+ """
+ .trimIndent(),
+ indent = "")
+ }
+
+ @Test
+ fun testPreCodeConversion() {
+ val source =
+ """
+ /**
+ * <pre><code>
+ * More sample code.
+ * </code></pre>
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(maxLineWidth = 72),
+ """
+ /**
+ * ```
+ * More sample code.
+ * ```
+ */
+ """
+ .trimIndent(),
+ indent = " ",
+ // <pre> and ``` are rendered differently; this is an intentional diff
+ verifyDokka = false)
+ }
+
+ @Test
+ fun testPreConversion2() {
+ // From AndroidX and Studio methods
+ val source =
+ """
+ /**
+ * Checks if any of the GL calls since the last time this method was called set an error
+ * condition. Call this method immediately after calling a GL method. Pass the name of the GL
+ * operation. For example:
+ *
+ * <pre>
+ * mColorHandle = GLES20.glGetUniformLocation(mProgram, "uColor");
+ * MyGLRenderer.checkGlError("glGetUniformLocation");</pre>
+ *
+ * If the operation is not successful, the check throws an exception.
+ *
+ * <pre>public performItemClick(T item) {
+ * ...
+ * sendEventForVirtualView(item.id, AccessibilityEvent.TYPE_VIEW_CLICKED)
+ * }
+ * </pre>
+ * *Note* This is quite slow so it's best to use it sparingly in production builds.
+ * Injector to load associated file. It will create code like:
+ * <pre>file = FileUtil.loadLabels(extractor.getAssociatedFile(fileName))</pre>
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(maxLineWidth = 72),
+ """
+ /**
+ * Checks if any of the GL calls since the last time this
+ * method was called set an error condition. Call this method
+ * immediately after calling a GL method. Pass the name of the
+ * GL operation. For example:
+ * ```
+ * mColorHandle = GLES20.glGetUniformLocation(mProgram, "uColor");
+ * MyGLRenderer.checkGlError("glGetUniformLocation");
+ * ```
+ *
+ * If the operation is not successful, the check throws an
+ * exception.
+ *
+ * ```
+ * public performItemClick(T item) {
+ * ...
+ * sendEventForVirtualView(item.id, AccessibilityEvent.TYPE_VIEW_CLICKED)
+ * }
+ * ```
+ *
+ * *Note* This is quite slow so it's best to use it sparingly in
+ * production builds. Injector to load associated file. It will
+ * create code like:
+ * ```
+ * file = FileUtil.loadLabels(extractor.getAssociatedFile(fileName))
+ * ```
+ */
+ """
+ .trimIndent(),
+ indent = " ",
+ // <pre> and ``` are rendered differently; this is an intentional diff
+ verifyDokka = false)
+ }
+
+ /**
+ * Test utility method: from a source kdoc, derive an "equivalent" kdoc (same punctuation,
+ * whitespace, capitalization and length of words) with words from Lorem Ipsum. Useful to create
+ * test cases for the formatter without checking in original comments.
+ */
+ private fun loremize(s: String): String {
+ val lorem =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt " +
+ "ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco " +
+ "laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in " +
+ "voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat " +
+ "non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"
+ val loremWords = lorem.filter { it.isLetter() || it == ' ' }.lowercase().split(" ")
+ var next = 0
+
+ fun adjustCapitalization(word: String, original: String): String {
+ return if (original[0].isUpperCase()) {
+ if (original.all { it.isUpperCase() }) {
+ word.uppercase()
+ } else {
+ word.replaceFirstChar { it.uppercase() }
+ }
+ } else {
+ word
+ }
+ }
+
+ fun nextLorem(word: String): String {
+ val length = word.length
+ val start = next
+ while (next < loremWords.size) {
+ val nextLorem = loremWords[next]
+ if (nextLorem.length == length) {
+ return adjustCapitalization(nextLorem, word)
+ }
+ next++
+ }
+ next = 0
+ while (next < start) {
+ val nextLorem = loremWords[next]
+ if (nextLorem.length == length) {
+ return adjustCapitalization(nextLorem, word)
+ }
+ next++
+ }
+ if (length == 1) {
+ return ('a' + (start % 26)).toString()
+ }
+ // No match for this word
+ return word
+ }
+
+ val sb = StringBuilder()
+ var i = 0
+ while (i < s.length) {
+ val c = s[i]
+ if (c.isLetter()) {
+ var end = i + 1
+ while (end < s.length && s[end].isLetter()) {
+ end++
+ }
+ val word = s.substring(i, end)
+ if (i > 0 && s[i - 1] == '@' || word == "http" || word == "https" || word == "com") {
+ // Don't translate URL prefix/suffixes and doc tags
+ sb.append(word)
+ } else {
+ sb.append(nextLorem(word))
+ }
+ i = end
+ } else {
+ sb.append(c)
+ i++
+ }
+ }
+ return sb.toString()
+ }
+
+ // --------------------------------------------------------------------
+ // A few failing test cases here for corner cases that aren't handled
+ // right yet.
+ // --------------------------------------------------------------------
+
+ @Ignore("Lists within quoted blocks not yet supported")
+ @Test
+ fun testNestedWithinQuoted() {
+ val source =
+ """
+ /*
+ * Lists within a block quote:
+ * > Here's my quoted text.
+ * > 1. First item
+ * > 2. Second item
+ * > 3. Third item
+ */
+ """
+ .trimIndent()
+ checkFormatter(
+ source,
+ KDocFormattingOptions(40),
+ """
+ /*
+ * Lists within a block quote:
+ * > Here's my quoted text.
+ * > 1. First item
+ * > 2. Second item
+ * > 3. Third item
+ */
+ """
+ .trimIndent())
+
+ checkFormatter(
+ """
+ /**
+ * Here's some text.
+ * > Here's some more text that
+ * > is indented. More text.
+ * > > And here's some even
+ * > > more indented text
+ * > Back to the top level
+ */
+ """
+ .trimIndent(),
+ KDocFormattingOptions(maxLineWidth = 100, maxCommentWidth = 60),
+ """
+ /**
+ * Here's some text.
+ * > Here's some more text that
+ * > is indented. More text.
+ * > > And here's some even
+ * > > more indented text
+ * > Back to the top level
+ */
+ """
+ .trimIndent())
}
}