diff options
Diffstat (limited to 'plugins/base')
7 files changed, 249 insertions, 81 deletions
diff --git a/plugins/base/base-test-utils/api/base-test-utils.api b/plugins/base/base-test-utils/api/base-test-utils.api index b07dbb9e..a6bdc192 100644 --- a/plugins/base/base-test-utils/api/base-test-utils.api +++ b/plugins/base/base-test-utils/api/base-test-utils.api @@ -99,6 +99,7 @@ public final class signatures/SignatureUtilsKt { public static final fun renderedContent (Lutils/TestOutputWriter;Ljava/lang/String;)Lorg/jsoup/nodes/Element; public static synthetic fun renderedContent$default (Lutils/TestOutputWriter;Ljava/lang/String;ILjava/lang/Object;)Lorg/jsoup/nodes/Element; public static final fun signature (Lorg/jsoup/nodes/Element;)Lorg/jsoup/select/Elements; + public static final fun tab (Lorg/jsoup/nodes/Element;Ljava/lang/String;)Lorg/jsoup/select/Elements; } public final class utils/A : utils/Tag { diff --git a/plugins/base/base-test-utils/src/main/kotlin/renderers/SignatureUtils.kt b/plugins/base/base-test-utils/src/main/kotlin/renderers/SignatureUtils.kt index 3397b2b4..f8b2c453 100644 --- a/plugins/base/base-test-utils/src/main/kotlin/renderers/SignatureUtils.kt +++ b/plugins/base/base-test-utils/src/main/kotlin/renderers/SignatureUtils.kt @@ -11,6 +11,7 @@ fun TestOutputWriter.renderedContent(path: String = "root/example.html"): Elemen .single() fun Element.signature(): Elements = select("div.symbol.monospace") +fun Element.tab(tabName: String): Elements = select("div[data-togglable=\"$tabName\"]") fun Element.firstSignature(): Element = signature().first() ?: throw NoSuchElementException("No signature found") fun Element.lastSignature(): Element = signature().last() ?: throw NoSuchElementException("No signature found") diff --git a/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt b/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt index 1da8e3d1..22f67445 100644 --- a/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt +++ b/plugins/base/src/main/kotlin/signatures/KotlinSignatureProvider.kt @@ -309,24 +309,28 @@ class KotlinSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLog annotationsBlock(f) f.visibility[sourceSet]?.takeIf { it !in ignoredVisibilities }?.name?.let { keyword("$it ") } if (f.isExpectActual) keyword(if (sourceSet == f.expectPresentInSet) "expect " else "actual ") - f.modifier[sourceSet]?.takeIf { it !in ignoredModifiers }?.let { - if (it is JavaModifier.Empty) KotlinModifier.Open else it - }?.name?.let { keyword("$it ") } - f.modifiers()[sourceSet]?.toSignatureString()?.let { keyword(it) } - keyword("fun ") - val usedGenerics = if (f.isConstructor) f.generics.filter { f uses it } else f.generics - list(usedGenerics, prefix = "<", suffix = "> ", - separatorStyles = mainStyles + TokenStyle.Punctuation, - surroundingCharactersStyle = mainStyles + TokenStyle.Operator) { - annotationsInline(it) - +buildSignature(it) - } - f.receiver?.also { - signatureForProjection(it.type) - punctuation(".") + if (f.isConstructor) { + keyword("constructor") + } else { + f.modifier[sourceSet]?.takeIf { it !in ignoredModifiers }?.let { + if (it is JavaModifier.Empty) KotlinModifier.Open else it + }?.name?.let { keyword("$it ") } + f.modifiers()[sourceSet]?.toSignatureString()?.let { keyword(it) } + keyword("fun ") + list( + f.generics, prefix = "<", suffix = "> ", + separatorStyles = mainStyles + TokenStyle.Punctuation, + surroundingCharactersStyle = mainStyles + TokenStyle.Operator + ) { + annotationsInline(it) + +buildSignature(it) + } + f.receiver?.also { + signatureForProjection(it.type) + punctuation(".") + } + link(f.name, f.dri, styles = mainStyles + TokenStyle.Function + f.stylesIfDeprecated(sourceSet)) } - link(f.name, f.dri, styles = mainStyles + TokenStyle.Function + f.stylesIfDeprecated(sourceSet)) - // for a function, opening and closing parentheses must be present // anyway, even if it has no parameters, resulting in `fun test(): R` punctuation("(") diff --git a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt index 12fbb33c..ff60170c 100644 --- a/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt +++ b/plugins/base/src/main/kotlin/translators/documentables/DefaultPageCreator.kt @@ -377,7 +377,7 @@ open class DefaultPageCreator( "Constructors", 2, ContentKind.Constructors, - constructorsToDocumented.groupBy { it.parameters.map { it.dri } } + constructorsToDocumented.groupBy { it.name } .map { (_, v) -> v.first().name to v }, @Suppress("UNCHECKED_CAST") (csWithConstructor as List<Documentable>).sourceSets, diff --git a/plugins/base/src/test/kotlin/content/functions/ContentForBriefTest.kt b/plugins/base/src/test/kotlin/content/functions/ContentForBriefTest.kt index 50f9e357..f86506af 100644 --- a/plugins/base/src/test/kotlin/content/functions/ContentForBriefTest.kt +++ b/plugins/base/src/test/kotlin/content/functions/ContentForBriefTest.kt @@ -1,12 +1,14 @@ package content.functions import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.links.DRI 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.Assert.assertEquals +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import kotlin.test.assertNotNull import kotlin.test.assertNull @@ -55,26 +57,19 @@ class ContentForBriefTest : BaseAbstractTest() { |} """.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 WithDocumentables).documentables.firstOrNull() is DClass } as ContentPage - val constructorsTable = - classPage.content.dfs { it is ContentTable && it.dci.kind == ContentKind.Constructors } as ContentTable + val classPage = module.findClassPage("Example") - assertEquals(2, constructorsTable.children.size) - val primary = constructorsTable.children.first { - it.dci.dri.first().callable?.params?.first() == TypeConstructor( - "kotlin.Int", - emptyList() - ) + val constructorsWithBriefs = classPage.findConstructorsWithBriefs() + val constructorDocs = constructorsWithBriefs.findConstructorDocs { + it.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) + assertEquals("constructor docs", constructorDocs.text) } } } @@ -83,32 +78,47 @@ class ContentForBriefTest : BaseAbstractTest() { fun `secondary constructor should not inherit docs from its parameter`() { testInline(codeWithSecondaryAndPrimaryConstructorsDocumented, testConfiguration) { pagesTransformationStage = { module -> - val classPage = - module.dfs { it.name == "Example" && (it as WithDocumentables).documentables.firstOrNull() is DClass } as ContentPage - val constructorsTable = - classPage.content.dfs { it is ContentTable && it.dci.kind == ContentKind.Constructors } as ContentTable + val classPage = module.findClassPage("Example") - assertEquals(2, constructorsTable.children.size) - val primary = constructorsTable.children.first { - it.dci.dri.first().callable?.params?.first() == TypeConstructor( - "kotlin.String", - emptyList() - ) + val constructorsWithBriefs = classPage.findConstructorsWithBriefs() + val constructorDocs = constructorsWithBriefs.findConstructorDocs { + it.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) + assertEquals("secondary constructor", constructorDocs.text) } } } + /** + * All constructors are merged in one block (like overloaded functions). + * That leads to the structure where content block (`constructorsWithBriefs`) consist of plain list + * of constructors and briefs. In that list constructor is above, brief is below. + */ + private fun ContentPage.findConstructorsWithBriefs(): List<ContentNode> { + val constructorsTable = this.content.dfs { + it is ContentTable && it.dci.kind == ContentKind.Constructors + } as ContentTable + + val constructorsWithBriefs = constructorsTable.dfs { + it is ContentGroup && it.dci.kind == ContentKind.SourceSetDependentHint + }?.children + assertNotNull(constructorsWithBriefs, "Content node with constructors and briefs is not found") + + return constructorsWithBriefs + } + + private fun List<ContentNode>.findConstructorDocs(constructorMatcher: (DRI) -> Boolean): ContentText { + val constructorIndex = this.indexOfFirst { constructorMatcher(it.dci.dri.first()) } + return this[constructorIndex + 1] // expect that the relevant comment is below the constructor + .dfs { it is ContentText && it.dci.kind == ContentKind.Comment } as ContentText + } + @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 WithDocumentables).documentables.firstOrNull() is DClass } as ContentPage + val classPage = module.findClassPage("Example") val constructorsTable = classPage.content.dfs { it is ContentTable && it.dci.kind == ContentKind.Constructors } as ContentTable @@ -318,8 +328,15 @@ class ContentForBriefTest : BaseAbstractTest() { } } + private fun RootPageNode.findClassPage(className: String): ContentPage { + return this.dfs { + it.name == className && (it as WithDocumentables).documentables.firstOrNull() is DClass + } as ContentPage + } + private fun RootPageNode.singleFunctionDescription(className: String): ContentGroup { - val classPage = dfs { it.name == className && (it as WithDocumentables).documentables.firstOrNull() is DClass } as ContentPage + val classPage = + dfs { it.name == className && (it as WithDocumentables).documentables.firstOrNull() is DClass } as ContentPage val functionsTable = classPage.content.dfs { it is ContentTable && it.dci.kind == ContentKind.Functions } as ContentTable diff --git a/plugins/base/src/test/kotlin/content/signatures/ConstructorsSignaturesTest.kt b/plugins/base/src/test/kotlin/content/signatures/ConstructorsSignaturesTest.kt index 943d1bc4..c8c088f7 100644 --- a/plugins/base/src/test/kotlin/content/signatures/ConstructorsSignaturesTest.kt +++ b/plugins/base/src/test/kotlin/content/signatures/ConstructorsSignaturesTest.kt @@ -1,11 +1,9 @@ package content.signatures import matchers.content.* -import org.jetbrains.dokka.pages.ContentPage import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.pages.ContentPage import org.junit.jupiter.api.Test -import utils.ParamAttributes -import utils.functionSignature class ConstructorsSignaturesTest : BaseAbstractTest() { private val testConfiguration = dokkaConfiguration { @@ -196,24 +194,133 @@ class ConstructorsSignaturesTest : BaseAbstractTest() { table { group { link { +"SomeClass" } - functionSignature( - annotations = emptyMap(), - visibility = "", - modifier = "", - keywords = emptySet(), - name = "SomeClass" - ) + platformHinted { + group { + +"constructor" + +"(" + +")" + } + group { + +"constructor" + +"(" + group { + group { + +"a: " + group { link { +"String" } } + } + } + +")" + } + } + } + } + skipAllNotMatching() + } + } + } + } + } + + + @Test + fun `class with a few documented constructors`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + | /** + | * some comment + | * @constructor ctor comment + | **/ + |class SomeClass(a: String){ + | /** + | * ctor one + | **/ + | constructor(): this("") + | + | /** + | * ctor two + | **/ + | constructor(b: Int): this("") + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "SomeClass" } as ContentPage + page.content.assertNode { + group { + header(1) { +"SomeClass" } + platformHinted { + group { + +"class " + link { +"SomeClass" } + +"(" + group { + group { + +"a: " + group { link { +"String" } } + } + } + +")" } + skipAllNotMatching() + } + } + group { + header { +"Constructors" } + table { group { link { +"SomeClass" } - functionSignature( - annotations = emptyMap(), - visibility = "", - modifier = "", - keywords = emptySet(), - name = "SomeClass", - params = listOf("a" to ParamAttributes(emptyMap(), emptySet(), "String")).toTypedArray() - ) + platformHinted { + group { + +"constructor" + +"(" + +")" + } + group { + group { + group { +"ctor one" } + } + } + group { + +"constructor" + +"(" + group { + group { + +"b: " + group { + link { +"Int" } + } + } + } + +")" + } + group { + group { + group { +"ctor two" } + } + } + group { + +"constructor" + +"(" + group { + group { + +"a: " + group { + link { +"String" } + } + } + } + +")" + } + group { + group { + group { +"ctor comment" } + } + } + } } } skipAllNotMatching() @@ -266,8 +373,7 @@ class ConstructorsSignaturesTest : BaseAbstractTest() { link { +"SomeClass" } platformHinted { group { - +"fun " - link { +"SomeClass" } + +"constructor" +"(" group { group { diff --git a/plugins/base/src/test/kotlin/signatures/SignatureTest.kt b/plugins/base/src/test/kotlin/signatures/SignatureTest.kt index 13e103b4..77c92d9c 100644 --- a/plugins/base/src/test/kotlin/signatures/SignatureTest.kt +++ b/plugins/base/src/test/kotlin/signatures/SignatureTest.kt @@ -8,7 +8,6 @@ import org.jetbrains.dokka.model.dfs import org.junit.jupiter.api.Test import utils.* import kotlin.test.assertEquals -import kotlin.test.assertFalse class SignatureTest : BaseAbstractTest() { private val configuration = dokkaConfiguration { @@ -760,38 +759,50 @@ class SignatureTest : BaseAbstractTest() { writerPlugin.writer.renderedContent("root/example/-generic-class/-generic-class.html").signature().zip( listOf( arrayOf( - "fun <", A("T"), "> ", A("GenericClass"), "(", Parameters( + "constructor(", + Parameters( Parameter("x: ", A("T")) - ), ")", + ), + ")", ), arrayOf( - "fun ", A("GenericClass"), "(", Parameters( + "constructor(", + Parameters( Parameter("x: ", A("Int"), ", "), Parameter("y: ", A("String")) - ), ")", + ), + ")", ), arrayOf( - "fun <", A("T"), "> ", A("GenericClass"), "(", Parameters( + "constructor(", + Parameters( Parameter("x: ", A("Int"), ", "), Parameter("y: ", A("List"), "<", A("T"), ">") - ), ")", + ), + ")", ), arrayOf( - "fun ", A("GenericClass"), "(", Parameters( + "constructor(", + Parameters( Parameter("x: ", A("Boolean"), ", "), Parameter("y: ", A("Int"), ", "), Parameter("z:", A("String")) - ), ")", + ), + ")", ), arrayOf( - "fun <", A("T"), "> ", A("GenericClass"), "(", Parameters( + "constructor(", + Parameters( Parameter("x: ", A("List"), "<", A("Comparable"), "<", A("Lazy"), "<", A("T"), ">>>?") - ), ")", + ), + ")", ), arrayOf( - "fun ", A("GenericClass"), "(", Parameters( + "constructor(", + Parameters( Parameter("x: ", A("Int")) - ), ")", + ), + ")", ), ) ).forEach { @@ -802,6 +813,34 @@ class SignatureTest : BaseAbstractTest() { } @Test + fun `constructor has its own custom signature keyword in Constructor tab`() { + val writerPlugin = TestOutputWriterPlugin() + + testInline( + """ + |/src/main/kotlin/common/Test.kt + |package example + | + |class PrimaryConstructorClass(x: String) { } + """.trimMargin(), + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val constructorTabFirstElement = + writerPlugin.writer.renderedContent("root/example/-primary-constructor-class/index.html") + .tab("Constructors") + .first() ?: throw NoSuchElementException("No Constructors tab found or it is empty") + + constructorTabFirstElement.firstSignature().match( + "constructor(", Parameters(Parameter("x: ", A("String"))), ")", + ignoreSpanWithTokenStyle = true + ) + } + } + } + + @Test fun `primary constructor with properties check for all tokens`() { val writerPlugin = TestOutputWriterPlugin() |