From 9be7ac19b3ad38c80c78f39443eb2f55b3b699c6 Mon Sep 17 00:00:00 2001
From: Marcin Aman <marcin.aman@gmail.com>
Date: Sat, 21 Aug 2021 14:50:55 +0200
Subject: Make briefs contain first paragraph as in spec, resolve issues with
 i.e. (#2048)

* Make briefs contain first paragraph as in spec, resolve issues with i.e.

* Html and the end
---
 plugins/base/api/base.api                          |   5 +-
 .../documentables/DefaultPageCreator.kt            |  28 +-
 .../documentables/PageContentBuilder.kt            |  28 +-
 .../documentables/briefFromContentNodes.kt         |  39 ++-
 .../documentables/documentableLanguage.kt          |  15 +
 .../translators/psi/parsers/JavadocParser.kt       |   5 +-
 .../content/functions/ContentForBriefTest.kt       | 330 +++++++++++++++++++++
 .../jetbrains/dokka/javadoc/JavadocPageCreator.kt  |   6 +-
 8 files changed, 434 insertions(+), 22 deletions(-)
 create mode 100644 plugins/base/src/main/kotlin/translators/documentables/documentableLanguage.kt
 create mode 100644 plugins/base/src/test/kotlin/content/functions/ContentForBriefTest.kt

diff --git a/plugins/base/api/base.api b/plugins/base/api/base.api
index c6395403..093f7961 100644
--- a/plugins/base/api/base.api
+++ b/plugins/base/api/base.api
@@ -1249,7 +1249,8 @@ public final class org/jetbrains/dokka/base/translators/descriptors/DefaultDescr
 }
 
 public final class org/jetbrains/dokka/base/translators/documentables/BriefFromContentNodesKt {
-	public static final fun briefFromContentNodes (Ljava/util/List;)Ljava/util/List;
+	public static final fun firstParagraphBrief (Lorg/jetbrains/dokka/model/doc/DocTag;)Lorg/jetbrains/dokka/model/doc/DocTag;
+	public static final fun firstSentenceBriefFromContentNodes (Ljava/util/List;)Ljava/util/List;
 }
 
 public abstract interface annotation class org/jetbrains/dokka/base/translators/documentables/ContentBuilderMarker : java/lang/annotation/Annotation {
@@ -1355,6 +1356,8 @@ public class org/jetbrains/dokka/base/translators/documentables/PageContentBuild
 	protected final fun createText (Ljava/lang/String;Lorg/jetbrains/dokka/pages/Kind;Ljava/util/Set;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;)Lorg/jetbrains/dokka/pages/ContentText;
 	public final fun divergentGroup (Lorg/jetbrains/dokka/pages/ContentDivergentGroup$GroupID;Ljava/util/Set;Lorg/jetbrains/dokka/pages/Kind;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;ZLkotlin/jvm/functions/Function1;)V
 	public static synthetic fun divergentGroup$default (Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Lorg/jetbrains/dokka/pages/ContentDivergentGroup$GroupID;Ljava/util/Set;Lorg/jetbrains/dokka/pages/Kind;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+	public final fun firstParagraphComment (Lorg/jetbrains/dokka/model/doc/DocTag;Lorg/jetbrains/dokka/pages/Kind;Ljava/util/Set;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;)V
+	public static synthetic fun firstParagraphComment$default (Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Lorg/jetbrains/dokka/model/doc/DocTag;Lorg/jetbrains/dokka/pages/Kind;Ljava/util/Set;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;ILjava/lang/Object;)V
 	public final fun firstSentenceComment (Lorg/jetbrains/dokka/model/doc/DocTag;Lorg/jetbrains/dokka/pages/Kind;Ljava/util/Set;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;)V
 	public static synthetic fun firstSentenceComment$default (Lorg/jetbrains/dokka/base/translators/documentables/PageContentBuilder$DocumentableContentBuilder;Lorg/jetbrains/dokka/model/doc/DocTag;Lorg/jetbrains/dokka/pages/Kind;Ljava/util/Set;Ljava/util/Set;Lorg/jetbrains/dokka/model/properties/PropertyContainer;ILjava/lang/Object;)V
 	protected final fun getContents ()Ljava/util/List;
diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
index 081608d6..4b5fc1c0 100644
--- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
+++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt
@@ -141,7 +141,7 @@ open class DefaultPageCreator(
 
             link(it.name, it.dri)
             if (it.sourceSets.size == 1 || (documentations.isNotEmpty() && haveSameContent)) {
-                documentations.first()?.let { firstSentenceComment(kind = ContentKind.Comment, content = it.root) }
+                documentations.first()?.let { firstParagraphComment(kind = ContentKind.Comment, content = it.root) }
             }
         }
     }
@@ -375,7 +375,7 @@ open class DefaultPageCreator(
         val platforms = d.sourceSets
 
         fun DocumentableContentBuilder.contentForParams() {
-            if (tags.isNotEmptyForTag<Param>()) {
+            if (tags.isNotEmptyForTag<Param>() && d !is DProperty) {
                 header(2, "Parameters", kind = ContentKind.Parameters)
                 group(
                     extra = mainExtra + SimpleAttr.header("Parameters"),
@@ -504,15 +504,33 @@ open class DefaultPageCreator(
 
     protected open fun DocumentableContentBuilder.contentForBrief(documentable: Documentable) {
         documentable.sourceSets.forEach { sourceSet ->
-            documentable.documentation[sourceSet]?.children?.firstOrNull()?.root?.let {
+            documentable.documentation[sourceSet]?.let {
+                /*
+                    Get description or a tag that holds documentation.
+                    This tag can be either property or constructor but constructor tags are handled already in analysis so we
+                    only need to keep an eye on property
+
+                    We purposefully ignore all other tags as they should not be visible in brief
+                 */
+                it.firstMemberOfTypeOrNull<Description>() ?: it.firstMemberOfTypeOrNull<Property>().takeIf { documentable is DProperty }
+            }?.let {
                 group(sourceSets = setOf(sourceSet), kind = ContentKind.BriefComment) {
-                    if (documentable.hasSeparatePage) firstSentenceComment(it)
-                    else comment(it)
+                    if (documentable.hasSeparatePage) createBriefComment(documentable, sourceSet, it)
+                    else comment(it.root)
                 }
             }
         }
     }
 
+    private fun DocumentableContentBuilder.createBriefComment(documentable: Documentable, sourceSet: DokkaSourceSet, tag: TagWrapper){
+        (documentable as? WithSources)?.documentableLanguage(sourceSet)?.let {
+            when(it){
+                DocumentableLanguage.KOTLIN -> firstParagraphComment(tag.root)
+                DocumentableLanguage.JAVA -> firstSentenceComment(tag.root)
+            }
+        } ?: firstParagraphComment(tag.root)
+    }
+
     protected open fun DocumentableContentBuilder.contentForSinceKotlin(documentable: Documentable) {
         documentable.documentation.mapValues {
             it.value.children.find { it is CustomTagWrapper && it.name == "Since Kotlin" } as CustomTagWrapper?
diff --git a/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt b/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt
index f98e284f..2db55d45 100644
--- a/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt
+++ b/plugins/base/src/main/kotlin/translators/documentables/PageContentBuilder.kt
@@ -283,13 +283,37 @@ open class PageContentBuilder(
             contents += ContentGroup(content, DCI(mainDRI, kind), sourceSets.toDisplaySourceSets(), styles, extra)
         }
 
-        fun firstSentenceComment(
+        fun firstParagraphComment(
             content: DocTag,
             kind: Kind = ContentKind.Comment,
             sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
             styles: Set<Style> = mainStyles,
             extra: PropertyContainer<ContentNode> = mainExtra
         ) {
+            firstParagraphBrief(content)?.let { brief ->
+                val builtDescription = commentsConverter.buildContent(
+                    brief,
+                    DCI(mainDRI, kind),
+                    sourceSets
+                )
+
+                contents += ContentGroup(
+                    builtDescription,
+                    DCI(mainDRI, kind),
+                    sourceSets.toDisplaySourceSets(),
+                    styles,
+                    extra
+                )
+            }
+        }
+
+        fun firstSentenceComment(
+            content: DocTag,
+            kind: Kind = ContentKind.Comment,
+            sourceSets: Set<DokkaSourceSet> = mainSourcesetData,
+            styles: Set<Style> = mainStyles,
+            extra: PropertyContainer<ContentNode> = mainExtra
+        ){
             val builtDescription = commentsConverter.buildContent(
                 content,
                 DCI(mainDRI, kind),
@@ -297,7 +321,7 @@ open class PageContentBuilder(
             )
 
             contents += ContentGroup(
-                briefFromContentNodes(builtDescription),
+                firstSentenceBriefFromContentNodes(builtDescription),
                 DCI(mainDRI, kind),
                 sourceSets.toDisplaySourceSets(),
                 styles,
diff --git a/plugins/base/src/main/kotlin/translators/documentables/briefFromContentNodes.kt b/plugins/base/src/main/kotlin/translators/documentables/briefFromContentNodes.kt
index 7ac6763d..81ddb6ed 100644
--- a/plugins/base/src/main/kotlin/translators/documentables/briefFromContentNodes.kt
+++ b/plugins/base/src/main/kotlin/translators/documentables/briefFromContentNodes.kt
@@ -1,10 +1,20 @@
 package org.jetbrains.dokka.base.translators.documentables
 
+import org.jetbrains.dokka.model.doc.*
 import org.jetbrains.dokka.model.withDescendants
 import org.jetbrains.dokka.pages.*
+import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
 import org.jetbrains.kotlin.utils.addToStdlib.safeAs
 
-fun briefFromContentNodes(description: List<ContentNode>): List<ContentNode> {
+fun firstParagraphBrief(docTag: DocTag): DocTag? =
+    when(docTag){
+        is P -> docTag
+        is CustomDocTag -> docTag.children.firstNotNullResult { firstParagraphBrief(it) }
+        is Text -> docTag
+        else -> null
+    }
+
+fun firstSentenceBriefFromContentNodes(description: List<ContentNode>): List<ContentNode> {
     val firstSentenceRegex = """^((?:[^.?!]|[.!?](?!\s))*[.!?])""".toRegex()
 
     //Description that is entirely based on html content. In html it is hard to define a brief so we render all of it
@@ -13,21 +23,34 @@ fun briefFromContentNodes(description: List<ContentNode>): List<ContentNode> {
     }
 
     var sentenceFound = false
-    fun lookthrough(node: ContentNode): ContentNode =
-        if (node is ContentText && !node.isHtml && firstSentenceRegex.containsMatchIn(node.text)) {
+    fun lookthrough(node: ContentNode, neighbours: List<ContentNode>, currentIndex: Int): ContentNode =
+        if (node.finishesWithSentenceNotFollowedByHtml(firstSentenceRegex, neighbours, currentIndex) || node.containsSentenceFinish(firstSentenceRegex)) {
+            node as ContentText
             sentenceFound = true
             node.copy(text = firstSentenceRegex.find(node.text)?.value.orEmpty())
         } else if (node is ContentGroup) {
-            node.copy(children = node.children.mapNotNull {
-                if (!sentenceFound) lookthrough(it) else null
+            node.copy(children = node.children.mapIndexedNotNull { i, element ->
+                if (!sentenceFound) lookthrough(element, node.children, i) else null
             }, style = node.style - TextStyle.Paragraph)
         } else {
             node
         }
-    return description.mapNotNull {
-        if (!sentenceFound) lookthrough(it) else null
+    return description.mapIndexedNotNull { i, element ->
+        if (!sentenceFound) lookthrough(element, description, i) else null
     }
 }
 
-private val ContentText.isHtml
+private fun ContentNode.finishesWithSentenceNotFollowedByHtml(firstSentenceRegex: Regex, neighbours: List<ContentNode>, currentIndex: Int): Boolean =
+    this is ContentText && !isHtml && matchContainsEnd(this, firstSentenceRegex) && !neighbours.nextElementIsHtml(currentIndex)
+
+private fun ContentNode.containsSentenceFinish(firstSentenceRegex: Regex): Boolean =
+    this is ContentText && !isHtml && firstSentenceRegex.containsMatchIn(text) && !matchContainsEnd(this, firstSentenceRegex)
+
+private fun matchContainsEnd(node: ContentText, regex: Regex): Boolean =
+    regex.find(node.text)?.let { node.text.endsWith(it.value) } ?: false
+
+private fun List<ContentNode>.nextElementIsHtml(currentElementIndex: Int): Boolean =
+    currentElementIndex != lastIndex && get(currentElementIndex + 1).isHtml
+
+private val ContentNode.isHtml
     get() = extra[HtmlContent] != null
diff --git a/plugins/base/src/main/kotlin/translators/documentables/documentableLanguage.kt b/plugins/base/src/main/kotlin/translators/documentables/documentableLanguage.kt
new file mode 100644
index 00000000..b3ce7c5c
--- /dev/null
+++ b/plugins/base/src/main/kotlin/translators/documentables/documentableLanguage.kt
@@ -0,0 +1,15 @@
+package org.jetbrains.dokka.base.translators.documentables
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.analysis.PsiDocumentableSource
+import org.jetbrains.dokka.model.WithSources
+
+internal enum class DocumentableLanguage {
+    JAVA, KOTLIN
+}
+
+internal fun WithSources.documentableLanguage(sourceSet: DokkaConfiguration.DokkaSourceSet): DocumentableLanguage =
+    when (sources[sourceSet]) {
+        is PsiDocumentableSource -> DocumentableLanguage.JAVA
+        else -> DocumentableLanguage.KOTLIN
+    }
\ No newline at end of file
diff --git a/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt b/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt
index 9cb362cb..8583edf7 100644
--- a/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt
+++ b/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt
@@ -22,9 +22,7 @@ import org.jetbrains.kotlin.idea.util.CommentSaver.Companion.tokenType
 import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespace
 import org.jetbrains.kotlin.psi.psiUtil.siblings
 import org.jsoup.Jsoup
-import org.jsoup.nodes.Element
-import org.jsoup.nodes.Node
-import org.jsoup.nodes.TextNode
+import org.jsoup.nodes.*
 import java.util.*
 import org.jetbrains.dokka.utilities.htmlEscape
 
@@ -414,6 +412,7 @@ class JavadocParser(
             } else {
                 node.wholeText.parseHtmlEncodedWithNormalisedSpaces(renderWhiteCharactersAsSpaces = true)
             }).orEmpty()
+            is Comment -> listOf(Text(body = node.outerHtml(), params = DocTag.contentTypeParam("html")))
             is Element -> createBlock(node, keepFormatting)
             else -> emptyList()
         }
diff --git a/plugins/base/src/test/kotlin/content/functions/ContentForBriefTest.kt b/plugins/base/src/test/kotlin/content/functions/ContentForBriefTest.kt
new file mode 100644
index 00000000..7d8a169b
--- /dev/null
+++ b/plugins/base/src/test/kotlin/content/functions/ContentForBriefTest.kt
@@ -0,0 +1,330 @@
+package content.functions
+
+import org.junit.Assert.assertEquals
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.links.TypeConstructor
+import org.jetbrains.dokka.model.DClass
+import org.jetbrains.dokka.model.dfs
+import org.jetbrains.dokka.pages.*
+import org.junit.jupiter.api.Test
+import kotlin.test.assertNull
+
+
+class ContentForBriefTest : BaseAbstractTest() {
+    private val testConfiguration = dokkaConfiguration {
+        sourceSets {
+            sourceSet {
+                sourceRoots = listOf("src/")
+                analysisPlatform = "jvm"
+            }
+        }
+    }
+
+    private val codeWithSecondaryAndPrimaryConstructorsDocumented =
+        """
+            |/src/main/kotlin/test/source.kt
+            |package test
+            |
+            |/**
+            | * Dummy text.
+            | *
+            | * @constructor constructor docs
+            | * @param exampleParameter dummy parameter.
+            | */
+            |class Example(val exampleParameter: Int) {
+            | 
+            |    /**
+            |     * secondary constructor
+            |     * @param param1 param1 docs
+            |     */
+            |    constructor(param1: String) : this(1)
+            |}
+        """.trimIndent()
+
+    private val codeWithDocumentedParameter =
+        """
+            |/src/main/kotlin/test/source.kt
+            |package test
+            |
+            |/**
+            | * Dummy text.
+            | *
+            | * @param exampleParameter dummy parameter.
+            | */
+            |class Example(val exampleParameter: Int) {
+            |}
+        """.trimIndent()
+
+    @Test
+    fun `primary constructor should not inherit docs from its parameter`() {
+        testInline(codeWithSecondaryAndPrimaryConstructorsDocumented, testConfiguration) {
+            pagesTransformationStage = { module ->
+                val classPage =
+                    module.dfs { it.name == "Example" && (it as ContentPage).documentable is DClass } as ContentPage
+                val constructorsTable =
+                    classPage.content.dfs { it is ContentTable && it.dci.kind == ContentKind.Constructors } as ContentTable
+
+                assertEquals(2, constructorsTable.children.size)
+                val primary = constructorsTable.children.first {
+                    it.dci.dri.first().callable?.params?.first() == TypeConstructor(
+                        "kotlin.Int",
+                        emptyList()
+                    )
+                }
+                val primaryConstructorDocs =
+                    primary.dfs { it is ContentText && it.dci.kind == ContentKind.Comment } as ContentText
+
+                assertEquals("constructor docs", primaryConstructorDocs.text)
+            }
+        }
+    }
+
+    @Test
+    fun `secondary constructor should not inherit docs from its parameter`() {
+        testInline(codeWithSecondaryAndPrimaryConstructorsDocumented, testConfiguration) {
+            pagesTransformationStage = { module ->
+                val classPage =
+                    module.dfs { it.name == "Example" && (it as ContentPage).documentable is DClass } as ContentPage
+                val constructorsTable =
+                    classPage.content.dfs { it is ContentTable && it.dci.kind == ContentKind.Constructors } as ContentTable
+
+                assertEquals(2, constructorsTable.children.size)
+                val primary = constructorsTable.children.first {
+                    it.dci.dri.first().callable?.params?.first() == TypeConstructor(
+                        "kotlin.String",
+                        emptyList()
+                    )
+                }
+                val primaryConstructorDocs =
+                    primary.dfs { it is ContentText && it.dci.kind == ContentKind.Comment } as ContentText
+
+                assertEquals("secondary constructor", primaryConstructorDocs.text)
+            }
+        }
+    }
+
+    @Test
+    fun `primary constructor should not inherit docs from its parameter when no specific docs are provided`() {
+        testInline(codeWithDocumentedParameter, testConfiguration) {
+            pagesTransformationStage = { module ->
+                val classPage =
+                    module.dfs { it.name == "Example" && (it as ContentPage).documentable is DClass } as ContentPage
+                val constructorsTable =
+                    classPage.content.dfs { it is ContentTable && it.dci.kind == ContentKind.Constructors } as ContentTable
+
+                assertEquals(1, constructorsTable.children.size)
+                val primary = constructorsTable.children.first()
+                val primaryConstructorDocs = primary.dfs { it is ContentText && it.dci.kind == ContentKind.Comment }
+
+                assertNull(primaryConstructorDocs, "Expected no primary constructor docs to be present")
+            }
+        }
+    }
+
+    @Test
+    fun `brief for functions should work with html`() {
+        testInline(
+            """
+            |/src/main/kotlin/test/source.kt
+            |package test
+            |
+            |class Example(val exampleParameter: Int) {
+            |   /**
+            |    * This is an example <!-- not visible --> of html
+            |    *
+            |    * This is definitely not a brief
+            |    */
+            |   fun test(): String = "TODO"
+            |}
+        """.trimIndent(),
+            testConfiguration
+        ) {
+            pagesTransformationStage = { module ->
+                val functionBriefDocs = module.singleFunctionDescription("Example")
+
+                assertEquals(
+                    "This is an example <!-- not visible --> of html",
+                    functionBriefDocs.children.joinToString("") { (it as ContentText).text })
+            }
+        }
+    }
+
+    @Test
+    fun `brief for functions should work with ie`() {
+        testInline(
+            """
+            |/src/main/kotlin/test/source.kt
+            |package test
+            |
+            |class Example(val exampleParameter: Int) {
+            |   /**
+            |    * The user token, i.e. "Bearer xyz". Throw an exception if not available.
+            |    *
+            |    * This is definitely not a brief
+            |    */
+            |   fun test(): String = "TODO"
+            |}
+        """.trimIndent(),
+            testConfiguration
+        ) {
+            pagesTransformationStage = { module ->
+                val functionBriefDocs = module.singleFunctionDescription("Example")
+
+                assertEquals(
+                    "The user token, i.e. \"Bearer xyz\". Throw an exception if not available.",
+                    functionBriefDocs.children.joinToString("") { (it as ContentText).text })
+            }
+        }
+    }
+
+    @Test
+    fun `brief for functions should work with eg`() {
+        testInline(
+            """
+            |/src/main/kotlin/test/source.kt
+            |package test
+            |
+            |class Example(val exampleParameter: Int) {
+            |   /**
+            |    * The user token, e.g. "Bearer xyz". Throw an exception if not available.
+            |    *
+            |    * This is definitely not a brief
+            |    */
+            |   fun test(): String = "TODO"
+            |}
+        """.trimIndent(),
+            testConfiguration
+        ) {
+            pagesTransformationStage = { module ->
+                val functionBriefDocs = module.singleFunctionDescription("Example")
+
+                assertEquals(
+                    "The user token, e.g. \"Bearer xyz\". Throw an exception if not available.",
+                    functionBriefDocs.children.joinToString("") { (it as ContentText).text })
+            }
+        }
+    }
+
+    @Test
+    fun `brief for functions should be first sentence for Java`() {
+        testInline(
+            """
+            |/src/main/java/test/Example.java
+            |package test;
+            |
+            |public class Example {
+            |   /**
+            |    * The user token, or not. This is definitely not a brief in java
+            |    */
+            |   public static String test() {
+            |       return "TODO";
+            |   }
+            |}
+        """.trimIndent(),
+            testConfiguration
+        ) {
+            pagesTransformationStage = { module ->
+                val functionBriefDocs = module.singleFunctionDescription("Example")
+
+                assertEquals(
+                    "The user token, or not.",
+                    functionBriefDocs.children.joinToString("") { (it as ContentText).text })
+            }
+        }
+    }
+
+    @Test
+    fun `brief for functions should work with ie for Java`() {
+        testInline(
+            """
+            |/src/main/java/test/Example.java
+            |package test;
+            |
+            |public class Example {
+            |   /**
+            |    * The user token, e.g.&nbsp;"Bearer xyz". This is definitely not a brief in java
+            |    */
+            |   public static String test() {
+            |       return "TODO";
+            |   }
+            |}
+        """.trimIndent(),
+            testConfiguration
+        ) {
+            pagesTransformationStage = { module ->
+                val functionBriefDocs = module.singleFunctionDescription("Example")
+
+                assertEquals(
+                    "The user token, e.g. \"Bearer xyz\".",
+                    functionBriefDocs.children.joinToString("") { (it as ContentText).text })
+            }
+        }
+    }
+
+    //Source: https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html#exampleresult
+    @Test
+    fun `brief for functions should work with html comment for Java`() {
+        testInline(
+            """
+            |/src/main/java/test/Example.java
+            |package test;
+            |
+            |public class Example {
+            |   /**
+            |    * This is a simulation of Prof.<!-- --> Knuth's MIX computer. This is definitely not a brief in java
+            |    */
+            |   public static String test() {
+            |       return "TODO";
+            |   }
+            |}
+        """.trimIndent(),
+            testConfiguration
+        ) {
+            pagesTransformationStage = { module ->
+                val functionBriefDocs = module.singleFunctionDescription("Example")
+
+                assertEquals(
+                    "This is a simulation of Prof.<!-- --> Knuth's MIX computer.",
+                    functionBriefDocs.children.joinToString("") { (it as ContentText).text })
+            }
+        }
+    }
+
+    @Test
+    fun `brief for functions should work with html comment at the end for Java`() {
+        testInline(
+            """
+            |/src/main/java/test/Example.java
+            |package test;
+            |
+            |public class Example {
+            |   /**
+            |    * This is a simulation of Prof.<!-- --> Knuth's MIX computer. This is definitely not a brief in java <!-- -->
+            |    */
+            |   public static String test() {
+            |       return "TODO";
+            |   }
+            |}
+        """.trimIndent(),
+            testConfiguration
+        ) {
+            pagesTransformationStage = { module ->
+                val functionBriefDocs = module.singleFunctionDescription("Example")
+
+                assertEquals(
+                    "This is a simulation of Prof.<!-- --> Knuth's MIX computer.",
+                    functionBriefDocs.children.joinToString("") { (it as ContentText).text })
+            }
+        }
+    }
+
+    private fun RootPageNode.singleFunctionDescription(className: String): ContentGroup {
+        val classPage = dfs { it.name == className && (it as ContentPage).documentable is DClass } as ContentPage
+        val functionsTable =
+            classPage.content.dfs { it is ContentTable && it.dci.kind == ContentKind.Functions } as ContentTable
+
+        assertEquals(1, functionsTable.children.size)
+        val function = functionsTable.children.first()
+        return function.dfs { it is ContentGroup && it.dci.kind == ContentKind.Comment && it.children.all { it is ContentText } } as ContentGroup
+    }
+}
\ No newline at end of file
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPageCreator.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPageCreator.kt
index 4d0a62c3..4dbd5ca7 100644
--- a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPageCreator.kt
+++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPageCreator.kt
@@ -4,7 +4,7 @@ import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
 import org.jetbrains.dokka.base.DokkaBase
 import org.jetbrains.dokka.base.signatures.SignatureProvider
 import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter
-import org.jetbrains.dokka.base.translators.documentables.briefFromContentNodes
+import org.jetbrains.dokka.base.translators.documentables.firstSentenceBriefFromContentNodes
 import org.jetbrains.dokka.javadoc.pages.*
 import org.jetbrains.dokka.model.*
 import org.jetbrains.dokka.model.doc.Description
@@ -194,10 +194,10 @@ open class JavadocPageCreator(context: DokkaContext) {
             ?: throw IllegalStateException("No source set found for ${jvm.sourceSetID} ")
 
     private fun Documentable.brief(sourceSet: DokkaSourceSet? = highestJvmSourceSet): List<ContentNode> =
-        briefFromContentNodes(descriptionToContentNodes(sourceSet))
+        firstSentenceBriefFromContentNodes(descriptionToContentNodes(sourceSet))
 
     private fun DParameter.brief(sourceSet: DokkaSourceSet? = highestJvmSourceSet): List<ContentNode> =
-        briefFromContentNodes(paramsToContentNodes(sourceSet).dropWhile { it is ContentDRILink })
+        firstSentenceBriefFromContentNodes(paramsToContentNodes(sourceSet).dropWhile { it is ContentDRILink })
 
     private fun ContentNode.asJavadocNode(): JavadocSignatureContentNode =
         (this as ContentGroup).firstChildOfTypeOrNull<JavadocSignatureContentNode>()
-- 
cgit