diff options
Diffstat (limited to 'plugins/base')
10 files changed, 457 insertions, 191 deletions
diff --git a/plugins/base/src/main/kotlin/transformers/pages/comments/DocTagToContentConverter.kt b/plugins/base/src/main/kotlin/transformers/pages/comments/DocTagToContentConverter.kt index 0f953e0f..9d667623 100644 --- a/plugins/base/src/main/kotlin/transformers/pages/comments/DocTagToContentConverter.kt +++ b/plugins/base/src/main/kotlin/transformers/pages/comments/DocTagToContentConverter.kt @@ -53,6 +53,9 @@ object DocTagToContentConverter : CommentsToContentConverter { ) ) + fun P.collapseParagraphs(): P = + if (children.size == 1 && children.first() is P) (children.first() as P).collapseParagraphs() else this + return when (docTag) { is H1 -> buildHeader(1) is H2 -> buildHeader(2) @@ -63,12 +66,14 @@ object DocTagToContentConverter : CommentsToContentConverter { is Ul -> buildList(false) is Ol -> buildList(true, docTag.params["start"]?.toInt() ?: 1) is Li -> listOf( - ContentGroup(children = buildChildren(docTag), dci, sourceSets, styles, extra) + ContentGroup(buildChildren(docTag), dci, sourceSets, styles, extra) ) is Br -> buildNewLine() is B -> buildChildren(docTag, setOf(TextStyle.Strong)) is I -> buildChildren(docTag, setOf(TextStyle.Italic)) - is P -> buildChildren(docTag, newStyles = setOf(TextStyle.Paragraph)) + is P -> listOf( + ContentGroup(buildChildren(docTag.collapseParagraphs()), dci, sourceSets, styles + setOf(TextStyle.Paragraph), extra) + ) is A -> listOf( ContentResolvedLink( buildChildren(docTag), diff --git a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt index df5d4ee1..9ed37c30 100644 --- a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt @@ -285,8 +285,7 @@ class DefaultPsiToDocumentableTranslator( psiParameter.name, DocumentationNode( listOfNotNull(docs.firstChildOfTypeOrNull<Param> { - it.firstChildOfTypeOrNull<DocumentationLink>() - ?.firstChildOfTypeOrNull<Text>()?.body == psiParameter.name + it.name == psiParameter.name })).toSourceSetDependent(), null, getBound(psiParameter.type), diff --git a/plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt b/plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt index 81955fde..8262b3c6 100644 --- a/plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt +++ b/plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt @@ -34,18 +34,30 @@ class JavadocParser( docComment.getDescription()?.let { nodes.add(it) } nodes.addAll(docComment.tags.mapNotNull { tag -> when (tag.name) { - "param" -> Param(P(convertJavadocElements(tag.dataElements.toList())), tag.text) - "throws" -> Throws(P(convertJavadocElements(tag.dataElements.toList())), tag.text) - "return" -> Return(P(convertJavadocElements(tag.dataElements.toList()))) - "author" -> Author(P(convertJavadocElements(tag.dataElements.toList()))) - "see" -> See(P(getSeeTagElementContent(tag)), tag.referenceElement()?.text.orEmpty(), null) - "deprecated" -> Deprecated(P(convertJavadocElements(tag.dataElements.toList()))) + "param" -> Param( + wrapTagIfNecessary(convertJavadocElements(tag.contentElements())), + tag.children.firstIsInstanceOrNull<PsiDocParamRef>()?.text.orEmpty() + ) + "throws" -> Throws(wrapTagIfNecessary(convertJavadocElements(tag.contentElements())), tag.text) + "return" -> Return(wrapTagIfNecessary(convertJavadocElements(tag.contentElements()))) + "author" -> Author(wrapTagIfNecessary(convertJavadocElements(tag.contentElements()))) + "see" -> getSeeTagElementContent(tag).let { + See( + wrapTagIfNecessary(it.first), + tag.referenceElement()?.text.orEmpty(), + it.second + ) + } + "deprecated" -> Deprecated(wrapTagIfNecessary(convertJavadocElements(tag.dataElements.toList()))) else -> null } }) return DocumentationNode(nodes) } + private fun wrapTagIfNecessary(list: List<DocTag>): DocTag = + if (list.size == 1) list.first() else P(list) + private fun findClosestDocComment(element: PsiNamedElement): PsiDocComment? { (element as? PsiDocCommentOwner)?.docComment?.run { return this } if (element is PsiMethod) { @@ -119,88 +131,118 @@ class JavadocParser( } } - private fun getSeeTagElementContent(tag: PsiDocTag): List<DocTag> = - listOfNotNull(tag.referenceElement()?.toDocumentationLink()) + private fun getSeeTagElementContent(tag: PsiDocTag): Pair<List<DocumentationLink>, DRI?> { + val content = tag.referenceElement()?.toDocumentationLink() + return Pair(listOfNotNull(content), content?.dri) + } private fun PsiDocComment.getDescription(): Description? { - val nonEmptyDescriptionElements = descriptionElements.filter { it.text.trim().isNotEmpty() } - val convertedDescriptionElements = convertJavadocElements(nonEmptyDescriptionElements) - if (convertedDescriptionElements.isNotEmpty()) { - return Description(P(convertedDescriptionElements)) + val nonEmptyDescriptionElements = descriptionElements.filter { it.text.isNotBlank() } + return convertJavadocElements(nonEmptyDescriptionElements).takeIf { it.isNotEmpty() }?.let { + Description(wrapTagIfNecessary(it)) } - - return null } - private fun convertJavadocElements(elements: Iterable<PsiElement>): List<DocTag> = - elements.mapNotNull { - when (it) { - is PsiReference -> convertJavadocElements(it.children.toList()) - is PsiInlineDocTag -> listOfNotNull(convertInlineDocTag(it)) - is PsiDocParamRef -> listOfNotNull(it.toDocumentationLink()) - is PsiDocTagValue, - is LeafPsiElement -> Jsoup.parse(it.text.trim()).body().childNodes().mapNotNull(::convertHtmlNode) - else -> null + private inner class Parse : (Iterable<PsiElement>, Boolean) -> List<DocTag> { + val driMap = mutableMapOf<String, DRI>() + + private fun PsiElement.stringify(): String? = when (this) { + is PsiReference -> children.joinToString("") { it.stringify().orEmpty() } + is PsiInlineDocTag -> convertInlineDocTag(this) + is PsiDocParamRef -> toDocumentationLinkString() + is PsiDocTagValue, + is LeafPsiElement -> (if ((prevSibling as? PsiDocToken)?.isLeadingAsterisk() == true) text?.trim() else text)?.takeUnless { it.isBlank() } + else -> null + } + + private fun PsiElement.toDocumentationLinkString( + labelElement: PsiElement? = null + ): String? = + reference?.resolve()?.let { + if (it !is PsiParameter) { + val dri = DRI.from(it) + driMap[dri.toString()] = dri + val label = labelElement ?: defaultLabel() + """<a data-dri=$dri>${label.text}</a>""" + } else null } - }.flatten() - private fun convertHtmlNode(node: Node, insidePre: Boolean = false): DocTag? = when (node) { - is TextNode -> Text(body = if (insidePre) node.wholeText else node.text()) - is Element -> createBlock(node) - else -> null - } + private fun convertInlineDocTag(tag: PsiInlineDocTag) = when (tag.name) { + "link", "linkplain" -> { + tag.referenceElement()?.toDocumentationLinkString(tag.dataElements.firstIsInstanceOrNull<PsiDocToken>()) + } + "code", "literal" -> { + "<code data-inline>${tag.text}</code>" + } + "index" -> "<index>${tag.children.filterIsInstance<PsiDocTagValue>().joinToString { it.text }}</index>" + else -> tag.text + } - private fun createBlock(element: Element): DocTag { - val children = element.childNodes().mapNotNull { convertHtmlNode(it) } - return when (element.tagName()) { - "p" -> P(listOf(Br, Br) + children) - "b" -> B(children) - "strong" -> Strong(children) - "i" -> I(children) - "em" -> Em(children) - "code" -> CodeBlock(children) - "pre" -> Pre(children) - "ul" -> Ul(children) - "ol" -> Ol(children) - "li" -> Li(children) - "a" -> createLink(element, children) - else -> Text(body = element.ownText()) + private fun createLink(element: Element, children: List<DocTag>): DocTag { + return when { + element.hasAttr("docref") -> + A(children, params = mapOf("docref" to element.attr("docref"))) + element.hasAttr("href") -> + A(children, params = mapOf("href" to element.attr("href"))) + element.hasAttr("data-dri") && driMap.containsKey(element.attr("data-dri")) -> + DocumentationLink(driMap[element.attr("data-dri")]!!, children) + else -> Text(children = children) + } } - } - private fun createLink(element: Element, children: List<DocTag>): DocTag { - return when { - element.hasAttr("docref") -> { - A(children, params = mapOf("docref" to element.attr("docref"))) + private fun createBlock(element: Element): DocTag? { + val children = element.childNodes().mapNotNull { convertHtmlNode(it) } + fun ifChildrenPresent(operation: () -> DocTag): DocTag? { + return if (children.isNotEmpty()) operation() else null } - element.hasAttr("href") -> { - A(children, params = mapOf("href" to element.attr("href"))) + return when (element.tagName()) { + "blockquote" -> ifChildrenPresent { BlockQuote(children) } + "p" -> ifChildrenPresent { P(children) } + "b" -> ifChildrenPresent { B(children) } + "strong" -> ifChildrenPresent { Strong(children) } + "index" -> Index(children) + "i" -> ifChildrenPresent { I(children) } + "em" -> Em(children) + "code" -> ifChildrenPresent { + if (element.hasAttr("data-inline")) CodeInline(children) else CodeBlock( + children + ) + } + "pre" -> Pre(children) + "ul" -> ifChildrenPresent { Ul(children) } + "ol" -> ifChildrenPresent { Ol(children) } + "li" -> Li(children) + "a" -> createLink(element, children) + else -> Text(body = element.ownText()) } - else -> Text(children = children) } + + private fun convertHtmlNode(node: Node, insidePre: Boolean = false): DocTag? = when (node) { + is TextNode -> Text(body = if (insidePre) node.wholeText else node.text()) + is Element -> createBlock(node) + else -> null + } + + override fun invoke(elements: Iterable<PsiElement>, asParagraph: Boolean): List<DocTag> = + Jsoup.parseBodyFragment(elements.mapNotNull { it.stringify() }.joinToString(" ", prefix = if (asParagraph) "<p>" else "")) + .body().childNodes().mapNotNull { convertHtmlNode(it) } } + private fun PsiDocTag.contentElements(): List<PsiElement> = + dataElements.mapNotNull { it.takeIf { it.text.isNotBlank() } } + + private fun convertJavadocElements(elements: Iterable<PsiElement>, asParagraph: Boolean = true): List<DocTag> = Parse()(elements, asParagraph) + private fun PsiDocToken.isSharpToken() = tokenType.toString() == "DOC_TAG_VALUE_SHARP_TOKEN" + private fun PsiDocToken.isLeadingAsterisk() = tokenType.toString() == "DOC_COMMENT_LEADING_ASTERISKS" + private fun PsiElement.toDocumentationLink(labelElement: PsiElement? = null) = reference?.resolve()?.let { val dri = DRI.from(it) - val label = labelElement ?: children.firstOrNull { - it is PsiDocToken && it.text.isNotBlank() && !it.isSharpToken() - } ?: this - DocumentationLink(dri, convertJavadocElements(listOfNotNull(label))) - } - - private fun convertInlineDocTag(tag: PsiInlineDocTag) = when (tag.name) { - "link", "linkplain" -> { - tag.referenceElement()?.toDocumentationLink(tag.dataElements.firstIsInstanceOrNull<PsiDocToken>()) + val label = labelElement ?: defaultLabel() + DocumentationLink(dri, convertJavadocElements(listOfNotNull(label), asParagraph = false)) } - "code", "literal" -> { - CodeInline(listOf(Text(tag.text))) - } - "index" -> Index(tag.children.filterIsInstance<PsiDocTagValue>().map { Text(it.text) }) - else -> Text(tag.text) - } private fun PsiDocTag.referenceElement(): PsiElement? = linkElement()?.let { @@ -211,6 +253,10 @@ class JavadocParser( } } + private fun PsiElement.defaultLabel() = children.firstOrNull { + it is PsiDocToken && it.text.isNotBlank() && !it.isSharpToken() + } ?: this + private fun PsiDocTag.linkElement(): PsiElement? = valueElement ?: dataElements.firstOrNull { it !is PsiWhiteSpace } } diff --git a/plugins/base/src/test/kotlin/content/params/ContentForParamsTest.kt b/plugins/base/src/test/kotlin/content/params/ContentForParamsTest.kt index a9689bc5..4ac5717d 100644 --- a/plugins/base/src/test/kotlin/content/params/ContentForParamsTest.kt +++ b/plugins/base/src/test/kotlin/content/params/ContentForParamsTest.kt @@ -86,7 +86,7 @@ class ContentForParamsTest : AbstractCoreTest() { divergentGroup { divergentInstance { before { - pWrapped("comment to function") + group { pWrapped("comment to function") } } divergent { bareSignature( @@ -131,8 +131,8 @@ class ContentForParamsTest : AbstractCoreTest() { divergentGroup { divergentInstance { before { - unnamedTag("Author") { +"Kordyjan" } - unnamedTag("Since") { +"0.11" } + unnamedTag("Author") { group { +"Kordyjan" } } + unnamedTag("Since") { group { +"0.11" } } } divergent { bareSignature( @@ -178,9 +178,9 @@ class ContentForParamsTest : AbstractCoreTest() { divergentGroup { divergentInstance { before { - pWrapped("comment to function") - unnamedTag("Author") { +"Kordyjan" } - unnamedTag("Since") { +"0.11" } + group { pWrapped("comment to function") } + unnamedTag("Author") { group { +"Kordyjan" } } + unnamedTag("Since") { group { +"0.11" } } } divergent { bareSignature( @@ -225,14 +225,14 @@ class ContentForParamsTest : AbstractCoreTest() { divergentGroup { divergentInstance { before { - pWrapped("comment to function") + group { pWrapped("comment to function") } header(2) { +"Parameters" } group { platformHinted { table { group { +"abc" - group { +"comment to param" } + group { group { +"comment to param" } } } } } @@ -283,22 +283,22 @@ class ContentForParamsTest : AbstractCoreTest() { divergentGroup { divergentInstance { before { - pWrapped("comment to function") + group { group { group { +"comment to function" } } } header(2) { +"Parameters" } group { platformHinted { table { group { +"first" - group { +"comment to first param" } + group { group { +"comment to first param" } } } group { +"second" - group { +"comment to second param" } + group { group { +"comment to second param" } } } group { +"third" - group { +"comment to third param" } + group { group { +"comment to third param" } } } } } @@ -351,15 +351,15 @@ class ContentForParamsTest : AbstractCoreTest() { table { group { +"first" - group { +"comment to first param" } + group { group { +"comment to first param" } } } group { +"second" - group { +"comment to second param" } + group { group { +"comment to second param" } } } group { +"third" - group { +"comment to third param" } + group { group { +"comment to third param" } } } } } @@ -406,18 +406,18 @@ class ContentForParamsTest : AbstractCoreTest() { divergentGroup { divergentInstance { before { - pWrapped("comment to function") + group { pWrapped("comment to function") } header(2) { +"Parameters" } group { platformHinted { table { group { +"<receiver>" - group { +"comment to receiver" } + group { group { +"comment to receiver" } } } group { +"abc" - group { +"comment to param" } + group { group { +"comment to param" } } } } } @@ -468,18 +468,18 @@ class ContentForParamsTest : AbstractCoreTest() { divergentGroup { divergentInstance { before { - pWrapped("comment to function") + group { group { group { +"comment to function" } } } header(2) { +"Parameters" } group { platformHinted { table { group { +"first" - group { +"comment to first param" } + group { group { +"comment to first param" } } } group { +"third" - group { +"comment to third param" } + group { group { +"comment to third param" } } } } } @@ -529,9 +529,9 @@ class ContentForParamsTest : AbstractCoreTest() { divergentGroup { divergentInstance { before { - pWrapped("comment to function") - unnamedTag("Author") { +"Kordyjan" } - unnamedTag("Since") { +"0.11" } + group { pWrapped("comment to function") } + unnamedTag("Author") { group { +"Kordyjan" } } + unnamedTag("Since") { group { +"0.11" } } header(2) { +"Parameters" } group { @@ -539,15 +539,15 @@ class ContentForParamsTest : AbstractCoreTest() { table { group { +"first" - group { +"comment to first param" } + group { group { +"comment to first param" } } } group { +"second" - group { +"comment to second param" } + group { group { +"comment to second param" } } } group { +"third" - group { +"comment to third param" } + group { group { +"comment to third param" } } } } } diff --git a/plugins/base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt b/plugins/base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt index 24970660..fd51c895 100644 --- a/plugins/base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt +++ b/plugins/base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt @@ -89,7 +89,7 @@ class ContentForSeeAlsoTest : AbstractCoreTest() { group { //DRI should be "test//abc/#/-1/" link { +"abc" } - group { } + group { group { } } } } } @@ -144,7 +144,9 @@ class ContentForSeeAlsoTest : AbstractCoreTest() { group { //DRI should be "test//abc/#/-1/" link { +"abc" } - group { +"Comment to abc" } + group { + group { +"Comment to abc" } + } } } } @@ -199,7 +201,9 @@ class ContentForSeeAlsoTest : AbstractCoreTest() { group { //DRI should be "kotlin.collections/Collection////" link { +"Collection" } - group { } + group { + group { } + } } } } @@ -254,7 +258,9 @@ class ContentForSeeAlsoTest : AbstractCoreTest() { group { //DRI should be "test//abc/#/-1/" link { +"Collection" } - group { +"Comment to stdliblink" } + group { + group { +"Comment to stdliblink" } + } } } } @@ -305,9 +311,9 @@ class ContentForSeeAlsoTest : AbstractCoreTest() { divergentGroup { divergentInstance { before { - pWrapped("random comment") - unnamedTag("Author") { +"pikinier20" } - unnamedTag("Since") { +"0.11" } + group { group { group { +"random comment"} } } + unnamedTag("Author") { group { +"pikinier20" } } + unnamedTag("Since") { group { +"0.11" } } header(2) { +"See also" } group { @@ -316,7 +322,9 @@ class ContentForSeeAlsoTest : AbstractCoreTest() { group { //DRI should be "test//abc/#/-1/" link { +"Collection" } - group { +"Comment to stdliblink" } + group { + group { +"Comment to stdliblink" } + } } } } @@ -372,7 +380,9 @@ class ContentForSeeAlsoTest : AbstractCoreTest() { group { //DRI should be "test//abc/#/-1/" link { +"abc" } - group { +"Comment to abc2" } + group { + group { +"Comment to abc2" } + } } } } @@ -428,12 +438,14 @@ class ContentForSeeAlsoTest : AbstractCoreTest() { group { //DRI should be "test//abc/#/-1/" link { +"abc" } - group { +"Comment to abc1" } + group { + group { +"Comment to abc1" } + } } group { //DRI should be "test//abc/#/-1/" link { +"Collection" } - group { +"Comment to collection" } + group { group { +"Comment to collection" } } } } } diff --git a/plugins/base/src/test/kotlin/content/signatures/SkippingParenthesisForConstructorsTest.kt b/plugins/base/src/test/kotlin/content/signatures/SkippingParenthesisForConstructorsTest.kt index 90a38055..b3da3f71 100644 --- a/plugins/base/src/test/kotlin/content/signatures/SkippingParenthesisForConstructorsTest.kt +++ b/plugins/base/src/test/kotlin/content/signatures/SkippingParenthesisForConstructorsTest.kt @@ -230,7 +230,7 @@ class ConstructorsSignaturesTest : AbstractCoreTest() { platformHinted { group { group { - +"ctor comment" + group { +"ctor comment" } } } group { diff --git a/plugins/base/src/test/kotlin/enums/EnumsTest.kt b/plugins/base/src/test/kotlin/enums/EnumsTest.kt index 6a973f8e..9cd41dcd 100644 --- a/plugins/base/src/test/kotlin/enums/EnumsTest.kt +++ b/plugins/base/src/test/kotlin/enums/EnumsTest.kt @@ -210,7 +210,9 @@ class EnumsTest : AbstractCoreTest() { platformHinted { group { group { - + "Sample docs for E1" + group { + +"Sample docs for E1" + } } } group { diff --git a/plugins/base/src/test/kotlin/transformers/CommentsToContentConverterTest.kt b/plugins/base/src/test/kotlin/transformers/CommentsToContentConverterTest.kt index 5197afc6..ad023d84 100644 --- a/plugins/base/src/test/kotlin/transformers/CommentsToContentConverterTest.kt +++ b/plugins/base/src/test/kotlin/transformers/CommentsToContentConverterTest.kt @@ -37,7 +37,7 @@ class CommentsToContentConverterTest { fun `simple text`() { val docTag = P(listOf(Text("This is simple test of string Next line"))) executeTest(docTag) { - +"This is simple test of string Next line" + group { +"This is simple test of string Next line" } } } @@ -51,9 +51,11 @@ class CommentsToContentConverterTest { ) ) executeTest(docTag) { - +"This is simple test of string" - node<ContentBreakLine>() - +"Next line" + group { + +"This is simple test of string" + node<ContentBreakLine>() + +"Next line" + } } } @@ -66,10 +68,14 @@ class CommentsToContentConverterTest { ) ) executeTest(docTag) { - +"Paragraph number one" - +"Paragraph" - node<ContentBreakLine>() - +"number two" + group { + group { +"Paragraph number one" } + group { + +"Paragraph" + node<ContentBreakLine>() + +"number two" + } + } } } @@ -122,20 +128,22 @@ class CommentsToContentConverterTest { ) ) executeTest(docTag) { - node<ContentList> { - group { +"Outer first Outer next line" } - group { +"Outer second" } + group { node<ContentList> { - group { +"Middle first Middle next line" } - group { +"Middle second" } + group { +"Outer first Outer next line" } + group { +"Outer second" } node<ContentList> { - group { +"Inner first Inner next line" } + group { +"Middle first Middle next line" } + group { +"Middle second" } + node<ContentList> { + group { +"Inner first Inner next line" } + } + group { +"Middle third" } } - group { +"Middle third" } + group { +"Outer third" } } - group { +"Outer third" } + group { +"New paragraph" } } - +"New paragraph" } } @@ -149,9 +157,11 @@ class CommentsToContentConverterTest { ) ) executeTest(docTag) { - header(1) { +"Header 1" } - +"Following text" - +"New paragraph" + group { + header(1) { +"Header 1" } + group { +"Following text" } + group { +"New paragraph" } + } } } @@ -174,18 +184,20 @@ class CommentsToContentConverterTest { ) ) executeTest(docTag) { - header(1) {+"Header 1"} - +"Text 1" - header(2) {+"Header 2"} - +"Text 2" - header(3) {+"Header 3"} - +"Text 3" - header(4) {+"Header 4"} - +"Text 4" - header(5) {+"Header 5"} - +"Text 5" - header(6) {+"Header 6"} - +"Text 6" + group { + header(1) { +"Header 1" } + group { +"Text 1" } + header(2) { +"Header 2" } + group { +"Text 2" } + header(3) { +"Header 3" } + group { +"Text 3" } + header(4) { +"Header 4" } + group { +"Text 4" } + header(5) { +"Header 5" } + group { +"Text 5" } + header(6) { +"Header 6" } + group { +"Text 6" } + } } } @@ -211,12 +223,14 @@ class CommentsToContentConverterTest { ) ) executeTest(docTag) { - node<ContentCodeBlock> { - +"Blockquotes are very handy in email to emulate reply text. This line is part of the same quote." - } - +"Quote break." - node<ContentCodeBlock> { - +"Quote" + group { + node<ContentCodeBlock> { + +"Blockquotes are very handy in email to emulate reply text. This line is part of the same quote." + } + group { +"Quote break." } + node<ContentCodeBlock> { + +"Quote" + } } } } @@ -245,16 +259,18 @@ class CommentsToContentConverterTest { ) ) executeTest(docTag) { - node<ContentCodeBlock> { - +"text 1 text 2" + group { node<ContentCodeBlock> { - +"text 3 text 4" + +"text 1 text 2" + node<ContentCodeBlock> { + +"text 3 text 4" + } + +"text 5" + } + group { +"Quote break." } + node<ContentCodeBlock> { + +"Quote" } - +"text 5" - } - +"Quote break." - node<ContentCodeBlock> { - +"Quote" } } } @@ -278,21 +294,23 @@ class CommentsToContentConverterTest { ) ) executeTest(docTag) { - node<ContentCodeBlock> { - +"val x: Int = 0" - node<ContentBreakLine>() - +"val y: String = \"Text\"" - node<ContentBreakLine>() - node<ContentBreakLine>() - +" val z: Boolean = true" - node<ContentBreakLine>() - +"for(i in 0..10) {" - node<ContentBreakLine>() - +" println(i)" - node<ContentBreakLine>() - +"}" + group { + node<ContentCodeBlock> { + +"val x: Int = 0" + node<ContentBreakLine>() + +"val y: String = \"Text\"" + node<ContentBreakLine>() + node<ContentBreakLine>() + +" val z: Boolean = true" + node<ContentBreakLine>() + +"for(i in 0..10) {" + node<ContentBreakLine>() + +" println(i)" + node<ContentBreakLine>() + +"}" + } + group { +"Sample text" } } - +"Sample text" } } @@ -307,7 +325,7 @@ class CommentsToContentConverterTest { ) ) executeTest(docTag) { - link { + group { link { +"I'm an inline-style link" check { assertEquals( @@ -315,7 +333,7 @@ class CommentsToContentConverterTest { "https://www.google.com" ) } - } + } } } } @@ -384,20 +402,24 @@ class CommentsToContentConverterTest { ) ) executeTest(docTag) { - node<ContentList> { - group { +"Outer first Outer next line" } - group { +"Outer second" } + group { node<ContentList> { - group { +"Middle first Middle next line" } - group { +"Middle second" } + group { +"Outer first Outer next line" } + group { +"Outer second" } node<ContentList> { - +"Inner first Inner next line" + group { +"Middle first Middle next line" } + group { +"Middle second" } + node<ContentList> { + +"Inner first Inner next line" + } + group { +"Middle third" } } - group { +"Middle third" } + group { +"Outer third" } + } + group { + +"New paragraph" } - group { +"Outer third" } } - +"New paragraph" } } }
\ No newline at end of file diff --git a/plugins/base/src/test/kotlin/translators/JavadocParserTest.kt b/plugins/base/src/test/kotlin/translators/JavadocParserTest.kt new file mode 100644 index 00000000..a1fbb2a0 --- /dev/null +++ b/plugins/base/src/test/kotlin/translators/JavadocParserTest.kt @@ -0,0 +1,156 @@ +package translators + +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.DModule +import org.jetbrains.dokka.model.childrenOfType +import org.jetbrains.dokka.model.doc.* +import org.jetbrains.dokka.model.firstChildOfType +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Assertions.* +import utils.text + +class JavadocParserTest : AbstractCoreTest() { + + private fun performJavadocTest(testOperation: (DModule) -> Unit) { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/java") + } + } + } + + testInline( + """ + |/src/main/java/sample/Date2.java + |/** + | * The class <code>Date</code> represents a specific instant + | * in time, with millisecond precision. + | * <p> + | * Prior to JDK 1.1, the class <code>Date</code> had two additional + | * functions. It allowed the interpretation of dates as year, month, day, hour, + | * minute, and second values. It also allowed the formatting and parsing + | * of date strings. Unfortunately, the API for these functions was not + | * amenable to internationalization. As of JDK 1.1, the + | * <code>Calendar</code> class should be used to convert between dates and time + | * fields and the <code>DateFormat</code> class should be used to format and + | * parse date strings. + | * The corresponding methods in <code>Date</code> are deprecated. + | * <p> + | * Although the <code>Date</code> class is intended to reflect + | * coordinated universal time (UTC), it may not do so exactly, + | * depending on the host environment of the Java Virtual Machine. + | * Nearly all modern operating systems assume that 1 day = + | * 24 × 60 × 60 = 86400 seconds + | * in all cases. In UTC, however, about once every year or two there + | * is an extra second, called a "leap second." The leap + | * second is always added as the last second of the day, and always + | * on December 31 or June 30. For example, the last minute of the + | * year 1995 was 61 seconds long, thanks to an added leap second. + | * Most computer clocks are not accurate enough to be able to reflect + | * the leap-second distinction. + | * <p> + | * Some computer standards are defined in terms of Greenwich mean + | * time (GMT), which is equivalent to universal time (UT). GMT is + | * the "civil" name for the standard; UT is the + | * "scientific" name for the same standard. The + | * distinction between UTC and UT is that UTC is based on an atomic + | * clock and UT is based on astronomical observations, which for all + | * practical purposes is an invisibly fine hair to split. Because the + | * earth's rotation is not uniform (it slows down and speeds up + | * in complicated ways), UT does not always flow uniformly. Leap + | * seconds are introduced as needed into UTC so as to keep UTC within + | * 0.9 seconds of UT1, which is a version of UT with certain + | * corrections applied. There are other time and date systems as + | * well; for example, the time scale used by the satellite-based + | * global positioning system (GPS) is synchronized to UTC but is + | * <i>not</i> adjusted for leap seconds. An interesting source of + | * further information is the U.S. Naval Observatory, particularly + | * the Directorate of Time at: + | * <blockquote><pre> + | * <a href=http://tycho.usno.navy.mil>http://tycho.usno.navy.mil</a> + | * </pre></blockquote> + | * <p> + | * and their definitions of "Systems of Time" at: + | * <blockquote><pre> + | * <a href=http://tycho.usno.navy.mil/systime.html>http://tycho.usno.navy.mil/systime.html</a> + | * </pre></blockquote> + | * <p> + | * In all methods of class <code>Date</code> that accept or return + | * year, month, date, hours, minutes, and seconds values, the + | * following representations are used: + | * <ul> + | * <li>A year <i>y</i> is represented by the integer + | * <i>y</i> <code>- 1900</code>. + | * <li>A month is represented by an integer from 0 to 11; 0 is January, + | * 1 is February, and so forth; thus 11 is December. + | * <li>A date (day of month) is represented by an integer from 1 to 31 + | * in the usual manner. + | * <li>An hour is represented by an integer from 0 to 23. Thus, the hour + | * from midnight to 1 a.m. is hour 0, and the hour from noon to 1 + | * p.m. is hour 12. + | * <li>A minute is represented by an integer from 0 to 59 in the usual manner. + | * <li>A second is represented by an integer from 0 to 61; the values 60 and + | * 61 occur only for leap seconds and even then only in Java + | * implementations that actually track leap seconds correctly. Because + | * of the manner in which leap seconds are currently introduced, it is + | * extremely unlikely that two leap seconds will occur in the same + | * minute, but this specification follows the date and time conventions + | * for ISO C. + | * </ul> + | * <p> + | * In all cases, arguments given to methods for these purposes need + | * not fall within the indicated ranges; for example, a date may be + | * specified as January 32 and is interpreted as meaning February 1. + | * + | * @author James Gosling + | * @author Arthur van Hoff + | * @author Alan Liu + | * @see java.text.DateFormat + | * @see java.util.Calendar + | * @since JDK1.0 + | * @apiSince 1 + | */ + |public class Date2 implements java.io.Serializable, java.lang.Cloneable, java.lang.Comparable<java.util.Date> { + | void x() { } + |} + """.trimIndent(), + configuration + ) { + documentablesMergingStage = testOperation + } + } + + @Test + fun `correctly parsed list`() { + performJavadocTest { module -> + val dateDescription = module.descriptionOf("Date2")!! + assertEquals(6, dateDescription.firstChildOfType<Ul>().children.filterIsInstance<Li>().size) + } + } + + @Test + fun `correctly parsed author tags`() { + performJavadocTest { module -> + val authors = module.findClasslike().documentation.values.single().childrenOfType<Author>() + assertEquals(3, authors.size) + assertEquals("James Gosling", authors[0].firstChildOfType<Text>().text()) + assertEquals("Arthur van Hoff", authors[1].firstChildOfType<Text>().text()) + assertEquals("Alan Liu", authors[2].firstChildOfType<Text>().text()) + } + } + + @Test + fun `correctly parsed see tags`() { + performJavadocTest { module -> + val sees = module.findClasslike().documentation.values.single().childrenOfType<See>() + assertEquals(2, sees.size) + assertEquals(DRI("java.text", "DateFormat"), sees[0].address) + assertEquals("java.text.DateFormat", sees[0].name) + assertEquals(DRI("java.util", "Calendar"), sees[1].address) + assertEquals("java.util.Calendar", sees[1].name) + } + } +} diff --git a/plugins/base/src/test/kotlin/translators/utils.kt b/plugins/base/src/test/kotlin/translators/utils.kt index 96d3035a..71b4a28b 100644 --- a/plugins/base/src/test/kotlin/translators/utils.kt +++ b/plugins/base/src/test/kotlin/translators/utils.kt @@ -1,16 +1,40 @@ package translators -import org.jetbrains.dokka.model.DModule +import org.jetbrains.dokka.model.* import org.jetbrains.dokka.model.doc.Description import org.jetbrains.dokka.model.doc.Text +import java.util.NoSuchElementException -fun DModule.documentationOf(className: String, functionName: String): String { - return (packages.single() - .classlikes.single { it.name == className } - .functions.single { it.name == functionName } - .documentation.values.singleOrNull() - ?.children?.singleOrNull() - .run { this as? Description } - ?.root?.children?.single() as? Text) +fun DModule.documentationOf(className: String, functionName: String? = null): String = + descriptionOf(className, functionName) + ?.firstChildOfType<Text>() ?.body.orEmpty() + +fun DModule.descriptionOf(className: String, functionName: String? = null): Description? { + val classlike = packages.single() + .classlikes.single { it.name == className } + val target: Documentable = + if (functionName != null) classlike.functions.single { it.name == functionName } else classlike + return target.documentation.values.singleOrNull() + ?.firstChildOfTypeOrNull<Description>() +} + +fun DModule.findPackage(packageName: String? = null) = + packageName?.let { packages.firstOrNull { pkg -> pkg.name == packageName } + ?: throw NoSuchElementException("No packageName with name $packageName") } ?: packages.single() + +fun DModule.findClasslike(packageName: String? = null, className: String? = null): DClasslike { + val pkg = findPackage(packageName) + return className?.let { + pkg.classlikes.firstOrNull { cls -> cls.name == className } + ?: throw NoSuchElementException("No classlike with name $className") + } ?: pkg.classlikes.single() +} + +fun DModule.findFunction(packageName: String? = null, className: String, functionName: String? = null): DFunction { + val classlike = findClasslike(packageName, className) + return functionName?.let { + classlike.functions.firstOrNull { fn -> fn.name == functionName } + ?: throw NoSuchElementException("No classlike with name $functionName") + } ?: classlike.functions.single() }
\ No newline at end of file |